Compare commits

..

1 Commits

Author SHA1 Message Date
akwizgran
580be7cfdc Log DB table names. 2022-06-08 15:10:11 +01:00
202 changed files with 1644 additions and 9969 deletions

View File

@@ -1,10 +0,0 @@
Folder-Description:
===================
* `briar-*`: Specifically for the Briar app (Phone/Desktop/Headless)
* `bramble-*`: The protocol stack - not necessarily Briar-dependent
---
* `*-api`: public stuff that can be referenced from other packages and modules - mostly interfaces + a few utility classes
* `*-core`: implementations of api that are portable across Android/Desktop/Headless
* `*-java`: implementations of api that are specific to Desktop & Headless

View File

@@ -15,8 +15,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 30 targetSdkVersion 30
versionCode 10411 versionCode 10408
versionName "1.4.11" versionName "1.4.8"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -18,7 +18,3 @@
-dontnote com.google.common.** -dontnote com.google.common.**
-dontwarn com.fasterxml.jackson.databind.ext.Java7SupportImpl -dontwarn com.fasterxml.jackson.databind.ext.Java7SupportImpl
# Keep all Jackson-serialisable classes and their members
-keep interface com.fasterxml.jackson.databind.annotation.JsonSerialize
-keep @com.fasterxml.jackson.databind.annotation.JsonSerialize class * { *; }

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble; package org.briarproject.bramble;
import org.briarproject.bramble.battery.AndroidBatteryModule; import org.briarproject.bramble.battery.AndroidBatteryModule;
import org.briarproject.bramble.io.DnsModule;
import org.briarproject.bramble.network.AndroidNetworkModule; import org.briarproject.bramble.network.AndroidNetworkModule;
import org.briarproject.bramble.plugin.tor.CircumventionModule; import org.briarproject.bramble.plugin.tor.CircumventionModule;
import org.briarproject.bramble.reporting.ReportingModule; import org.briarproject.bramble.reporting.ReportingModule;
@@ -19,7 +18,6 @@ import dagger.Module;
AndroidTaskSchedulerModule.class, AndroidTaskSchedulerModule.class,
AndroidWakefulIoExecutorModule.class, AndroidWakefulIoExecutorModule.class,
CircumventionModule.class, CircumventionModule.class,
DnsModule.class,
ReportingModule.class, ReportingModule.class,
SocksModule.class SocksModule.class
}) })

View File

@@ -5,7 +5,6 @@ targetCompatibility = 1.8
apply plugin: 'ru.vyarus.animalsniffer' apply plugin: 'ru.vyarus.animalsniffer'
apply plugin: 'witness' apply plugin: 'witness'
apply from: 'witness.gradle' apply from: 'witness.gradle'
apply plugin: 'checkstyle'
dependencies { dependencies {
implementation "com.google.dagger:dagger:$dagger_version" implementation "com.google.dagger:dagger:$dagger_version"

View File

@@ -7,7 +7,6 @@ import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
@NotNullByDefault @NotNullByDefault
public interface ConnectionManager { public interface ConnectionManager {
@@ -46,14 +45,6 @@ public interface ConnectionManager {
void manageOutgoingConnection(ContactId c, TransportId t, void manageOutgoingConnection(ContactId c, TransportId t,
TransportConnectionWriter w); TransportConnectionWriter w);
/**
* Manages an outgoing connection to a contact via a mailbox. The IDs of
* any messages sent or acked are added to the given
* {@link OutgoingSessionRecord}.
*/
void manageOutgoingConnection(ContactId c, TransportId t,
TransportConnectionWriter w, OutgoingSessionRecord sessionRecord);
/** /**
* Manages an outgoing connection to a contact over a duplex transport. * Manages an outgoing connection to a contact over a duplex transport.
*/ */

View File

@@ -126,11 +126,16 @@ public interface DatabaseComponent extends TransactionManager {
TransportKeys k) throws DbException; TransportKeys k) throws DbException;
/** /**
* Returns true if there are any acks to send to the given contact. * Returns true if there are any acks or messages to send to the given
* contact over a transport with the given maximum latency.
* <p/> * <p/>
* Read-only. * Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/ */
boolean containsAcksToSend(Transaction txn, ContactId c) throws DbException; boolean containsAnythingToSend(Transaction txn, ContactId c,
long maxLatency, boolean eager) 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
@@ -156,18 +161,6 @@ public interface DatabaseComponent extends TransactionManager {
*/ */
boolean containsIdentity(Transaction txn, AuthorId a) throws DbException; boolean containsIdentity(Transaction txn, AuthorId a) throws DbException;
/**
* Returns true if there are any messages to send to the given contact
* over a transport with the given maximum latency.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsMessagesToSend(Transaction txn, ContactId c,
long maxLatency, boolean eager) throws DbException;
/** /**
* Returns true if the database contains the given pending contact. * Returns true if the database contains the given pending contact.
* <p/> * <p/>
@@ -283,13 +276,6 @@ public interface DatabaseComponent extends TransactionManager {
*/ */
Group getGroup(Transaction txn, GroupId g) throws DbException; Group getGroup(Transaction txn, GroupId g) throws DbException;
/**
* Returns the ID of the group containing the given message.
* <p/>
* Read-only.
*/
GroupId getGroupId(Transaction txn, MessageId m) throws DbException;
/** /**
* Returns the metadata for the given group. * Returns the metadata for the given group.
* <p/> * <p/>
@@ -356,13 +342,13 @@ public interface DatabaseComponent extends TransactionManager {
Metadata query) throws DbException; Metadata query) throws DbException;
/** /**
* Returns the IDs of all messages received from the given contact that * Returns the IDs of some messages received from the given contact that
* need to be acknowledged. * need to be acknowledged, up to the given number of messages.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToAck(Transaction txn, ContactId c) Collection<MessageId> getMessagesToAck(Transaction txn, ContactId c,
throws DbException; int maxMessages) throws DbException;
/** /**
* 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
@@ -499,8 +485,6 @@ public interface DatabaseComponent extends TransactionManager {
* Returns the message with the given ID for transmission to the given * Returns the message with the given ID for transmission to the given
* contact over a transport with the given maximum latency. Returns null * contact over a transport with the given maximum latency. Returns null
* if the message is no longer visible to the contact. * if the message is no longer visible to the contact.
* <p/>
* Read-only if {@code markAsSent} is false.
* *
* @param markAsSent True if the message should be marked as sent. * @param markAsSent True if the message should be marked as sent.
* If false it can be marked as sent by calling * If false it can be marked as sent by calling
@@ -550,18 +534,15 @@ public interface DatabaseComponent extends TransactionManager {
*/ */
long getNextCleanupDeadline(Transaction txn) throws DbException; long getNextCleanupDeadline(Transaction txn) throws DbException;
/** /*
* Returns the next time (in milliseconds since the Unix epoch) when a * Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be sent to the given contact over a transport with * message is due to be sent to the given contact. The returned value may
* the given latency. * be zero if a message is due to be sent immediately, or Long.MAX_VALUE if
* <p> * no messages are scheduled to be sent.
* The returned value may be zero if a message is due to be sent
* immediately, or Long.MAX_VALUE if no messages are scheduled to be sent.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
long getNextSendTime(Transaction txn, ContactId c, long maxLatency) long getNextSendTime(Transaction txn, ContactId c) throws DbException;
throws DbException;
/** /**
* Returns the pending contact with the given ID. * Returns the pending contact with the given ID.

View File

@@ -37,14 +37,8 @@ public interface LifecycleManager {
*/ */
enum LifecycleState { enum LifecycleState {
CREATED, STARTING, MIGRATING_DATABASE, COMPACTING_DATABASE, STARTING_SERVICES,
STARTING, RUNNING, STOPPING;
MIGRATING_DATABASE,
COMPACTING_DATABASE,
STARTING_SERVICES,
RUNNING,
STOPPING,
STOPPED;
public boolean isAfter(LifecycleState state) { public boolean isAfter(LifecycleState state) {
return ordinal() > state.ordinal(); return ordinal() > state.ordinal();

View File

@@ -5,7 +5,6 @@ import org.briarproject.bramble.api.plugin.TransportId;
import java.util.List; import java.util.List;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.HOURS;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH; import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH; import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
@@ -66,8 +65,4 @@ public interface MailboxConstants {
*/ */
long PROBLEM_MS_SINCE_LAST_SUCCESS = HOURS.toMillis(1); long PROBLEM_MS_SINCE_LAST_SUCCESS = HOURS.toMillis(1);
/**
* The maximum latency of the mailbox transport in milliseconds.
*/
long MAX_LATENCY = DAYS.toMillis(14);
} }

View File

@@ -1,22 +1,19 @@
package org.briarproject.bramble.api.mailbox; package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List; import java.util.List;
import java.util.TreeSet; import java.util.TreeSet;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_CLIENT_TOO_OLD; import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_CLIENT_TOO_OLD;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_SERVER_TOO_OLD; import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_SERVER_TOO_OLD;
@NotNullByDefault class MailboxHelper {
public class MailboxHelper {
/** /**
* Returns the highest major version that both client and server support * Returns the highest major version that both client and server support
* or {@link MailboxConstants#API_SERVER_TOO_OLD} if the server is too old * or {@link MailboxConstants#API_SERVER_TOO_OLD} if the server is too old
* or {@link MailboxConstants#API_CLIENT_TOO_OLD} if the client is too old. * or {@link MailboxConstants#API_CLIENT_TOO_OLD} if the client is too old.
*/ */
public static int getHighestCommonMajorVersion( static int getHighestCommonMajorVersion(
List<MailboxVersion> client, List<MailboxVersion> server) { List<MailboxVersion> client, List<MailboxVersion> server) {
TreeSet<Integer> clientVersions = new TreeSet<>(); TreeSet<Integer> clientVersions = new TreeSet<>();
for (MailboxVersion version : client) { for (MailboxVersion version : client) {
@@ -35,13 +32,4 @@ public class MailboxHelper {
return API_SERVER_TOO_OLD; return API_SERVER_TOO_OLD;
} }
/**
* Returns true if a client and server with the given API versions can
* communicate with each other (ie, have any major API versions in common).
*/
public static boolean isClientCompatibleWithServer(
List<MailboxVersion> client, List<MailboxVersion> server) {
int common = getHighestCommonMajorVersion(client, server);
return common != API_CLIENT_TOO_OLD && common != API_SERVER_TOO_OLD;
}
} }

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.api.mailbox;
import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.JsonValue;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -33,7 +32,7 @@ public abstract class MailboxId extends UniqueId {
} }
try { try {
return fromHexString(token); return fromHexString(token);
} catch (FormatException e) { } catch (IllegalArgumentException e) {
throw new InvalidMailboxIdException(); throw new InvalidMailboxIdException();
} }
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.api.mailbox; package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NullSafety;
import java.util.List; import java.util.List;
@@ -12,7 +11,7 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault @NotNullByDefault
public class MailboxProperties { public class MailboxProperties {
private final String onion; private final String baseUrl;
private final MailboxAuthToken authToken; private final MailboxAuthToken authToken;
private final boolean owner; private final boolean owner;
private final List<MailboxVersion> serverSupports; private final List<MailboxVersion> serverSupports;
@@ -24,9 +23,9 @@ public class MailboxProperties {
/** /**
* Constructor for properties used by the mailbox's owner. * Constructor for properties used by the mailbox's owner.
*/ */
public MailboxProperties(String onion, MailboxAuthToken authToken, public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
List<MailboxVersion> serverSupports) { List<MailboxVersion> serverSupports) {
this.onion = onion; this.baseUrl = baseUrl;
this.authToken = authToken; this.authToken = authToken;
this.owner = true; this.owner = true;
this.serverSupports = serverSupports; this.serverSupports = serverSupports;
@@ -37,10 +36,10 @@ public class MailboxProperties {
/** /**
* Constructor for properties used by a contact of the mailbox's owner. * Constructor for properties used by a contact of the mailbox's owner.
*/ */
public MailboxProperties(String onion, MailboxAuthToken authToken, public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
List<MailboxVersion> serverSupports, MailboxFolderId inboxId, List<MailboxVersion> serverSupports, MailboxFolderId inboxId,
MailboxFolderId outboxId) { MailboxFolderId outboxId) {
this.onion = onion; this.baseUrl = baseUrl;
this.authToken = authToken; this.authToken = authToken;
this.owner = false; this.owner = false;
this.serverSupports = serverSupports; this.serverSupports = serverSupports;
@@ -48,11 +47,13 @@ public class MailboxProperties {
this.outboxId = outboxId; this.outboxId = outboxId;
} }
/** public String getBaseUrl() {
* Returns the onion address of the mailbox, excluding the .onion suffix. return baseUrl;
*/ }
public String getOnion() { public String getOnion() {
return onion; return baseUrl.replaceFirst("^http://", "")
.replaceFirst("\\.onion$", "");
} }
public MailboxAuthToken getAuthToken() { public MailboxAuthToken getAuthToken() {
@@ -76,23 +77,4 @@ public class MailboxProperties {
public MailboxFolderId getOutboxId() { public MailboxFolderId getOutboxId() {
return outboxId; return outboxId;
} }
@Override
public boolean equals(Object o) {
if (o instanceof MailboxProperties) {
MailboxProperties m = (MailboxProperties) o;
return owner == m.owner &&
onion.equals(m.onion) &&
authToken.equals(m.authToken) &&
NullSafety.equals(inboxId, m.inboxId) &&
NullSafety.equals(outboxId, m.outboxId) &&
serverSupports.equals(m.serverSupports);
}
return false;
}
@Override
public int hashCode() {
return authToken.hashCode();
}
} }

View File

@@ -32,8 +32,8 @@ public interface MailboxSettingsManager {
MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException; MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException;
void recordSuccessfulConnection(Transaction txn, long now, void recordSuccessfulConnection(Transaction txn, long now)
List<MailboxVersion> versions) throws DbException; throws DbException;
void recordFailedConnectionAttempt(Transaction txn, long now) void recordFailedConnectionAttempt(Transaction txn, long now)
throws DbException; throws DbException;
@@ -46,28 +46,20 @@ public interface MailboxSettingsManager {
interface MailboxHook { interface MailboxHook {
/** /**
* Called when Briar is paired with a mailbox. * Called when Briar is paired with a mailbox
* *
* @param txn A read-write transaction * @param txn A read-write transaction
* @param ownOnion Our new mailbox's onion (56 base32 chars)
*/ */
void mailboxPaired(Transaction txn, MailboxProperties p) void mailboxPaired(Transaction txn, String ownOnion,
List<MailboxVersion> serverSupports)
throws DbException; throws DbException;
/** /**
* Called when the mailbox is unpaired. * Called when the mailbox is unpaired
* *
* @param txn A read-write transaction * @param txn A read-write transaction
*/ */
void mailboxUnpaired(Transaction txn) throws DbException; void mailboxUnpaired(Transaction txn) throws DbException;
/**
* Called when we receive our mailbox's server-supported API versions.
* This happens whenever we successfully check the connectivity of
* our mailbox, so this hook may be called frequently.
*
* @param txn A read-write transaction
*/
void serverSupportedVersionsReceived(Transaction txn,
List<MailboxVersion> serverSupports) throws DbException;
} }
} }

View File

@@ -67,13 +67,6 @@ public class MailboxStatus {
return attemptsSinceSuccess; return attemptsSinceSuccess;
} }
/**
* Returns the mailbox's supported API versions.
*/
public List<MailboxVersion> getServerSupports() {
return serverSupports;
}
/** /**
* @return true if this status indicates a problem with the mailbox. * @return true if this status indicates a problem with the mailbox.
*/ */

View File

@@ -29,35 +29,19 @@ public interface MailboxUpdateManager {
/** /**
* The number of properties required for an update message with a mailbox. * The number of properties required for an update message with a mailbox.
* <p>
* The required properties are {@link #PROP_KEY_ONION},
* {@link #PROP_KEY_AUTHTOKEN}, {@link #PROP_KEY_INBOXID} and
* {@link #PROP_KEY_OUTBOXID}.
*/ */
int PROP_COUNT = 4; int PROP_COUNT = 4;
/** /**
* The onion address of the mailbox, excluding the .onion suffix. * The required properties of an update message with a mailbox.
*/ */
String PROP_KEY_ONION = "onion"; String PROP_KEY_ONION = "onion";
/**
* A bearer token for accessing the mailbox (64 hex digits).
*/
String PROP_KEY_AUTHTOKEN = "authToken"; String PROP_KEY_AUTHTOKEN = "authToken";
/**
* A folder ID for downloading messages (64 hex digits).
*/
String PROP_KEY_INBOXID = "inboxId"; String PROP_KEY_INBOXID = "inboxId";
/**
* A folder ID for uploading messages (64 hex digits).
*/
String PROP_KEY_OUTBOXID = "outboxId"; String PROP_KEY_OUTBOXID = "outboxId";
/** /**
* Length of the {@link #PROP_KEY_ONION} property. * Length of the Onion property.
*/ */
int PROP_ONION_LENGTH = 56; int PROP_ONION_LENGTH = 56;
@@ -79,32 +63,9 @@ public interface MailboxUpdateManager {
*/ */
String GROUP_KEY_SENT_CLIENT_SUPPORTS = "sentClientSupports"; String GROUP_KEY_SENT_CLIENT_SUPPORTS = "sentClientSupports";
/**
* Key in the client's local group for storing the serverSupports list that
* was last sent out, if any.
*/
String GROUP_KEY_SENT_SERVER_SUPPORTS = "sentServerSupports";
/**
* Returns the latest {@link MailboxUpdate} sent to the given contact.
* <p>
* If we have our own mailbox then the update will be a
* {@link MailboxUpdateWithMailbox} containing the
* {@link MailboxProperties} the contact should use for communicating with
* our mailbox.
*/
MailboxUpdate getLocalUpdate(Transaction txn, ContactId c) MailboxUpdate getLocalUpdate(Transaction txn, ContactId c)
throws DbException; throws DbException;
/**
* Returns the latest {@link MailboxUpdate} received from the given
* contact, or null if no update has been received.
* <p>
* If the contact has a mailbox then the update will be a
* {@link MailboxUpdateWithMailbox} containing the
* {@link MailboxProperties} we should use for communicating with the
* contact's mailbox.
*/
@Nullable @Nullable
MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c) MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
throws DbException; throws DbException;

View File

@@ -1,36 +0,0 @@
package org.briarproject.bramble.api.mailbox.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Map;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when a mailbox is paired.
*/
@Immutable
@NotNullByDefault
public class MailboxPairedEvent extends Event {
private final MailboxProperties properties;
private final Map<ContactId, MailboxUpdateWithMailbox> localUpdates;
public MailboxPairedEvent(MailboxProperties properties,
Map<ContactId, MailboxUpdateWithMailbox> localUpdates) {
this.properties = properties;
this.localUpdates = localUpdates;
}
public MailboxProperties getProperties() {
return properties;
}
public Map<ContactId, MailboxUpdateWithMailbox> getLocalUpdates() {
return localUpdates;
}
}

View File

@@ -1,28 +0,0 @@
package org.briarproject.bramble.api.mailbox.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Map;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when a mailbox is unpaired.
*/
@Immutable
@NotNullByDefault
public class MailboxUnpairedEvent extends Event {
private final Map<ContactId, MailboxUpdate> localUpdates;
public MailboxUnpairedEvent(Map<ContactId, MailboxUpdate> localUpdates) {
this.localUpdates = localUpdates;
}
public Map<ContactId, MailboxUpdate> getLocalUpdates() {
return localUpdates;
}
}

View File

@@ -1,40 +0,0 @@
package org.briarproject.bramble.api.mailbox.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when the first mailbox update is sent to a
* newly added contact, which happens in the same transaction in which the
* contact is added.
* <p>
* This event is not broadcast when the first mailbox update is sent to an
* existing contact when setting up the
* {@link MailboxUpdateManager mailbox update client}.
*/
@Immutable
@NotNullByDefault
public class MailboxUpdateSentToNewContactEvent extends Event {
private final ContactId contactId;
private final MailboxUpdate mailboxUpdate;
public MailboxUpdateSentToNewContactEvent(ContactId contactId,
MailboxUpdate mailboxUpdate) {
this.contactId = contactId;
this.mailboxUpdate = mailboxUpdate;
}
public ContactId getContactId() {
return contactId;
}
public MailboxUpdate getMailboxUpdate() {
return mailboxUpdate;
}
}

View File

@@ -0,0 +1,15 @@
package org.briarproject.bramble.api.sync;
import java.util.Collection;
/**
* An interface for holding the IDs of messages sent and acked during an
* outgoing {@link SyncSession} so they can be recorded in the DB as sent
* or acked at some later time.
*/
public interface DeferredSendHandler {
void onAckSent(Collection<MessageId> acked);
void onMessageSent(MessageId sent);
}

View File

@@ -1,37 +0,0 @@
package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.concurrent.ThreadSafe;
/**
* A container for holding the IDs of messages sent and acked during an
* outgoing {@link SyncSession}, so they can be recorded in the DB as sent
* or acked at some later time.
*/
@ThreadSafe
@NotNullByDefault
public class OutgoingSessionRecord {
private final Collection<MessageId> ackedIds = new CopyOnWriteArrayList<>();
private final Collection<MessageId> sentIds = new CopyOnWriteArrayList<>();
public void onAckSent(Collection<MessageId> acked) {
ackedIds.addAll(acked);
}
public void onMessageSent(MessageId sent) {
sentIds.add(sent);
}
public Collection<MessageId> getAckedIds() {
return ackedIds;
}
public Collection<MessageId> getSentIds() {
return sentIds;
}
}

View File

@@ -12,30 +12,12 @@ import javax.annotation.Nullable;
@NotNullByDefault @NotNullByDefault
public interface SyncSessionFactory { public interface SyncSessionFactory {
/**
* Creates a session for receiving data from a contact.
*/
SyncSession createIncomingSession(ContactId c, InputStream in, SyncSession createIncomingSession(ContactId c, InputStream in,
PriorityHandler handler); PriorityHandler handler);
/**
* Creates a session for sending data to a contact over a simplex transport.
*
* @param eager True if messages should be sent eagerly, ie regardless of
* whether they're due for retransmission.
*/
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t, SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
long maxLatency, boolean eager, StreamWriter streamWriter); long maxLatency, boolean eager, StreamWriter streamWriter);
/**
* Creates a session for sending data to a contact via a mailbox. The IDs
* of any messages sent or acked will be added to the given
* {@link OutgoingSessionRecord}.
*/
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
long maxLatency, StreamWriter streamWriter,
OutgoingSessionRecord sessionRecord);
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t, SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
long maxLatency, int maxIdleTime, StreamWriter streamWriter, long maxLatency, int maxIdleTime, StreamWriter streamWriter,
@Nullable Priority priority); @Nullable Priority priority);

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.api.sync.event;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group.Visibility;
import java.util.Collection; import java.util.Collection;
@@ -16,19 +15,12 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault @NotNullByDefault
public class GroupVisibilityUpdatedEvent extends Event { public class GroupVisibilityUpdatedEvent extends Event {
private final Visibility visibility;
private final Collection<ContactId> affected; private final Collection<ContactId> affected;
public GroupVisibilityUpdatedEvent(Visibility visibility, public GroupVisibilityUpdatedEvent(Collection<ContactId> affected) {
Collection<ContactId> affected) {
this.visibility = visibility;
this.affected = affected; this.affected = affected;
} }
public Visibility getVisibility() {
return visibility;
}
/** /**
* Returns the contacts affected by the update. * Returns the contacts affected by the update.
*/ */

View File

@@ -1,14 +1,9 @@
package org.briarproject.bramble.api.sync.event; package org.briarproject.bramble.api.sync.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group.Visibility;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import java.util.Map;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
@@ -19,32 +14,12 @@ import javax.annotation.concurrent.Immutable;
public class MessageSharedEvent extends Event { public class MessageSharedEvent extends Event {
private final MessageId messageId; private final MessageId messageId;
private final GroupId groupId;
private final Map<ContactId, Boolean> groupVisibility;
public MessageSharedEvent(MessageId message, GroupId groupId, public MessageSharedEvent(MessageId message) {
Map<ContactId, Boolean> groupVisibility) {
this.messageId = message; this.messageId = message;
this.groupId = groupId;
this.groupVisibility = groupVisibility;
} }
public MessageId getMessageId() { public MessageId getMessageId() {
return messageId; return messageId;
} }
public GroupId getGroupId() {
return groupId;
}
/**
* Returns the IDs of all contacts for which the visibility of the
* message's group is either {@link Visibility#SHARED shared} or
* {@link Visibility#VISIBLE visible}. The value in the map is true if the
* group is {@link Visibility#SHARED shared} or false if the group is
* {@link Visibility#VISIBLE visible}.
*/
public Map<ContactId, Boolean> getGroupVisibility() {
return groupVisibility;
}
} }

View File

@@ -40,7 +40,7 @@ public class IoUtils {
} }
} }
public static void delete(File f) { private static void delete(File f) {
if (!f.delete() && LOG.isLoggable(WARNING)) if (!f.delete() && LOG.isLoggable(WARNING))
LOG.warning("Could not delete " + f.getAbsolutePath()); LOG.warning("Could not delete " + f.getAbsolutePath());
} }

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.util; package org.briarproject.bramble.util;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@@ -96,10 +95,10 @@ public class StringUtils {
/** /**
* Converts the given hex string to a byte array. * Converts the given hex string to a byte array.
*/ */
public static byte[] fromHexString(String hex) throws FormatException { public static byte[] fromHexString(String hex) {
int len = hex.length(); int len = hex.length();
if (len % 2 != 0) if (len % 2 != 0)
throw new FormatException(); throw new IllegalArgumentException("Not a hex string");
byte[] bytes = new byte[len / 2]; byte[] bytes = new byte[len / 2];
for (int i = 0, j = 0; i < len; i += 2, j++) { for (int i = 0, j = 0; i < len; i += 2, j++) {
int high = hexDigitToInt(hex.charAt(i)); int high = hexDigitToInt(hex.charAt(i));
@@ -109,11 +108,11 @@ public class StringUtils {
return bytes; return bytes;
} }
private static int hexDigitToInt(char c) throws FormatException { private static int hexDigitToInt(char c) {
if (c >= '0' && c <= '9') return c - '0'; if (c >= '0' && c <= '9') return c - '0';
if (c >= 'A' && c <= 'F') return c - 'A' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'a' && c <= 'f') return c - 'a' + 10;
throw new FormatException(); throw new IllegalArgumentException("Not a hex digit: " + c);
} }
public static String trim(String s) { public static String trim(String s) {
@@ -131,13 +130,13 @@ public class StringUtils {
return MAC.matcher(mac).matches(); return MAC.matcher(mac).matches();
} }
public static byte[] macToBytes(String mac) throws FormatException { public static byte[] macToBytes(String mac) {
if (!MAC.matcher(mac).matches()) throw new FormatException(); if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException();
return fromHexString(mac.replaceAll(":", "")); return fromHexString(mac.replaceAll(":", ""));
} }
public static String macToString(byte[] mac) throws FormatException { public static String macToString(byte[] mac) {
if (mac.length != 6) throw new FormatException(); if (mac.length != 6) throw new IllegalArgumentException();
StringBuilder s = new StringBuilder(); StringBuilder s = new StringBuilder();
for (byte b : mac) { for (byte b : mac) {
if (s.length() > 0) s.append(':'); if (s.length() > 0) s.append(':');

View File

@@ -230,14 +230,14 @@ public class TestUtils {
public static MailboxProperties getMailboxProperties(boolean owner, public static MailboxProperties getMailboxProperties(boolean owner,
List<MailboxVersion> serverSupports) { List<MailboxVersion> serverSupports) {
String onion = getRandomString(56); String baseUrl = "http://" + getRandomString(56) + ".onion"; // TODO
MailboxAuthToken authToken = new MailboxAuthToken(getRandomId()); MailboxAuthToken authToken = new MailboxAuthToken(getRandomId());
if (owner) { if (owner) {
return new MailboxProperties(onion, authToken, serverSupports); return new MailboxProperties(baseUrl, authToken, serverSupports);
} }
MailboxFolderId inboxId = new MailboxFolderId(getRandomId()); MailboxFolderId inboxId = new MailboxFolderId(getRandomId());
MailboxFolderId outboxId = new MailboxFolderId(getRandomId()); MailboxFolderId outboxId = new MailboxFolderId(getRandomId());
return new MailboxProperties(onion, authToken, serverSupports, return new MailboxProperties(baseUrl, authToken, serverSupports,
inboxId, outboxId); inboxId, outboxId);
} }
@@ -338,17 +338,6 @@ public class TestUtils {
return false; return false;
} }
public static <E extends Event> E getEvent(Transaction txn,
Class<E> eventClass) {
for (CommitAction action : txn.getActions()) {
if (action instanceof EventAction) {
Event event = ((EventAction) action).getEvent();
if (eventClass.isInstance(event)) return eventClass.cast(event);
}
}
throw new AssertionError();
}
public static boolean isCryptoStrengthUnlimited() { public static boolean isCryptoStrengthUnlimited() {
try { try {
return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding") return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding")

View File

@@ -7,7 +7,6 @@ apply plugin: 'idea'
apply plugin: 'witness' apply plugin: 'witness'
apply from: 'witness.gradle' apply from: 'witness.gradle'
apply from: '../dagger.gradle' apply from: '../dagger.gradle'
apply plugin: 'checkstyle'
dependencies { dependencies {
implementation project(path: ':bramble-api', configuration: 'default') implementation project(path: ':bramble-api', configuration: 'default')
@@ -17,7 +16,7 @@ dependencies {
implementation 'org.bitlet:weupnp:0.1.4' implementation 'org.bitlet:weupnp:0.1.4'
implementation 'net.i2p.crypto:eddsa:0.2.0' implementation 'net.i2p.crypto:eddsa:0.2.0'
implementation 'org.whispersystems:curve25519-java:0.5.0' implementation 'org.whispersystems:curve25519-java:0.5.0'
implementation 'org.briarproject:jtorctl:0.5' implementation 'org.briarproject:jtorctl:0.4'
//noinspection GradleDependency //noinspection GradleDependency
implementation "com.squareup.okhttp3:okhttp:$okhttp_version" implementation "com.squareup.okhttp3:okhttp:$okhttp_version"

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.account; package org.briarproject.bramble.account;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.account.AccountManager; import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.DecryptionException; import org.briarproject.bramble.api.crypto.DecryptionException;
@@ -210,13 +209,7 @@ class AccountManagerImpl implements AccountManager {
LOG.warning("Failed to load encrypted database key"); LOG.warning("Failed to load encrypted database key");
throw new DecryptionException(INVALID_CIPHERTEXT); throw new DecryptionException(INVALID_CIPHERTEXT);
} }
byte[] ciphertext; byte[] ciphertext = fromHexString(hex);
try {
ciphertext = fromHexString(hex);
} catch (FormatException e) {
LOG.warning("Encrypted database key has invalid format");
throw new DecryptionException(INVALID_CIPHERTEXT);
}
KeyStrengthener keyStrengthener = databaseConfig.getKeyStrengthener(); KeyStrengthener keyStrengthener = databaseConfig.getKeyStrengthener();
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password, byte[] plaintext = crypto.decryptWithPassword(ciphertext, password,
keyStrengthener); keyStrengthener);

View File

@@ -52,7 +52,6 @@ import java.util.Map.Entry;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.sort;
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION; import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
@@ -457,7 +456,8 @@ class ClientHelperImpl implements ClientHelper {
checkLength(inboxId, UniqueId.LENGTH); checkLength(inboxId, UniqueId.LENGTH);
byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID); byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID);
checkLength(outboxId, UniqueId.LENGTH); checkLength(outboxId, UniqueId.LENGTH);
MailboxProperties props = new MailboxProperties(onion, String baseUrl = "http://" + onion + ".onion"; // TODO
MailboxProperties props = new MailboxProperties(baseUrl,
new MailboxAuthToken(authToken), serverSupportsList, new MailboxAuthToken(authToken), serverSupportsList,
new MailboxFolderId(inboxId), new MailboxFolderId(outboxId)); new MailboxFolderId(inboxId), new MailboxFolderId(outboxId));
return new MailboxUpdateWithMailbox(clientSupportsList, props); return new MailboxUpdateWithMailbox(clientSupportsList, props);
@@ -475,8 +475,6 @@ class ClientHelperImpl implements ClientHelper {
list.add(new MailboxVersion(element.getLong(0).intValue(), list.add(new MailboxVersion(element.getLong(0).intValue(),
element.getLong(1).intValue())); element.getLong(1).intValue()));
} }
// Sort the list of versions for easier comparison
sort(list);
return list; return list;
} }

View File

@@ -13,7 +13,6 @@ import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.SyncSessionFactory; import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamReaderFactory; import org.briarproject.bramble.api.transport.StreamReaderFactory;
@@ -101,16 +100,7 @@ class ConnectionManagerImpl implements ConnectionManager {
TransportConnectionWriter w) { TransportConnectionWriter w) {
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager, ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory, connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, c, t, w, null)); syncSessionFactory, transportPropertyManager, c, t, w));
}
@Override
public void manageOutgoingConnection(ContactId c, TransportId t,
TransportConnectionWriter w, OutgoingSessionRecord sessionRecord) {
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, c, t, w,
sessionRecord));
} }
@Override @Override

View File

@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.SyncSession; import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory; import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
@@ -17,8 +16,6 @@ import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException; import java.io.IOException;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@@ -29,8 +26,6 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
private final ContactId contactId; private final ContactId contactId;
private final TransportId transportId; private final TransportId transportId;
private final TransportConnectionWriter writer; private final TransportConnectionWriter writer;
@Nullable
private final OutgoingSessionRecord sessionRecord;
OutgoingSimplexSyncConnection(KeyManager keyManager, OutgoingSimplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry, ConnectionRegistry connectionRegistry,
@@ -39,15 +34,13 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
SyncSessionFactory syncSessionFactory, SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager, TransportPropertyManager transportPropertyManager,
ContactId contactId, TransportId transportId, ContactId contactId, TransportId transportId,
TransportConnectionWriter writer, TransportConnectionWriter writer) {
@Nullable OutgoingSessionRecord sessionRecord) {
super(keyManager, connectionRegistry, streamReaderFactory, super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory, streamWriterFactory, syncSessionFactory,
transportPropertyManager); transportPropertyManager);
this.contactId = contactId; this.contactId = contactId;
this.transportId = transportId; this.transportId = transportId;
this.writer = writer; this.writer = writer;
this.sessionRecord = sessionRecord;
} }
@Override @Override
@@ -78,16 +71,10 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter( StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx); w.getOutputStream(), ctx);
ContactId c = requireNonNull(ctx.getContactId()); ContactId c = requireNonNull(ctx.getContactId());
if (sessionRecord == null) { // Use eager retransmission if the transport is lossy and cheap
// Use eager retransmission if the transport is lossy and cheap return syncSessionFactory.createSimplexOutgoingSession(c,
return syncSessionFactory.createSimplexOutgoingSession(c, ctx.getTransportId(), w.getMaxLatency(), w.isLossyAndCheap(),
ctx.getTransportId(), w.getMaxLatency(), streamWriter);
w.isLossyAndCheap(), streamWriter);
} else {
return syncSessionFactory.createSimplexOutgoingSession(c,
ctx.getTransportId(), w.getMaxLatency(), streamWriter,
sessionRecord);
}
} }
} }

View File

@@ -163,11 +163,16 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns true if there are any acks to send to the given contact. * Returns true if there are any acks or messages to send to the given
* contact over a transport with the given maximum latency.
* <p/> * <p/>
* Read-only. * Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/ */
boolean containsAcksToSend(T txn, ContactId c) throws DbException; boolean containsAnythingToSend(T txn, ContactId c, long maxLatency,
boolean eager) 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
@@ -207,18 +212,6 @@ interface Database<T> {
*/ */
boolean containsMessage(T txn, MessageId m) throws DbException; boolean containsMessage(T txn, MessageId m) throws DbException;
/**
* Returns true if there are any messages to send to the given
* contact over a transport with the given maximum latency.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsMessagesToSend(T txn, ContactId c, long maxLatency,
boolean eager) throws DbException;
/** /**
* Returns true if the database contains the given pending contact. * Returns true if the database contains the given pending contact.
* <p/> * <p/>
@@ -320,13 +313,6 @@ interface Database<T> {
*/ */
Group getGroup(T txn, GroupId g) throws DbException; Group getGroup(T txn, GroupId g) throws DbException;
/**
* Returns the ID of the group containing the given message.
* <p/>
* Read-only.
*/
GroupId getGroupId(T txn, MessageId m) throws DbException;
/** /**
* Returns the metadata for the given group. * Returns the metadata for the given group.
* <p/> * <p/>
@@ -352,11 +338,8 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns the IDs of all contacts for which the given group's visibility * Returns the IDs of all contacts to which the given group's visibility is
* is either {@link Visibility#SHARED shared} or * either {@link Visibility VISIBLE} or {@link Visibility SHARED}.
* {@link Visibility#VISIBLE visible}. The value in the map is true if the
* group is {@link Visibility#SHARED shared} or false if the group is
* {@link Visibility#VISIBLE visible}.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -597,16 +580,13 @@ interface Database<T> {
/** /**
* Returns the next time (in milliseconds since the Unix epoch) when a * Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be sent to the given contact over a transport with * message is due to be sent to the given contact. The returned value may
* the given latency. * be zero if a message is due to be sent immediately, or Long.MAX_VALUE
* <p> * if no messages are scheduled to be sent.
* The returned value may be zero if a message is due to be sent
* immediately, or Long.MAX_VALUE if no messages are scheduled to be sent.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
long getNextSendTime(T txn, ContactId c, long maxLatency) long getNextSendTime(T txn, ContactId c) throws DbException;
throws DbException;
/** /**
* Returns the pending contact with the given ID. * Returns the pending contact with the given ID.

View File

@@ -287,12 +287,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new MessageAddedEvent(m, null)); transaction.attach(new MessageAddedEvent(m, null));
transaction.attach(new MessageStateChangedEvent(m.getId(), true, transaction.attach(new MessageStateChangedEvent(m.getId(), true,
DELIVERED)); DELIVERED));
if (shared) { if (shared) transaction.attach(new MessageSharedEvent(m.getId()));
Map<ContactId, Boolean> visibility =
db.getGroupVisibility(txn, m.getGroupId());
transaction.attach(new MessageSharedEvent(m.getId(),
m.getGroupId(), visibility));
}
} }
db.mergeMessageMetadata(txn, m.getId(), meta); db.mergeMessageMetadata(txn, m.getId(), meta);
} }
@@ -347,12 +342,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public boolean containsAcksToSend(Transaction transaction, ContactId c) public boolean containsAnythingToSend(Transaction transaction, ContactId c,
throws DbException { long maxLatency, boolean eager) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
return db.containsAcksToSend(txn, c); return db.containsAnythingToSend(txn, c, maxLatency, eager);
} }
@Override @Override
@@ -378,15 +373,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.containsIdentity(txn, a); return db.containsIdentity(txn, a);
} }
@Override
public boolean containsMessagesToSend(Transaction transaction, ContactId c,
long maxLatency, boolean eager) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.containsMessagesToSend(txn, c, maxLatency, eager);
}
@Override @Override
public boolean containsPendingContact(Transaction transaction, public boolean containsPendingContact(Transaction transaction,
PendingContactId p) throws DbException { PendingContactId p) throws DbException {
@@ -555,15 +541,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getGroup(txn, g); return db.getGroup(txn, g);
} }
@Override
public GroupId getGroupId(Transaction transaction, MessageId m)
throws DbException {
T txn = unbox(transaction);
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
return db.getGroupId(txn, m);
}
@Override @Override
public Metadata getGroupMetadata(Transaction transaction, GroupId g) public Metadata getGroupMetadata(Transaction transaction, GroupId g)
throws DbException { throws DbException {
@@ -634,11 +611,11 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Override @Override
public Collection<MessageId> getMessagesToAck(Transaction transaction, public Collection<MessageId> getMessagesToAck(Transaction transaction,
ContactId c) throws DbException { ContactId c, int maxMessages) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
return db.getMessagesToAck(txn, c, Integer.MAX_VALUE); return db.getMessagesToAck(txn, c, maxMessages);
} }
@Override @Override
@@ -760,9 +737,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
public Message getMessageToSend(Transaction transaction, ContactId c, public Message getMessageToSend(Transaction transaction, ContactId c,
MessageId m, long maxLatency, boolean markAsSent) MessageId m, long maxLatency, boolean markAsSent)
throws DbException { throws DbException {
if (markAsSent && transaction.isReadOnly()) { if (transaction.isReadOnly()) throw new IllegalArgumentException();
throw new IllegalArgumentException();
}
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
@@ -830,10 +805,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public long getNextSendTime(Transaction transaction, ContactId c, public long getNextSendTime(Transaction transaction, ContactId c)
long maxLatency) throws DbException { throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.getNextSendTime(txn, c, maxLatency); return db.getNextSendTime(txn, c);
} }
@Override @Override
@@ -1041,8 +1016,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.getGroupVisibility(txn, id).keySet(); db.getGroupVisibility(txn, id).keySet();
db.removeGroup(txn, id); db.removeGroup(txn, id);
transaction.attach(new GroupRemovedEvent(g)); transaction.attach(new GroupRemovedEvent(g));
transaction.attach(new GroupVisibilityUpdatedEvent(INVISIBLE, transaction.attach(new GroupVisibilityUpdatedEvent(affected));
affected));
} }
@Override @Override
@@ -1113,7 +1087,11 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
db.lowerAckFlag(txn, c, acked); List<MessageId> visible = new ArrayList<>(acked.size());
for (MessageId m : acked) {
if (db.containsVisibleMessage(txn, c, m)) visible.add(m);
}
db.lowerAckFlag(txn, c, visible);
} }
@Override @Override
@@ -1163,7 +1141,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g); else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
else db.setGroupVisibility(txn, c, g, v == SHARED); else db.setGroupVisibility(txn, c, g, v == SHARED);
List<ContactId> affected = singletonList(c); List<ContactId> affected = singletonList(c);
transaction.attach(new GroupVisibilityUpdatedEvent(v, affected)); transaction.attach(new GroupVisibilityUpdatedEvent(affected));
} }
@Override @Override
@@ -1196,9 +1174,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (db.getMessageState(txn, m) != DELIVERED) if (db.getMessageState(txn, m) != DELIVERED)
throw new IllegalArgumentException("Shared undelivered message"); throw new IllegalArgumentException("Shared undelivered message");
db.setMessageShared(txn, m, true); db.setMessageShared(txn, m, true);
GroupId g = db.getGroupId(txn, m); transaction.attach(new MessageSharedEvent(m));
Map<ContactId, Boolean> visibility = db.getGroupVisibility(txn, g);
transaction.attach(new MessageSharedEvent(m, g, visibility));
} }
@Override @Override

View File

@@ -3,15 +3,16 @@ package org.briarproject.bramble.db;
class DatabaseTypes { class DatabaseTypes {
private final String hashType, secretType, binaryType; private final String hashType, secretType, binaryType;
private final String counterType, stringType; private final String counterType, stringType, schemaQuery;
public DatabaseTypes(String hashType, String secretType, String binaryType, DatabaseTypes(String hashType, String secretType, String binaryType,
String counterType, String stringType) { String counterType, String stringType, String schemaQuery) {
this.hashType = hashType; this.hashType = hashType;
this.secretType = secretType; this.secretType = secretType;
this.binaryType = binaryType; this.binaryType = binaryType;
this.counterType = counterType; this.counterType = counterType;
this.stringType = stringType; this.stringType = stringType;
this.schemaQuery = schemaQuery;
} }
/** /**
@@ -31,4 +32,8 @@ class DatabaseTypes {
s = s.replaceAll("_STRING", stringType); s = s.replaceAll("_STRING", stringType);
return s; return s;
} }
String getSchemaQuery() {
return schemaQuery;
}
} }

View File

@@ -41,8 +41,9 @@ class H2Database extends JdbcDatabase {
private static final String BINARY_TYPE = "BINARY"; private static final String BINARY_TYPE = "BINARY";
private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT"; private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT";
private static final String STRING_TYPE = "VARCHAR"; private static final String STRING_TYPE = "VARCHAR";
private static final String SCHEMA_QUERY = "SHOW TABLES";
private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE, private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE,
SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE); SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE, SCHEMA_QUERY);
private final DatabaseConfig config; private final DatabaseConfig config;
private final String url; private final String url;

View File

@@ -41,8 +41,11 @@ class HyperSqlDatabase extends JdbcDatabase {
private static final String COUNTER_TYPE = private static final String COUNTER_TYPE =
"INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY(START WITH 1)"; "INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY(START WITH 1)";
private static final String STRING_TYPE = "VARCHAR"; private static final String STRING_TYPE = "VARCHAR";
private static final String SCHEMA_QUERY =
"SELECT TABLE_NAME FROM INFORMATION_SCHEMA.SYSTEM_TABLES"
+ " WHERE TABLE_TYPE='TABLE'";
private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE, private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE,
SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE); SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE, SCHEMA_QUERY);
private final DatabaseConfig config; private final DatabaseConfig config;
private final String url; private final String url;

View File

@@ -405,6 +405,7 @@ abstract class JdbcDatabase implements Database<Connection> {
boolean compact; boolean compact;
Connection txn = startTransaction(); Connection txn = startTransaction();
try { try {
if (LOG.isLoggable(INFO)) logSchema(txn);
if (reopen) { if (reopen) {
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE); Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
wasDirtyOnInitialisation = isDirty(s); wasDirtyOnInitialisation = isDirty(s);
@@ -447,6 +448,24 @@ abstract class JdbcDatabase implements Database<Connection> {
return wasDirtyOnInitialisation; return wasDirtyOnInitialisation;
} }
private void logSchema(Connection txn) throws DbException {
Statement s = null;
ResultSet rs = null;
try {
s = txn.createStatement();
rs = s.executeQuery(dbTypes.getSchemaQuery());
StringBuilder sb = new StringBuilder("Tables:");
while (rs.next()) sb.append('\n').append(rs.getString(1));
LOG.info(sb.toString());
rs.close();
s.close();
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
/** /**
* Compares the schema version stored in the database with the schema * Compares the schema version stored in the database with the schema
* version used by the current code and applies any suitable migrations to * version used by the current code and applies any suitable migrations to
@@ -1147,8 +1166,8 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public boolean containsAcksToSend(Connection txn, ContactId c) public boolean containsAnythingToSend(Connection txn, ContactId c,
throws DbException { long maxLatency, boolean eager) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
@@ -1160,7 +1179,34 @@ abstract class JdbcDatabase implements Database<Connection> {
boolean acksToSend = rs.next(); boolean acksToSend = rs.next();
rs.close(); rs.close();
ps.close(); ps.close();
return acksToSend; if (acksToSend) return true;
if (eager) {
sql = "SELECT NULL from statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
} else {
long now = clock.currentTimeMillis();
sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
+ " AND (expiry <= ? OR maxLatency IS NULL"
+ " OR ? < maxLatency)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now);
ps.setLong(4, maxLatency);
}
rs = ps.executeQuery();
boolean messagesToSend = rs.next();
rs.close();
ps.close();
return messagesToSend;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs, LOG, WARNING); tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING); tryToClose(ps, LOG, WARNING);
@@ -1280,46 +1326,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public boolean containsMessagesToSend(Connection txn, ContactId c,
long maxLatency, boolean eager) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
if (eager) {
String sql = "SELECT NULL from statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
} else {
long now = clock.currentTimeMillis();
String sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
+ " AND (expiry <= ? OR maxLatency IS NULL"
+ " OR ? < maxLatency)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now);
ps.setLong(4, maxLatency);
}
rs = ps.executeQuery();
boolean messagesToSend = rs.next();
rs.close();
ps.close();
return messagesToSend;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public boolean containsPendingContact(Connection txn, PendingContactId p) public boolean containsPendingContact(Connection txn, PendingContactId p)
throws DbException { throws DbException {
@@ -1683,27 +1689,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public GroupId getGroupId(Connection txn, MessageId m) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT groupId FROM messages WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException();
GroupId g = new GroupId(rs.getBytes(1));
rs.close();
ps.close();
return g;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public Collection<Group> getGroups(Connection txn, ClientId c, public Collection<Group> getGroups(Connection txn, ClientId c,
int majorVersion) throws DbException { int majorVersion) throws DbException {
@@ -2511,28 +2496,12 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public long getNextSendTime(Connection txn, ContactId c, long maxLatency) public long getNextSendTime(Connection txn, ContactId c)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
// Are any messages sendable immediately? String sql = "SELECT expiry FROM statuses"
String sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
+ " AND (maxLatency IS NULL OR ? < maxLatency)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, maxLatency);
rs = ps.executeQuery();
boolean found = rs.next();
rs.close();
ps.close();
if (found) return 0;
// When is the earliest expiry time (could be in the past)?
sql = "SELECT expiry FROM statuses"
+ " WHERE contactId = ? AND state = ?" + " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE" + " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE" + " AND deleted = FALSE AND seen = FALSE"

View File

@@ -18,7 +18,7 @@ import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.Semaphore;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
@@ -29,12 +29,10 @@ import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.CREATED;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPED;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
@@ -62,11 +60,12 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
private final List<Service> services; private final List<Service> services;
private final List<OpenDatabaseHook> openDatabaseHooks; private final List<OpenDatabaseHook> openDatabaseHooks;
private final List<ExecutorService> executors; private final List<ExecutorService> executors;
private final Semaphore startStopSemaphore = new Semaphore(1);
private final CountDownLatch dbLatch = new CountDownLatch(1); private final CountDownLatch dbLatch = new CountDownLatch(1);
private final CountDownLatch startupLatch = new CountDownLatch(1); private final CountDownLatch startupLatch = new CountDownLatch(1);
private final CountDownLatch shutdownLatch = new CountDownLatch(1); private final CountDownLatch shutdownLatch = new CountDownLatch(1);
private final AtomicReference<LifecycleState> state =
new AtomicReference<>(CREATED); private volatile LifecycleState state = STARTING;
@Inject @Inject
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus, LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
@@ -103,8 +102,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
@Override @Override
public StartResult startServices(SecretKey dbKey) { public StartResult startServices(SecretKey dbKey) {
if (!state.compareAndSet(CREATED, STARTING)) { if (!startStopSemaphore.tryAcquire()) {
LOG.warning("Already running"); LOG.info("Already starting or stopping");
return ALREADY_RUNNING; return ALREADY_RUNNING;
} }
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
@@ -136,7 +135,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
}); });
LOG.info("Starting services"); LOG.info("Starting services");
state.set(STARTING_SERVICES); state = STARTING_SERVICES;
dbLatch.countDown(); dbLatch.countDown();
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES)); eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
@@ -149,7 +148,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
} }
} }
state.set(RUNNING); state = RUNNING;
startupLatch.countDown(); startupLatch.countDown();
eventBus.broadcast(new LifecycleEvent(RUNNING)); eventBus.broadcast(new LifecycleEvent(RUNNING));
return SUCCESS; return SUCCESS;
@@ -165,58 +164,63 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
} catch (ServiceException e) { } catch (ServiceException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
return SERVICE_ERROR; return SERVICE_ERROR;
} finally {
startStopSemaphore.release();
} }
} }
@Override @Override
public void onDatabaseMigration() { public void onDatabaseMigration() {
state.set(MIGRATING_DATABASE); state = MIGRATING_DATABASE;
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE)); eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
} }
@Override @Override
public void onDatabaseCompaction() { public void onDatabaseCompaction() {
state.set(COMPACTING_DATABASE); state = COMPACTING_DATABASE;
eventBus.broadcast(new LifecycleEvent(COMPACTING_DATABASE)); eventBus.broadcast(new LifecycleEvent(COMPACTING_DATABASE));
} }
@Override @Override
public void stopServices() { public void stopServices() {
if (!state.compareAndSet(RUNNING, STOPPING)) { try {
LOG.warning("Not running"); startStopSemaphore.acquire();
} catch (InterruptedException e) {
LOG.warning("Interrupted while waiting to stop services");
return; return;
} }
LOG.info("Stopping services"); try {
eventBus.broadcast(new LifecycleEvent(STOPPING)); if (state == STOPPING) {
for (Service s : services) { LOG.info("Already stopped");
try { return;
}
LOG.info("Stopping services");
state = STOPPING;
eventBus.broadcast(new LifecycleEvent(STOPPING));
for (Service s : services) {
long start = now(); long start = now();
s.stopService(); s.stopService();
if (LOG.isLoggable(FINE)) { if (LOG.isLoggable(FINE)) {
logDuration(LOG, "Stopping service " logDuration(LOG, "Stopping service "
+ s.getClass().getSimpleName(), start); + s.getClass().getSimpleName(), start);
} }
} catch (ServiceException e) {
logException(LOG, WARNING, e);
} }
} for (ExecutorService e : executors) {
for (ExecutorService e : executors) { if (LOG.isLoggable(FINE)) {
if (LOG.isLoggable(FINE)) { LOG.fine("Stopping executor "
LOG.fine("Stopping executor " + e.getClass().getSimpleName());
+ e.getClass().getSimpleName()); }
e.shutdownNow();
} }
e.shutdownNow();
}
try {
long start = now(); long start = now();
db.close(); db.close();
logDuration(LOG, "Closing database", start); logDuration(LOG, "Closing database", start);
} catch (DbException e) { shutdownLatch.countDown();
} catch (DbException | ServiceException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} finally {
startStopSemaphore.release();
} }
state.set(STOPPED);
shutdownLatch.countDown();
eventBus.broadcast(new LifecycleEvent(STOPPED));
} }
@Override @Override
@@ -236,6 +240,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
@Override @Override
public LifecycleState getLifecycleState() { public LifecycleState getLifecycleState() {
return state.get(); return state;
} }
} }

View File

@@ -22,21 +22,10 @@ interface ConnectivityChecker {
* the check succeeds. If a check is already running then the observer is * the check succeeds. If a check is already running then the observer is
* called when the check succeeds. If a connectivity check has recently * called when the check succeeds. If a connectivity check has recently
* succeeded then the observer is called immediately. * succeeded then the observer is called immediately.
* <p>
* Observers are removed after being called, or when the checker is
* {@link #destroy() destroyed}.
*/ */
void checkConnectivity(MailboxProperties properties, void checkConnectivity(MailboxProperties properties,
ConnectivityObserver o); ConnectivityObserver o);
/**
* Removes an observer that was added via
* {@link #checkConnectivity(MailboxProperties, ConnectivityObserver)}. If
* there are no remaining observers and a connectivity check is running
* then the check will be cancelled.
*/
void removeObserver(ConnectivityObserver o);
interface ConnectivityObserver { interface ConnectivityObserver {
void onConnectivityCheckSucceeded(); void onConnectivityCheckSucceeded();
} }

View File

@@ -80,7 +80,8 @@ abstract class ConnectivityCheckerImpl implements ConnectivityChecker {
> CONNECTIVITY_CHECK_FRESHNESS_MS) { > CONNECTIVITY_CHECK_FRESHNESS_MS) {
// The last connectivity check is stale, start a new one // The last connectivity check is stale, start a new one
connectivityObservers.add(o); connectivityObservers.add(o);
ApiCall task = createConnectivityCheckTask(properties); ApiCall task =
createConnectivityCheckTask(properties);
connectivityCheck = mailboxApiCaller.retryWithBackoff(task); connectivityCheck = mailboxApiCaller.retryWithBackoff(task);
} else { } else {
// The last connectivity check is fresh // The last connectivity check is fresh
@@ -107,16 +108,4 @@ abstract class ConnectivityCheckerImpl implements ConnectivityChecker {
o.onConnectivityCheckSucceeded(); o.onConnectivityCheckSucceeded();
} }
} }
@Override
public void removeObserver(ConnectivityObserver o) {
synchronized (lock) {
if (destroyed) return;
connectivityObservers.remove(o);
if (connectivityObservers.isEmpty() && connectivityCheck != null) {
connectivityCheck.cancel();
connectivityCheck = null;
}
}
}
} }

View File

@@ -1,124 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Logger.getLogger;
@ThreadSafe
@NotNullByDefault
class ContactMailboxClient implements MailboxClient {
private static final Logger LOG =
getLogger(ContactMailboxClient.class.getName());
private final MailboxWorkerFactory workerFactory;
private final ConnectivityChecker connectivityChecker;
private final TorReachabilityMonitor reachabilityMonitor;
private final Object lock = new Object();
@GuardedBy("lock")
@Nullable
private MailboxWorker uploadWorker = null, downloadWorker = null;
ContactMailboxClient(MailboxWorkerFactory workerFactory,
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor) {
this.workerFactory = workerFactory;
this.connectivityChecker = connectivityChecker;
this.reachabilityMonitor = reachabilityMonitor;
}
@Override
public void start() {
LOG.info("Started");
// Nothing to do until contact is assigned
}
@Override
public void destroy() {
LOG.info("Destroyed");
MailboxWorker uploadWorker, downloadWorker;
synchronized (lock) {
uploadWorker = this.uploadWorker;
this.uploadWorker = null;
downloadWorker = this.downloadWorker;
this.downloadWorker = null;
}
if (uploadWorker != null) uploadWorker.destroy();
if (downloadWorker != null) downloadWorker.destroy();
// The connectivity checker belongs to the client, so it should be
// destroyed. The Tor reachability monitor is shared between clients,
// so it should not be destroyed
connectivityChecker.destroy();
}
@Override
public void assignContactForUpload(ContactId contactId,
MailboxProperties properties, MailboxFolderId folderId) {
LOG.info("Contact assigned for upload");
if (properties.isOwner()) throw new IllegalArgumentException();
// For a contact's mailbox we should always be uploading to the outbox
// assigned to us by the contact
if (!folderId.equals(properties.getOutboxId())) {
throw new IllegalArgumentException();
}
MailboxWorker uploadWorker = workerFactory.createUploadWorker(
connectivityChecker, properties, folderId, contactId);
synchronized (lock) {
if (this.uploadWorker != null) throw new IllegalStateException();
this.uploadWorker = uploadWorker;
}
uploadWorker.start();
}
@Override
public void deassignContactForUpload(ContactId contactId) {
LOG.info("Contact deassigned for upload");
MailboxWorker uploadWorker;
synchronized (lock) {
uploadWorker = this.uploadWorker;
this.uploadWorker = null;
}
if (uploadWorker != null) uploadWorker.destroy();
}
@Override
public void assignContactForDownload(ContactId contactId,
MailboxProperties properties, MailboxFolderId folderId) {
LOG.info("Contact assigned for download");
if (properties.isOwner()) throw new IllegalArgumentException();
// For a contact's mailbox we should always be downloading from the
// inbox assigned to us by the contact
if (!folderId.equals(properties.getInboxId())) {
throw new IllegalArgumentException();
}
MailboxWorker downloadWorker =
workerFactory.createDownloadWorkerForContactMailbox(
connectivityChecker, reachabilityMonitor, properties);
synchronized (lock) {
if (this.downloadWorker != null) throw new IllegalStateException();
this.downloadWorker = downloadWorker;
}
downloadWorker.start();
}
@Override
public void deassignContactForDownload(ContactId contactId) {
LOG.info("Contact deassigned for download");
MailboxWorker downloadWorker;
synchronized (lock) {
downloadWorker = this.downloadWorker;
this.downloadWorker = null;
}
if (downloadWorker != null) downloadWorker.destroy();
}
}

View File

@@ -5,23 +5,16 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException; import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import java.util.logging.Logger; import java.io.IOException;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.logging.Logger.getLogger;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl { class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
private static final Logger LOG =
getLogger(ContactMailboxConnectivityChecker.class.getName());
private final MailboxApi mailboxApi; private final MailboxApi mailboxApi;
@Inject
ContactMailboxConnectivityChecker(Clock clock, ContactMailboxConnectivityChecker(Clock clock,
MailboxApiCaller mailboxApiCaller, MailboxApi mailboxApi) { MailboxApiCaller mailboxApiCaller, MailboxApi mailboxApi) {
super(clock, mailboxApiCaller); super(clock, mailboxApiCaller);
@@ -31,13 +24,16 @@ class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
@Override @Override
ApiCall createConnectivityCheckTask(MailboxProperties properties) { ApiCall createConnectivityCheckTask(MailboxProperties properties) {
if (properties.isOwner()) throw new IllegalArgumentException(); if (properties.isOwner()) throw new IllegalArgumentException();
return new SimpleApiCall(() -> { return new SimpleApiCall() {
LOG.info("Checking connectivity of contact's mailbox"); @Override
if (!mailboxApi.checkStatus(properties)) throw new ApiException(); void tryToCallApi() throws IOException, ApiException {
LOG.info("Contact's mailbox is reachable"); if (!mailboxApi.checkStatus(properties)) {
// Call the observers and cache the result throw new ApiException();
onConnectivityCheckSucceeded(clock.currentTimeMillis()); }
}); // Call the observers and cache the result
onConnectivityCheckSucceeded(clock.currentTimeMillis());
}
};
} }
} }

View File

@@ -1,65 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
@ThreadSafe
@NotNullByDefault
class ContactMailboxDownloadWorker extends MailboxDownloadWorker {
ContactMailboxDownloadWorker(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor torReachabilityMonitor,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager,
MailboxProperties mailboxProperties) {
super(connectivityChecker, torReachabilityMonitor, mailboxApiCaller,
mailboxApi, mailboxFileManager, mailboxProperties);
if (mailboxProperties.isOwner()) throw new IllegalArgumentException();
}
@Override
protected ApiCall createApiCallForDownloadCycle() {
return new SimpleApiCall(this::apiCallListInbox);
}
private void apiCallListInbox() throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
LOG.info("Listing inbox");
MailboxFolderId folderId =
requireNonNull(mailboxProperties.getInboxId());
List<MailboxFile> files;
try {
files = mailboxApi.getFiles(mailboxProperties, folderId);
} catch (TolerableFailureException e) {
LOG.warning("Inbox folder does not exist");
files = emptyList();
}
if (files.isEmpty()) {
onDownloadCycleFinished();
} else {
Queue<FolderFile> queue = new LinkedList<>();
for (MailboxFile file : files) {
queue.add(new FolderFile(folderId, file.name));
}
downloadNextFile(queue);
}
}
}

View File

@@ -91,13 +91,9 @@ interface MailboxApi {
* Used by owner and contacts to list their files to retrieve. * Used by owner and contacts to list their files to retrieve.
* <p> * <p>
* Returns 200 OK with the list of files in JSON. * Returns 200 OK with the list of files in JSON.
*
* @throws TolerableFailureException if response code is 404 (folder does
* not exist or client is not authorised to download from it)
*/ */
List<MailboxFile> getFiles(MailboxProperties properties, List<MailboxFile> getFiles(MailboxProperties properties,
MailboxFolderId folderId) MailboxFolderId folderId) throws IOException, ApiException;
throws IOException, ApiException, TolerableFailureException;
/** /**
* Used by owner and contacts to retrieve a file. * Used by owner and contacts to retrieve a file.
@@ -106,22 +102,17 @@ interface MailboxApi {
* in the response body. * in the response body.
* *
* @param file the empty file the response bytes will be written into. * @param file the empty file the response bytes will be written into.
* @throws TolerableFailureException if response code is 404 (folder does
* not exist, client is not authorised to download from folder, or file
* does not exist)
*/ */
void getFile(MailboxProperties properties, MailboxFolderId folderId, void getFile(MailboxProperties properties, MailboxFolderId folderId,
MailboxFileId fileId, File file) MailboxFileId fileId, File file) throws IOException, ApiException;
throws IOException, ApiException, TolerableFailureException;
/** /**
* Used by owner and contacts to delete files. * Used by owner and contacts to delete files.
* <p> * <p>
* Returns 200 OK (no exception) if deletion was successful. * Returns 200 OK (no exception) if deletion was successful.
* *
* @throws TolerableFailureException if response code is 404 (folder does * @throws TolerableFailureException on 404 response,
* not exist, client is not authorised to download from folder, or file * because file was most likely deleted already.
* does not exist)
*/ */
void deleteFile(MailboxProperties properties, MailboxFolderId folderId, void deleteFile(MailboxProperties properties, MailboxFolderId folderId,
MailboxFileId fileId) MailboxFileId fileId)

View File

@@ -24,8 +24,6 @@ interface MailboxApiCaller {
* Asynchronously calls the given API call on the {@link IoExecutor}, * Asynchronously calls the given API call on the {@link IoExecutor},
* automatically retrying at increasing intervals until the API call * automatically retrying at increasing intervals until the API call
* returns false or retries are cancelled. * returns false or retries are cancelled.
* <p>
* This method is safe to call while holding a lock.
* *
* @return A {@link Cancellable} that can be used to cancel any future * @return A {@link Cancellable} that can be used to cancel any future
* retries. * retries.

View File

@@ -22,6 +22,7 @@ import java.io.FileOutputStream;
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.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
@@ -34,7 +35,6 @@ import okhttp3.Response;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
import static com.fasterxml.jackson.databind.MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES; import static com.fasterxml.jackson.databind.MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES;
import static java.util.Collections.sort;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static okhttp3.internal.Util.EMPTY_REQUEST; import static okhttp3.internal.Util.EMPTY_REQUEST;
import static org.briarproject.bramble.util.IoUtils.copyAndClose; import static org.briarproject.bramble.util.IoUtils.copyAndClose;
@@ -42,22 +42,18 @@ import static org.briarproject.bramble.util.IoUtils.copyAndClose;
@NotNullByDefault @NotNullByDefault
class MailboxApiImpl implements MailboxApi { class MailboxApiImpl implements MailboxApi {
private final WeakSingletonProvider<OkHttpClient> httpClientProvider;
private final JsonMapper mapper = JsonMapper.builder()
.enable(BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES)
.build();
private static final MediaType JSON = private static final MediaType JSON =
requireNonNull(MediaType.parse("application/json; charset=utf-8")); requireNonNull(MediaType.parse("application/json; charset=utf-8"));
private static final MediaType FILE = private static final MediaType FILE =
requireNonNull(MediaType.parse("application/octet-stream")); requireNonNull(MediaType.parse("application/octet-stream"));
private final WeakSingletonProvider<OkHttpClient> httpClientProvider;
private final JsonMapper mapper = JsonMapper.builder()
.enable(BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES)
.build();
private final UrlConverter urlConverter;
@Inject @Inject
MailboxApiImpl(WeakSingletonProvider<OkHttpClient> httpClientProvider, MailboxApiImpl(WeakSingletonProvider<OkHttpClient> httpClientProvider) {
UrlConverter urlConverter) {
this.httpClientProvider = httpClientProvider; this.httpClientProvider = httpClientProvider;
this.urlConverter = urlConverter;
} }
@Override @Override
@@ -82,7 +78,7 @@ class MailboxApiImpl implements MailboxApi {
throws IOException, ApiException { throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException(); if (!properties.isOwner()) throw new IllegalArgumentException();
Request request = getRequestBuilder(properties.getAuthToken()) Request request = getRequestBuilder(properties.getAuthToken())
.url(getBaseUrl(properties) + "/setup") .url(properties.getBaseUrl() + "/setup")
.put(EMPTY_REQUEST) .put(EMPTY_REQUEST)
.build(); .build();
OkHttpClient client = httpClientProvider.get(); OkHttpClient client = httpClientProvider.get();
@@ -97,7 +93,7 @@ class MailboxApiImpl implements MailboxApi {
if (tokenNode == null) { if (tokenNode == null) {
throw new ApiException(); throw new ApiException();
} }
return new MailboxProperties(properties.getOnion(), return new MailboxProperties(properties.getBaseUrl(),
MailboxAuthToken.fromString(tokenNode.textValue()), MailboxAuthToken.fromString(tokenNode.textValue()),
parseServerSupports(node)); parseServerSupports(node));
} catch (JacksonException | InvalidMailboxIdException e) { } catch (JacksonException | InvalidMailboxIdException e) {
@@ -125,8 +121,6 @@ class MailboxApiImpl implements MailboxApi {
if (major < 0 || minor < 0) throw new ApiException(); if (major < 0 || minor < 0) throw new ApiException();
serverSupports.add(new MailboxVersion(major, minor)); serverSupports.add(new MailboxVersion(major, minor));
} }
// Sort the list of versions for easier comparison
sort(serverSupports);
return serverSupports; return serverSupports;
} }
@@ -143,7 +137,7 @@ class MailboxApiImpl implements MailboxApi {
throws IOException, ApiException { throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException(); if (!properties.isOwner()) throw new IllegalArgumentException();
Request request = getRequestBuilder(properties.getAuthToken()) Request request = getRequestBuilder(properties.getAuthToken())
.url(getBaseUrl(properties) + "/") .url(properties.getBaseUrl() + "/")
.delete() .delete()
.build(); .build();
OkHttpClient client = httpClientProvider.get(); OkHttpClient client = httpClientProvider.get();
@@ -168,7 +162,7 @@ class MailboxApiImpl implements MailboxApi {
public void deleteContact(MailboxProperties properties, ContactId contactId) public void deleteContact(MailboxProperties properties, ContactId contactId)
throws IOException, ApiException, TolerableFailureException { throws IOException, ApiException, TolerableFailureException {
if (!properties.isOwner()) throw new IllegalArgumentException(); if (!properties.isOwner()) throw new IllegalArgumentException();
String url = getBaseUrl(properties) + "/contacts/" + String url = properties.getBaseUrl() + "/contacts/" +
contactId.getInt(); contactId.getInt();
Request request = getRequestBuilder(properties.getAuthToken()) Request request = getRequestBuilder(properties.getAuthToken())
.delete() .delete()
@@ -218,11 +212,9 @@ class MailboxApiImpl implements MailboxApi {
@Override @Override
public List<MailboxFile> getFiles(MailboxProperties properties, public List<MailboxFile> getFiles(MailboxProperties properties,
MailboxFolderId folderId) MailboxFolderId folderId) throws IOException, ApiException {
throws IOException, ApiException, TolerableFailureException {
String path = "/files/" + folderId; String path = "/files/" + folderId;
Response response = sendGetRequest(properties, path); Response response = sendGetRequest(properties, path);
if (response.code() == 404) throw new TolerableFailureException();
if (response.code() != 200) throw new ApiException(); if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body(); ResponseBody body = response.body();
@@ -247,7 +239,7 @@ class MailboxApiImpl implements MailboxApi {
if (time < 1) throw new ApiException(); if (time < 1) throw new ApiException();
list.add(new MailboxFile(MailboxFileId.fromString(name), time)); list.add(new MailboxFile(MailboxFileId.fromString(name), time));
} }
sort(list); Collections.sort(list);
return list; return list;
} catch (JacksonException | InvalidMailboxIdException e) { } catch (JacksonException | InvalidMailboxIdException e) {
throw new ApiException(); throw new ApiException();
@@ -256,11 +248,9 @@ class MailboxApiImpl implements MailboxApi {
@Override @Override
public void getFile(MailboxProperties properties, MailboxFolderId folderId, public void getFile(MailboxProperties properties, MailboxFolderId folderId,
MailboxFileId fileId, File file) MailboxFileId fileId, File file) throws IOException, ApiException {
throws IOException, ApiException, TolerableFailureException {
String path = "/files/" + folderId + "/" + fileId; String path = "/files/" + folderId + "/" + fileId;
Response response = sendGetRequest(properties, path); Response response = sendGetRequest(properties, path);
if (response.code() == 404) throw new TolerableFailureException();
if (response.code() != 200) throw new ApiException(); if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body(); ResponseBody body = response.body();
@@ -276,7 +266,7 @@ class MailboxApiImpl implements MailboxApi {
String path = "/files/" + folderId + "/" + fileId; String path = "/files/" + folderId + "/" + fileId;
Request request = getRequestBuilder(properties.getAuthToken()) Request request = getRequestBuilder(properties.getAuthToken())
.delete() .delete()
.url(getBaseUrl(properties) + path) .url(properties.getBaseUrl() + path)
.build(); .build();
OkHttpClient client = httpClientProvider.get(); OkHttpClient client = httpClientProvider.get();
Response response = client.newCall(request).execute(); Response response = client.newCall(request).execute();
@@ -318,7 +308,7 @@ class MailboxApiImpl implements MailboxApi {
private Response sendGetRequest(MailboxProperties properties, String path) private Response sendGetRequest(MailboxProperties properties, String path)
throws IOException { throws IOException {
Request request = getRequestBuilder(properties.getAuthToken()) Request request = getRequestBuilder(properties.getAuthToken())
.url(getBaseUrl(properties) + path) .url(properties.getBaseUrl() + path)
.build(); .build();
OkHttpClient client = httpClientProvider.get(); OkHttpClient client = httpClientProvider.get();
return client.newCall(request).execute(); return client.newCall(request).execute();
@@ -327,7 +317,7 @@ class MailboxApiImpl implements MailboxApi {
private Response sendPostRequest(MailboxProperties properties, String path, private Response sendPostRequest(MailboxProperties properties, String path,
RequestBody body) throws IOException { RequestBody body) throws IOException {
Request request = getRequestBuilder(properties.getAuthToken()) Request request = getRequestBuilder(properties.getAuthToken())
.url(getBaseUrl(properties) + path) .url(properties.getBaseUrl() + path)
.post(body) .post(body)
.build(); .build();
OkHttpClient client = httpClientProvider.get(); OkHttpClient client = httpClientProvider.get();
@@ -349,7 +339,4 @@ class MailboxApiImpl implements MailboxApi {
return (ArrayNode) arrayNode; return (ArrayNode) arrayNode;
} }
private String getBaseUrl(MailboxProperties properties) {
return urlConverter.convertOnionToBaseUrl(properties.getOnion());
}
} }

View File

@@ -1,55 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
interface MailboxClient {
/**
* Asynchronously starts the client.
*/
void start();
/**
* Destroys the client and its workers, cancelling any pending tasks or
* retries.
*/
void destroy();
/**
* Assigns a contact to the client for upload.
*
* @param properties Properties for communicating with the mailbox
* managed by this client.
* @param folderId The ID of the folder to which files will be uploaded.
*/
void assignContactForUpload(ContactId c, MailboxProperties properties,
MailboxFolderId folderId);
/**
* Deassigns a contact from the client for upload.
*/
void deassignContactForUpload(ContactId c);
/**
* Assigns a contact to the client for download.
*
* @param properties Properties for communicating with the mailbox
* managed by this client.
* @param folderId The ID of the folder from which files will be
* downloaded.
*/
void assignContactForDownload(ContactId c, MailboxProperties properties,
MailboxFolderId folderId);
/**
* Deassigns a contact from the client for download.
*/
void deassignContactForDownload(ContactId c);
}

View File

@@ -1,21 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
interface MailboxClientFactory {
/**
* Creates a client for communicating with a contact's mailbox.
*/
MailboxClient createContactMailboxClient(
TorReachabilityMonitor reachabilityMonitor);
/**
* Creates a client for communicating with our own mailbox.
*/
MailboxClient createOwnMailboxClient(
TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties);
}

View File

@@ -1,52 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Provider;
@NotNullByDefault
class MailboxClientFactoryImpl implements MailboxClientFactory {
private final MailboxWorkerFactory workerFactory;
private final TaskScheduler taskScheduler;
private final Executor ioExecutor;
private final Provider<ContactMailboxConnectivityChecker>
contactCheckerProvider;
private final Provider<OwnMailboxConnectivityChecker> ownCheckerProvider;
@Inject
MailboxClientFactoryImpl(MailboxWorkerFactory workerFactory,
TaskScheduler taskScheduler,
@IoExecutor Executor ioExecutor,
Provider<ContactMailboxConnectivityChecker> contactCheckerProvider,
Provider<OwnMailboxConnectivityChecker> ownCheckerProvider) {
this.workerFactory = workerFactory;
this.taskScheduler = taskScheduler;
this.ioExecutor = ioExecutor;
this.contactCheckerProvider = contactCheckerProvider;
this.ownCheckerProvider = ownCheckerProvider;
}
@Override
public MailboxClient createContactMailboxClient(
TorReachabilityMonitor reachabilityMonitor) {
ConnectivityChecker connectivityChecker = contactCheckerProvider.get();
return new ContactMailboxClient(workerFactory, connectivityChecker,
reachabilityMonitor);
}
@Override
public MailboxClient createOwnMailboxClient(
TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties) {
ConnectivityChecker connectivityChecker = ownCheckerProvider.get();
return new OwnMailboxClient(workerFactory, connectivityChecker,
reachabilityMonitor, taskScheduler, ioExecutor, properties);
}
}

View File

@@ -1,667 +0,0 @@
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

@@ -1,250 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.mailbox.MailboxFileId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import org.briarproject.bramble.mailbox.TorReachabilityMonitor.TorReachabilityObserver;
import java.io.File;
import java.io.IOException;
import java.util.Queue;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Logger.getLogger;
@ThreadSafe
@NotNullByDefault
abstract class MailboxDownloadWorker implements MailboxWorker,
ConnectivityObserver, TorReachabilityObserver {
/**
* When the worker is started it waits for a connectivity check, then
* starts its first download cycle: checking for files to download,
* downloading and deleting the files, and checking again until all files
* have been downloaded and deleted.
* <p>
* The worker then waits for our Tor hidden service to be reachable before
* starting its second download cycle. This ensures that if a contact
* tried and failed to connect to our hidden service before it was
* reachable, and therefore uploaded a file to the mailbox instead, we'll
* find the file in the second download cycle.
*/
protected enum State {
CREATED,
CONNECTIVITY_CHECK,
DOWNLOAD_CYCLE_1,
WAITING_FOR_TOR,
DOWNLOAD_CYCLE_2,
FINISHED,
DESTROYED
}
protected static final Logger LOG =
getLogger(MailboxDownloadWorker.class.getName());
private final ConnectivityChecker connectivityChecker;
private final TorReachabilityMonitor torReachabilityMonitor;
protected final MailboxApiCaller mailboxApiCaller;
protected final MailboxApi mailboxApi;
private final MailboxFileManager mailboxFileManager;
protected final MailboxProperties mailboxProperties;
protected final Object lock = new Object();
@GuardedBy("lock")
protected State state = State.CREATED;
@GuardedBy("lock")
@Nullable
protected Cancellable apiCall = null;
/**
* Creates the API call that starts the worker's download cycle.
*/
protected abstract ApiCall createApiCallForDownloadCycle();
MailboxDownloadWorker(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor torReachabilityMonitor,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager,
MailboxProperties mailboxProperties) {
this.connectivityChecker = connectivityChecker;
this.torReachabilityMonitor = torReachabilityMonitor;
this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi;
this.mailboxFileManager = mailboxFileManager;
this.mailboxProperties = mailboxProperties;
}
@Override
public void start() {
LOG.info("Started");
synchronized (lock) {
// Don't allow the worker to be reused
if (state != State.CREATED) return;
state = State.CONNECTIVITY_CHECK;
}
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
connectivityChecker.checkConnectivity(mailboxProperties, this);
boolean destroyed;
synchronized (lock) {
destroyed = state == State.DESTROYED;
}
if (destroyed) connectivityChecker.removeObserver(this);
}
@Override
public void destroy() {
LOG.info("Destroyed");
Cancellable apiCall;
synchronized (lock) {
state = State.DESTROYED;
apiCall = this.apiCall;
this.apiCall = null;
}
if (apiCall != null) apiCall.cancel();
connectivityChecker.removeObserver(this);
torReachabilityMonitor.removeObserver(this);
}
@Override
public void onConnectivityCheckSucceeded() {
LOG.info("Connectivity check succeeded");
synchronized (lock) {
if (state != State.CONNECTIVITY_CHECK) return;
state = State.DOWNLOAD_CYCLE_1;
// Start first download cycle
apiCall = mailboxApiCaller.retryWithBackoff(
createApiCallForDownloadCycle());
}
}
void onDownloadCycleFinished() {
boolean addObserver = false;
synchronized (lock) {
if (state == State.DOWNLOAD_CYCLE_1) {
LOG.info("First download cycle finished");
state = State.WAITING_FOR_TOR;
apiCall = null;
addObserver = true;
} else if (state == State.DOWNLOAD_CYCLE_2) {
LOG.info("Second download cycle finished");
state = State.FINISHED;
apiCall = null;
}
}
if (addObserver) {
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
torReachabilityMonitor.addOneShotObserver(this);
boolean destroyed;
synchronized (lock) {
destroyed = state == State.DESTROYED;
}
if (destroyed) torReachabilityMonitor.removeObserver(this);
}
}
void downloadNextFile(Queue<FolderFile> queue) {
synchronized (lock) {
if (state == State.DESTROYED) return;
if (queue.isEmpty()) {
// Check for files again, as new files may have arrived while
// we were downloading
apiCall = mailboxApiCaller.retryWithBackoff(
createApiCallForDownloadCycle());
} else {
FolderFile file = queue.remove();
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(() ->
apiCallDownloadFile(file, queue)));
}
}
}
private void apiCallDownloadFile(FolderFile file, Queue<FolderFile> queue)
throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
LOG.info("Downloading file");
File tempFile = mailboxFileManager.createTempFileForDownload();
try {
mailboxApi.getFile(mailboxProperties, file.folderId, file.fileId,
tempFile);
} catch (IOException | ApiException e) {
if (!tempFile.delete()) {
LOG.warning("Failed to delete temporary file");
}
throw e;
} catch (TolerableFailureException e) {
// File not found - continue to the next file
LOG.warning("File does not exist");
if (!tempFile.delete()) {
LOG.warning("Failed to delete temporary file");
}
downloadNextFile(queue);
return;
}
mailboxFileManager.handleDownloadedFile(tempFile);
deleteFile(file, queue);
}
private void deleteFile(FolderFile file, Queue<FolderFile> queue) {
synchronized (lock) {
if (state == State.DESTROYED) return;
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(() -> apiCallDeleteFile(file, queue)));
}
}
private void apiCallDeleteFile(FolderFile file, Queue<FolderFile> queue)
throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
try {
mailboxApi.deleteFile(mailboxProperties, file.folderId,
file.fileId);
} catch (TolerableFailureException e) {
// File not found - continue to the next file
LOG.warning("File does not exist");
}
downloadNextFile(queue);
}
@Override
public void onTorReachable() {
LOG.info("Our Tor hidden service is reachable");
synchronized (lock) {
if (state != State.WAITING_FOR_TOR) return;
state = State.DOWNLOAD_CYCLE_2;
// Start second download cycle
apiCall = mailboxApiCaller.retryWithBackoff(
createApiCallForDownloadCycle());
}
}
// Package access for testing
static class FolderFile {
final MailboxFolderId folderId;
final MailboxFileId fileId;
FolderFile(MailboxFolderId folderId, MailboxFileId fileId) {
this.folderId = folderId;
this.fileId = fileId;
}
}
}

View File

@@ -1,8 +1,6 @@
package org.briarproject.bramble.mailbox; package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -18,14 +16,6 @@ interface MailboxFileManager {
*/ */
File createTempFileForDownload() throws IOException; File createTempFileForDownload() throws IOException;
/**
* Creates a file to be uploaded to the given contact and writes any
* waiting data to the file. The IDs of any messages sent or acked will
* be added to the given {@link OutgoingSessionRecord}.
*/
File createAndWriteTempFileForUpload(ContactId contactId,
OutgoingSessionRecord sessionRecord) throws IOException;
/** /**
* Handles a file that has been downloaded. The file should be created * Handles a file that has been downloaded. The file should be created
* with {@link #createTempFileForDownload()}. * with {@link #createTempFileForDownload()}.

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.mailbox; package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.connection.ConnectionManager; import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
@@ -11,18 +10,13 @@ import org.briarproject.bramble.api.mailbox.MailboxDirectory;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -36,7 +30,6 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleS
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID; import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH; import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
import static org.briarproject.bramble.util.IoUtils.delete;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe @ThreadSafe
@@ -48,7 +41,6 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
// Package access for testing // Package access for testing
static final String DOWNLOAD_DIR_NAME = "downloads"; static final String DOWNLOAD_DIR_NAME = "downloads";
static final String UPLOAD_DIR_NAME = "uploads";
private final Executor ioExecutor; private final Executor ioExecutor;
private final PluginManager pluginManager; private final PluginManager pluginManager;
@@ -75,44 +67,14 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
@Override @Override
public File createTempFileForDownload() throws IOException { public File createTempFileForDownload() throws IOException {
return createTempFile(DOWNLOAD_DIR_NAME);
}
@Override
public File createAndWriteTempFileForUpload(ContactId contactId,
OutgoingSessionRecord sessionRecord) throws IOException {
File f = createTempFile(UPLOAD_DIR_NAME);
// We shouldn't reach this point until the plugin has been started
SimplexPlugin plugin =
(SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID));
TransportProperties p = new TransportProperties();
p.put(PROP_PATH, f.getAbsolutePath());
TransportConnectionWriter writer = plugin.createWriter(p);
if (writer == null) {
delete(f);
throw new IOException();
}
MailboxFileWriter decorated = new MailboxFileWriter(writer);
LOG.info("Writing file for upload");
connectionManager.manageOutgoingConnection(contactId, ID, decorated,
sessionRecord);
if (decorated.awaitDisposal()) {
// An exception was thrown during the session - delete the file
delete(f);
throw new IOException();
}
return f;
}
private File createTempFile(String dirName) throws IOException {
// Wait for orphaned files to be handled before creating new files // Wait for orphaned files to be handled before creating new files
try { try {
orphanLatch.await(); orphanLatch.await();
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new IOException(e); throw new IOException(e);
} }
File dir = createDirectoryIfNeeded(dirName); File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
return File.createTempFile("mailbox", ".tmp", dir); return File.createTempFile("mailbox", ".tmp", downloadDir);
} }
private File createDirectoryIfNeeded(String name) throws IOException { private File createDirectoryIfNeeded(String name) throws IOException {
@@ -154,8 +116,6 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
// Wait for the transport to become active before handling orphaned
// files so that we can get the plugin from the plugin manager
if (e instanceof TransportActiveEvent) { if (e instanceof TransportActiveEvent) {
TransportActiveEvent t = (TransportActiveEvent) e; TransportActiveEvent t = (TransportActiveEvent) e;
if (t.getTransportId().equals(ID)) { if (t.getTransportId().equals(ID)) {
@@ -167,25 +127,17 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
/** /**
* This method is called at startup, as soon as the plugin is started, to * This method is called at startup, as soon as the plugin is started, to
* delete any files that were left in the upload directory at the last * handle any files that were left in the download directory at the last
* shutdown and handle any files that were left in the download directory. * shutdown.
*/ */
@IoExecutor @IoExecutor
private void handleOrphanedFiles() { private void handleOrphanedFiles() {
try { try {
File uploadDir = createDirectoryIfNeeded(UPLOAD_DIR_NAME);
File[] orphanedUploads = uploadDir.listFiles();
if (orphanedUploads != null) {
for (File f : orphanedUploads) delete(f);
}
File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME); File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
File[] orphanedDownloads = downloadDir.listFiles(); File[] orphans = downloadDir.listFiles();
// Now that we've got the list of orphaned downloads, new files // Now that we've got the list of orphans, new files can be created
// can be created in the download directory
orphanLatch.countDown(); orphanLatch.countDown();
if (orphanedDownloads != null) { if (orphans != null) for (File f : orphans) handleDownloadedFile(f);
for (File f : orphanedDownloads) handleDownloadedFile(f);
}
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
@@ -213,58 +165,9 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
delegate.dispose(exception, recognised); delegate.dispose(exception, recognised);
if (isHandlingComplete(exception, recognised)) { if (isHandlingComplete(exception, recognised)) {
LOG.info("Deleting downloaded file"); LOG.info("Deleting downloaded file");
delete(file); if (!file.delete()) {
} LOG.warning("Failed to delete downloaded file");
} }
}
private static class MailboxFileWriter
implements TransportConnectionWriter {
private final TransportConnectionWriter delegate;
private final BlockingQueue<Boolean> disposalResult =
new ArrayBlockingQueue<>(1);
private MailboxFileWriter(TransportConnectionWriter delegate) {
this.delegate = delegate;
}
@Override
public long getMaxLatency() {
return delegate.getMaxLatency();
}
@Override
public int getMaxIdleTime() {
return delegate.getMaxIdleTime();
}
@Override
public boolean isLossyAndCheap() {
return delegate.isLossyAndCheap();
}
@Override
public OutputStream getOutputStream() throws IOException {
return delegate.getOutputStream();
}
@Override
public void dispose(boolean exception) throws IOException {
delegate.dispose(exception);
disposalResult.add(exception);
}
/**
* Waits for the delegate to be disposed and returns true if an
* exception occurred.
*/
private boolean awaitDisposal() {
try {
return disposalResult.take();
} catch (InterruptedException e) {
LOG.info("Interrupted while waiting for disposal");
return true;
} }
} }
} }

View File

@@ -9,12 +9,10 @@ import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus; import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -100,35 +98,34 @@ class MailboxManagerImpl implements MailboxManager {
@Override @Override
public boolean checkConnection() { public boolean checkConnection() {
List<MailboxVersion> versions = null; boolean success;
try { try {
MailboxProperties props = db.transactionWithNullableResult(true, MailboxProperties props = db.transactionWithNullableResult(true,
mailboxSettingsManager::getOwnMailboxProperties); mailboxSettingsManager::getOwnMailboxProperties);
if (props == null) throw new DbException(); if (props == null) throw new DbException();
versions = api.getServerSupports(props); success = api.checkStatus(props);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
// we don't treat this is a failure to record // we don't treat this is a failure to record
return false; return false;
} catch (IOException | MailboxApi.ApiException e) { } catch (IOException | MailboxApi.ApiException e) {
// we record this as a failure // we record this as a failure
success = false;
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
try { try {
recordCheckResult(versions); recordCheckResult(success);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
return versions != null; return success;
} }
private void recordCheckResult(@Nullable List<MailboxVersion> versions) private void recordCheckResult(boolean success) throws DbException {
throws DbException {
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
db.transaction(false, txn -> { db.transaction(false, txn -> {
if (versions != null) { if (success) {
mailboxSettingsManager mailboxSettingsManager.recordSuccessfulConnection(txn, now);
.recordSuccessfulConnection(txn, now, versions);
} else { } else {
mailboxSettingsManager.recordFailedConnectionAttempt(txn, now); mailboxSettingsManager.recordFailedConnectionAttempt(txn, now);
} }

View File

@@ -4,22 +4,17 @@ import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder; 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.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.mailbox.MailboxManager; import org.briarproject.bramble.api.mailbox.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion; 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.sync.validation.ValidationManager;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -42,8 +37,6 @@ public class MailboxModule {
MailboxUpdateManager mailboxUpdateManager; MailboxUpdateManager mailboxUpdateManager;
@Inject @Inject
MailboxFileManager mailboxFileManager; MailboxFileManager mailboxFileManager;
@Inject
MailboxClientManager mailboxClientManager;
} }
@Provides @Provides
@@ -59,19 +52,13 @@ public class MailboxModule {
} }
@Provides @Provides
@Singleton
MailboxSettingsManager provideMailboxSettingsManager( MailboxSettingsManager provideMailboxSettingsManager(
MailboxSettingsManagerImpl mailboxSettingsManager) { MailboxSettingsManagerImpl mailboxSettingsManager) {
return mailboxSettingsManager; return mailboxSettingsManager;
} }
@Provides @Provides
UrlConverter provideUrlConverter(UrlConverterImpl urlConverter) { MailboxApi providesMailboxApi(MailboxApiImpl mailboxApi) {
return urlConverter;
}
@Provides
MailboxApi provideMailboxApi(MailboxApiImpl mailboxApi) {
return mailboxApi; return mailboxApi;
} }
@@ -127,55 +114,4 @@ public class MailboxModule {
} }
return mailboxFileManager; return mailboxFileManager;
} }
@Provides
MailboxWorkerFactory provideMailboxWorkerFactory(
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

@@ -120,8 +120,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
db.transaction(false, txn -> { db.transaction(false, txn -> {
mailboxSettingsManager mailboxSettingsManager
.setOwnMailboxProperties(txn, ownerProperties); .setOwnMailboxProperties(txn, ownerProperties);
mailboxSettingsManager.recordSuccessfulConnection(txn, time, mailboxSettingsManager.recordSuccessfulConnection(txn, time);
ownerProperties.getServerSupports());
// A (possibly new) mailbox is paired. Reset message retransmission // A (possibly new) mailbox is paired. Reset message retransmission
// timers for contacts who doesn't have their own mailbox. This way, // timers for contacts who doesn't have their own mailbox. This way,
// data stranded on our old mailbox will be re-uploaded to our new. // data stranded on our old mailbox will be re-uploaded to our new.
@@ -178,9 +177,10 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
LOG.info("QR code is valid"); LOG.info("QR code is valid");
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33); byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
String onion = crypto.encodeOnion(onionPubKey); String onion = crypto.encodeOnion(onionPubKey);
String baseUrl = "http://" + onion + ".onion"; // TODO
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65); byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes); MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes);
return new MailboxProperties(onion, setupToken, new ArrayList<>()); return new MailboxProperties(baseUrl, setupToken, new ArrayList<>());
} }
} }

View File

@@ -20,13 +20,13 @@ import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@ThreadSafe @Immutable
@NotNullByDefault @NotNullByDefault
class MailboxSettingsManagerImpl implements MailboxSettingsManager { class MailboxSettingsManagerImpl implements MailboxSettingsManager {
@@ -74,13 +74,19 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
public void setOwnMailboxProperties(Transaction txn, MailboxProperties p) public void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
throws DbException { throws DbException {
Settings s = new Settings(); Settings s = new Settings();
s.put(SETTINGS_KEY_ONION, p.getOnion()); s.put(SETTINGS_KEY_ONION, p.getBaseUrl());
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString()); s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
List<MailboxVersion> serverSupports = p.getServerSupports(); List<MailboxVersion> serverSupports = p.getServerSupports();
encodeServerSupports(serverSupports, s); int[] ints = new int[serverSupports.size() * 2];
int i = 0;
for (MailboxVersion v : serverSupports) {
ints[i++] = v.getMajor();
ints[i++] = v.getMinor();
}
s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE); settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
for (MailboxHook hook : hooks) { for (MailboxHook hook : hooks) {
hook.mailboxPaired(txn, p); hook.mailboxPaired(txn, p.getOnion(), p.getServerSupports());
} }
} }
@@ -89,10 +95,6 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
Settings s = new Settings(); Settings s = new Settings();
s.put(SETTINGS_KEY_ONION, ""); s.put(SETTINGS_KEY_ONION, "");
s.put(SETTINGS_KEY_TOKEN, ""); s.put(SETTINGS_KEY_TOKEN, "");
s.put(SETTINGS_KEY_ATTEMPTS, "");
s.put(SETTINGS_KEY_LAST_ATTEMPT, "");
s.put(SETTINGS_KEY_LAST_SUCCESS, "");
s.put(SETTINGS_KEY_SERVER_SUPPORTS, "");
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE); settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
for (MailboxHook hook : hooks) { for (MailboxHook hook : hooks) {
hook.mailboxUnpaired(txn); hook.mailboxUnpaired(txn);
@@ -117,38 +119,25 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
} }
@Override @Override
public void recordSuccessfulConnection(Transaction txn, long now, public void recordSuccessfulConnection(Transaction txn, long now)
List<MailboxVersion> versions) throws DbException { throws DbException {
// if we no longer have a paired mailbox, return
Settings oldSettings = Settings oldSettings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE); settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
String onion = oldSettings.get(SETTINGS_KEY_ONION);
String token = oldSettings.get(SETTINGS_KEY_TOKEN);
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return;
Settings s = new Settings(); Settings s = new Settings();
// record the successful connection
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now); s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now); s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
s.putInt(SETTINGS_KEY_ATTEMPTS, 0); s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
encodeServerSupports(versions, s);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE); settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
for (MailboxHook hook : hooks) { List<MailboxVersion> serverSupports = parseServerSupports(oldSettings);
hook.serverSupportedVersionsReceived(txn, versions); MailboxStatus status = new MailboxStatus(now, now, 0, serverSupports);
}
// broadcast status event
MailboxStatus status = new MailboxStatus(now, now, 0, versions);
txn.attach(new OwnMailboxConnectionStatusEvent(status)); txn.attach(new OwnMailboxConnectionStatusEvent(status));
} }
@Override @Override
public void recordFailedConnectionAttempt(Transaction txn, long now) public void recordFailedConnectionAttempt(Transaction txn, long now)
throws DbException { throws DbException {
// if we no longer have a paired mailbox, return
Settings oldSettings = Settings oldSettings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE); settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
String onion = oldSettings.get(SETTINGS_KEY_ONION);
String token = oldSettings.get(SETTINGS_KEY_TOKEN);
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return;
int newAttempts = 1 + oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0); int newAttempts = 1 + oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
long lastSuccess = oldSettings.getLong(SETTINGS_KEY_LAST_SUCCESS, 0); long lastSuccess = oldSettings.getLong(SETTINGS_KEY_LAST_SUCCESS, 0);
Settings newSettings = new Settings(); Settings newSettings = new Settings();
@@ -182,17 +171,6 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
return filename; return filename;
} }
private void encodeServerSupports(List<MailboxVersion> serverSupports,
Settings s) {
int[] ints = new int[serverSupports.size() * 2];
int i = 0;
for (MailboxVersion v : serverSupports) {
ints[i++] = v.getMajor();
ints[i++] = v.getMinor();
}
s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints);
}
private List<MailboxVersion> parseServerSupports(Settings s) private List<MailboxVersion> parseServerSupports(Settings s)
throws DbException { throws DbException {
if (!s.containsKey(SETTINGS_KEY_SERVER_SUPPORTS)) return emptyList(); if (!s.containsKey(SETTINGS_KEY_SERVER_SUPPORTS)) return emptyList();

View File

@@ -25,9 +25,6 @@ import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox; import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.mailbox.MailboxVersion; 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.RemoteMailboxUpdateEvent; import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
@@ -41,7 +38,6 @@ import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
import java.util.HashMap;
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;
@@ -49,9 +45,6 @@ import java.util.Map.Entry;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
@NotNullByDefault @NotNullByDefault
@@ -123,7 +116,7 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
db.addGroup(txn, localGroup); db.addGroup(txn, localGroup);
// Set things up for any pre-existing contacts // Set things up for any pre-existing contacts
for (Contact c : db.getContacts(txn)) { for (Contact c : db.getContacts(txn)) {
addingContact(txn, c, false); addingContact(txn, c);
} }
} }
@@ -139,17 +132,6 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
@Override @Override
public void addingContact(Transaction txn, Contact c) throws DbException { 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 // Create a group to share with the contact
Group g = getContactGroup(c); Group g = getContactGroup(c);
db.addGroup(txn, g); db.addGroup(txn, g);
@@ -161,17 +143,13 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
clientHelper.setContactId(txn, g.getId(), c.getId()); clientHelper.setContactId(txn, g.getId(), c.getId());
MailboxProperties ownProps = MailboxProperties ownProps =
mailboxSettingsManager.getOwnMailboxProperties(txn); mailboxSettingsManager.getOwnMailboxProperties(txn);
MailboxUpdate u;
if (ownProps != null) { if (ownProps != null) {
// We are paired, create and send props to the newly added contact // We are paired, create and send props to the newly added contact
u = createAndSendUpdateWithMailbox(txn, c, createAndSendUpdateWithMailbox(txn, c, ownProps.getServerSupports(),
ownProps.getServerSupports(), ownProps.getOnion()); ownProps.getOnion());
} else { } else {
// Not paired, but we still want to get our clientSupports sent // Not paired, but we still want to get our clientSupports sent
u = sendUpdateNoMailbox(txn, c); sendUpdateNoMailbox(txn, c);
}
if (attachEvent) {
txn.attach(new MailboxUpdateSentToNewContactEvent(c.getId(), u));
} }
} }
@@ -181,103 +159,18 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
} }
@Override @Override
public void mailboxPaired(Transaction txn, MailboxProperties p) public void mailboxPaired(Transaction txn, String ownOnion,
throws DbException { List<MailboxVersion> serverSupports) throws DbException {
Map<ContactId, MailboxUpdateWithMailbox> localUpdates = new HashMap<>();
for (Contact c : db.getContacts(txn)) { for (Contact c : db.getContacts(txn)) {
MailboxUpdateWithMailbox u = createAndSendUpdateWithMailbox(txn, c, createAndSendUpdateWithMailbox(txn, c, serverSupports, ownOnion);
p.getServerSupports(), p.getOnion());
localUpdates.put(c.getId(), u);
}
txn.attach(new MailboxPairedEvent(p, localUpdates));
// Store the list of server-supported versions
try {
storeSentServerSupports(txn, p.getServerSupports());
} catch (FormatException e) {
throw new DbException();
} }
} }
@Override @Override
public void mailboxUnpaired(Transaction txn) throws DbException { public void mailboxUnpaired(Transaction txn) throws DbException {
Map<ContactId, MailboxUpdate> localUpdates = new HashMap<>();
for (Contact c : db.getContacts(txn)) { for (Contact c : db.getContacts(txn)) {
MailboxUpdate u = sendUpdateNoMailbox(txn, c); sendUpdateNoMailbox(txn, c);
localUpdates.put(c.getId(), u);
} }
txn.attach(new MailboxUnpairedEvent(localUpdates));
// Remove the list of server-supported versions
try {
BdfDictionary meta = BdfDictionary.of(new BdfEntry(
GROUP_KEY_SENT_SERVER_SUPPORTS, NULL_VALUE));
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
} catch (FormatException e) {
throw new DbException();
}
}
@Override
public void serverSupportedVersionsReceived(Transaction txn,
List<MailboxVersion> serverSupports) throws DbException {
try {
List<MailboxVersion> oldServerSupports =
loadSentServerSupports(txn);
if (serverSupports.equals(oldServerSupports)) return;
storeSentServerSupports(txn, serverSupports);
for (Contact c : db.getContacts(txn)) {
Group contactGroup = getContactGroup(c);
LatestUpdate latest =
findLatest(txn, contactGroup.getId(), true);
// This method should only be called when we have a mailbox,
// in which case we should have sent a local update to every
// contact
if (latest == null) throw new DbException();
BdfList body =
clientHelper.getMessageAsList(txn, latest.messageId);
MailboxUpdate oldUpdate = parseUpdate(body);
if (!oldUpdate.hasMailbox()) throw new DbException();
MailboxUpdateWithMailbox newUpdate = updateServerSupports(
(MailboxUpdateWithMailbox) oldUpdate, serverSupports);
storeMessageReplaceLatest(txn, contactGroup.getId(), newUpdate,
latest);
}
} catch (FormatException e) {
throw new DbException();
}
}
private void storeSentServerSupports(Transaction txn,
List<MailboxVersion> serverSupports)
throws DbException, FormatException {
BdfDictionary meta = BdfDictionary.of(new BdfEntry(
GROUP_KEY_SENT_SERVER_SUPPORTS,
encodeSupportsList(serverSupports)));
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
}
private List<MailboxVersion> loadSentServerSupports(Transaction txn)
throws DbException, FormatException {
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn,
localGroup.getId());
BdfList serverSupports =
meta.getOptionalList(GROUP_KEY_SENT_SERVER_SUPPORTS);
if (serverSupports == null) return emptyList();
return clientHelper.parseMailboxVersionList(serverSupports);
}
/**
* Returns a new {@link MailboxUpdateWithMailbox} that updates the list
* of server-supported API versions in the given
* {@link MailboxUpdateWithMailbox}.
*/
private MailboxUpdateWithMailbox updateServerSupports(
MailboxUpdateWithMailbox old, List<MailboxVersion> serverSupports) {
MailboxProperties oldProps = old.getMailboxProperties();
MailboxProperties newProps = new MailboxProperties(oldProps.getOnion(),
oldProps.getAuthToken(), serverSupports,
requireNonNull(oldProps.getInboxId()),
requireNonNull(oldProps.getOutboxId()));
return new MailboxUpdateWithMailbox(old.getClientSupports(), newProps);
} }
@Override @Override
@@ -346,19 +239,19 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
* supported Mailbox API version(s). All of which the contact needs to * supported Mailbox API version(s). All of which the contact needs to
* communicate with our Mailbox. * communicate with our Mailbox.
*/ */
private MailboxUpdateWithMailbox createAndSendUpdateWithMailbox( private void createAndSendUpdateWithMailbox(Transaction txn, Contact c,
Transaction txn, Contact c, List<MailboxVersion> serverSupports, List<MailboxVersion> serverSupports, String ownOnion)
String ownOnion) throws DbException { throws DbException {
MailboxProperties properties = new MailboxProperties(ownOnion, String baseUrl = "http://" + ownOnion + ".onion"; // TODO
MailboxProperties properties = new MailboxProperties(baseUrl,
new MailboxAuthToken(crypto.generateUniqueId().getBytes()), new MailboxAuthToken(crypto.generateUniqueId().getBytes()),
serverSupports, serverSupports,
new MailboxFolderId(crypto.generateUniqueId().getBytes()), new MailboxFolderId(crypto.generateUniqueId().getBytes()),
new MailboxFolderId(crypto.generateUniqueId().getBytes())); new MailboxFolderId(crypto.generateUniqueId().getBytes()));
MailboxUpdateWithMailbox u = MailboxUpdate u =
new MailboxUpdateWithMailbox(clientSupports, properties); new MailboxUpdateWithMailbox(clientSupports, properties);
Group g = getContactGroup(c); Group g = getContactGroup(c);
storeMessageReplaceLatest(txn, g.getId(), u); storeMessageReplaceLatest(txn, g.getId(), u);
return u;
} }
/** /**
@@ -367,12 +260,11 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
* Mailbox that they can use. It still includes the list of Mailbox API * Mailbox that they can use. It still includes the list of Mailbox API
* version(s) that we support as a client. * version(s) that we support as a client.
*/ */
private MailboxUpdate sendUpdateNoMailbox(Transaction txn, Contact c) private void sendUpdateNoMailbox(Transaction txn, Contact c)
throws DbException { throws DbException {
Group g = getContactGroup(c); Group g = getContactGroup(c);
MailboxUpdate u = new MailboxUpdate(clientSupports); MailboxUpdate u = new MailboxUpdate(clientSupports);
storeMessageReplaceLatest(txn, g.getId(), u); storeMessageReplaceLatest(txn, g.getId(), u);
return u;
} }
@Nullable @Nullable
@@ -397,27 +289,21 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
MailboxUpdate u) throws DbException { MailboxUpdate u) throws DbException {
try { try {
LatestUpdate latest = findLatest(txn, g, true); LatestUpdate latest = findLatest(txn, g, true);
storeMessageReplaceLatest(txn, g, u, latest); long version = latest == null ? 1 : latest.version + 1;
Message m = clientHelper.createMessage(g, clock.currentTimeMillis(),
encodeProperties(version, u));
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_VERSION, version);
meta.put(MSG_KEY_LOCAL, true);
clientHelper.addLocalMessage(txn, m, meta, true, false);
if (latest != null) {
db.removeMessage(txn, latest.messageId);
}
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} }
} }
private void storeMessageReplaceLatest(Transaction txn, GroupId g,
MailboxUpdate u, @Nullable LatestUpdate latest)
throws DbException, FormatException {
long version = latest == null ? 1 : latest.version + 1;
Message m = clientHelper.createMessage(g, clock.currentTimeMillis(),
encodeProperties(version, u));
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_VERSION, version);
meta.put(MSG_KEY_LOCAL, true);
clientHelper.addLocalMessage(txn, m, meta, true, false);
if (latest != null) {
db.removeMessage(txn, latest.messageId);
}
}
@Nullable @Nullable
private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local) private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local)
throws DbException, FormatException { throws DbException, FormatException {

View File

@@ -1,464 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
import org.briarproject.bramble.api.sync.event.MessageSharedEvent;
import org.briarproject.bramble.api.sync.event.MessageToAckEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.lang.Boolean.TRUE;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_LATENCY;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.util.IoUtils.delete;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class MailboxUploadWorker implements MailboxWorker, ConnectivityObserver,
EventListener {
/**
* When the worker is started it checks for data to send. If data is ready
* to send, the worker waits for a connectivity check, then writes and
* uploads a file and checks again for data to send.
* <p>
* If data is due to be sent at some time in the future, the worker
* schedules a wakeup for that time and also listens for events indicating
* that new data may be ready to send.
* <p>
* If there's no data to send, the worker listens for events indicating
* that new data may be ready to send.
* <p>
* Whenever we're directly connected to the contact, the worker doesn't
* check for data to send or start connectivity checks until the contact
* disconnects. However, if the worker has already started writing and
* uploading a file when the contact connects, the worker will finish the
* upload.
*/
private enum State {
CREATED,
CONNECTED_TO_CONTACT,
CHECKING_FOR_DATA,
WAITING_FOR_DATA,
CONNECTIVITY_CHECK,
WRITING_UPLOADING,
DESTROYED
}
private static final Logger LOG =
getLogger(MailboxUploadWorker.class.getName());
/**
* When we're waiting for data to send and an event indicates that new data
* may have become available, wait this long before checking the DB. This
* should help to avoid creating lots of small files when several acks or
* messages become available to send in a short period (eg when reading a
* file downloaded from a mailbox).
* <p>
* Package access for testing.
*/
static final long CHECK_DELAY_MS = 5_000;
/**
* How long to wait before retrying when an exception occurs while writing
* a file.
* <p>
* Package access for testing.
*/
static final long RETRY_DELAY_MS = MINUTES.toMillis(1);
private final Executor ioExecutor;
private final DatabaseComponent db;
private final Clock clock;
private final TaskScheduler taskScheduler;
private final EventBus eventBus;
private final ConnectionRegistry connectionRegistry;
private final ConnectivityChecker connectivityChecker;
private final MailboxApiCaller mailboxApiCaller;
private final MailboxApi mailboxApi;
private final MailboxFileManager mailboxFileManager;
private final MailboxProperties mailboxProperties;
private final MailboxFolderId folderId;
private final ContactId contactId;
private final Object lock = new Object();
@GuardedBy("lock")
private State state = State.CREATED;
@GuardedBy("lock")
@Nullable
private Cancellable wakeupTask = null, checkTask = null, apiCall = null;
@GuardedBy("lock")
@Nullable
private File file = null;
MailboxUploadWorker(@IoExecutor Executor ioExecutor,
DatabaseComponent db,
Clock clock,
TaskScheduler taskScheduler,
EventBus eventBus,
ConnectionRegistry connectionRegistry,
ConnectivityChecker connectivityChecker,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager,
MailboxProperties mailboxProperties,
MailboxFolderId folderId,
ContactId contactId) {
this.ioExecutor = ioExecutor;
this.db = db;
this.clock = clock;
this.taskScheduler = taskScheduler;
this.eventBus = eventBus;
this.connectionRegistry = connectionRegistry;
this.connectivityChecker = connectivityChecker;
this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi;
this.mailboxFileManager = mailboxFileManager;
this.mailboxProperties = mailboxProperties;
this.folderId = folderId;
this.contactId = contactId;
}
@Override
public void start() {
LOG.info("Started");
synchronized (lock) {
// Don't allow the worker to be reused
if (state != State.CREATED) return;
state = State.CHECKING_FOR_DATA;
}
ioExecutor.execute(this::checkForDataToSend);
}
@Override
public void destroy() {
LOG.info("Destroyed");
Cancellable wakeupTask, checkTask, apiCall;
File file;
synchronized (lock) {
state = State.DESTROYED;
wakeupTask = this.wakeupTask;
this.wakeupTask = null;
checkTask = this.checkTask;
this.checkTask = null;
apiCall = this.apiCall;
this.apiCall = null;
file = this.file;
this.file = null;
}
if (wakeupTask != null) wakeupTask.cancel();
if (checkTask != null) checkTask.cancel();
if (apiCall != null) apiCall.cancel();
if (file != null) delete(file);
connectivityChecker.removeObserver(this);
eventBus.removeListener(this);
}
@IoExecutor
private void checkForDataToSend() {
synchronized (lock) {
checkTask = null;
if (state != State.CHECKING_FOR_DATA) return;
// Check whether we're directly connected to the contact. Calling
// this while holding the lock isn't ideal, but it avoids races
if (connectionRegistry.isConnected(contactId)) {
state = State.CONNECTED_TO_CONTACT;
return;
}
}
LOG.info("Checking for data to send");
try {
db.transaction(true, txn -> {
long nextSendTime;
if (db.containsAcksToSend(txn, contactId)) {
nextSendTime = 0L;
} else {
nextSendTime = db.getNextSendTime(txn, contactId,
MAX_LATENCY);
}
// Handle the result on the event executor to avoid races with
// incoming events
txn.attach(() -> handleNextSendTime(nextSendTime));
});
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
@EventExecutor
private void handleNextSendTime(long nextSendTime) {
if (nextSendTime == Long.MAX_VALUE) {
// Nothing is sendable now or due to be sent in the future. Wait
// for an event indicating that new data may be ready to send
waitForDataToSend();
} else {
// Work out the delay until data's ready to send (may be negative)
long delay = nextSendTime - clock.currentTimeMillis();
if (delay > 0) {
// Schedule a wakeup when data will be ready to send. If an
// event is received in the meantime indicating that new data
// may be ready to send, we'll cancel the wakeup
scheduleWakeup(delay);
} else {
// Data is ready to send now
checkConnectivity();
}
}
}
@EventExecutor
private void waitForDataToSend() {
synchronized (lock) {
if (state != State.CHECKING_FOR_DATA) return;
state = State.WAITING_FOR_DATA;
LOG.info("Waiting for data to send");
}
}
@EventExecutor
private void scheduleWakeup(long delay) {
synchronized (lock) {
if (state != State.CHECKING_FOR_DATA) return;
state = State.WAITING_FOR_DATA;
if (LOG.isLoggable(INFO)) {
LOG.info("Scheduling wakeup in " + delay + " ms");
}
wakeupTask = taskScheduler.schedule(this::wakeUp, ioExecutor,
delay, MILLISECONDS);
}
}
@IoExecutor
private void wakeUp() {
LOG.info("Woke up");
synchronized (lock) {
wakeupTask = null;
if (state != State.WAITING_FOR_DATA) return;
state = State.CHECKING_FOR_DATA;
}
checkForDataToSend();
}
@EventExecutor
private void checkConnectivity() {
synchronized (lock) {
if (state != State.CHECKING_FOR_DATA) return;
state = State.CONNECTIVITY_CHECK;
}
LOG.info("Checking connectivity");
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
connectivityChecker.checkConnectivity(mailboxProperties, this);
boolean destroyed;
synchronized (lock) {
destroyed = state == State.DESTROYED;
}
if (destroyed) connectivityChecker.removeObserver(this);
}
@Override
public void onConnectivityCheckSucceeded() {
LOG.info("Connectivity check succeeded");
synchronized (lock) {
if (state != State.CONNECTIVITY_CHECK) return;
state = State.WRITING_UPLOADING;
}
ioExecutor.execute(this::writeAndUploadFile);
}
@IoExecutor
private void writeAndUploadFile() {
synchronized (lock) {
if (state != State.WRITING_UPLOADING) return;
}
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
File file;
try {
file = mailboxFileManager.createAndWriteTempFileForUpload(
contactId, sessionRecord);
} catch (IOException e) {
logException(LOG, WARNING, e);
// Try again after a delay
synchronized (lock) {
if (state != State.WRITING_UPLOADING) return;
state = State.CHECKING_FOR_DATA;
checkTask = taskScheduler.schedule(this::checkForDataToSend,
ioExecutor, RETRY_DELAY_MS, MILLISECONDS);
}
return;
}
boolean deleteFile = false;
synchronized (lock) {
if (state == State.WRITING_UPLOADING) {
this.file = file;
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(() -> apiCallUploadFile(file,
sessionRecord)));
} else {
deleteFile = true;
}
}
if (deleteFile) delete(file);
}
@IoExecutor
private void apiCallUploadFile(File file,
OutgoingSessionRecord sessionRecord)
throws IOException, ApiException {
synchronized (lock) {
if (state != State.WRITING_UPLOADING) return;
}
LOG.info("Uploading file");
mailboxApi.addFile(mailboxProperties, folderId, file);
markMessagesSentOrAcked(sessionRecord);
delete(file);
synchronized (lock) {
if (state != State.WRITING_UPLOADING) return;
state = State.CHECKING_FOR_DATA;
apiCall = null;
this.file = null;
}
checkForDataToSend();
}
private void markMessagesSentOrAcked(OutgoingSessionRecord sessionRecord) {
Collection<MessageId> acked = sessionRecord.getAckedIds();
Collection<MessageId> sent = sessionRecord.getSentIds();
try {
db.transaction(false, txn -> {
if (!acked.isEmpty()) {
db.setAckSent(txn, contactId, acked);
}
if (!sent.isEmpty()) {
db.setMessagesSent(txn, contactId, sent, MAX_LATENCY);
}
});
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
@Override
public void eventOccurred(Event e) {
if (e instanceof MessageToAckEvent) {
MessageToAckEvent m = (MessageToAckEvent) e;
if (m.getContactId().equals(contactId)) {
LOG.info("Message to ack");
onDataToSend();
}
} else if (e instanceof MessageSharedEvent) {
MessageSharedEvent m = (MessageSharedEvent) e;
// If the contact is present in the map (ie the value is not null)
// and the value is true, the message's group is shared with the
// contact and therefore the message may now be sendable
if (m.getGroupVisibility().get(contactId) == TRUE) {
LOG.info("Message shared");
onDataToSend();
}
} else if (e instanceof GroupVisibilityUpdatedEvent) {
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
if (g.getVisibility() == SHARED &&
g.getAffectedContacts().contains(contactId)) {
LOG.info("Group shared");
onDataToSend();
}
} else if (e instanceof ContactConnectedEvent) {
ContactConnectedEvent c = (ContactConnectedEvent) e;
if (c.getContactId().equals(contactId)) {
LOG.info("Contact connected");
onContactConnected();
}
} else if (e instanceof ContactDisconnectedEvent) {
ContactDisconnectedEvent c = (ContactDisconnectedEvent) e;
if (c.getContactId().equals(contactId)) {
LOG.info("Contact disconnected");
onContactDisconnected();
}
}
}
@EventExecutor
private void onDataToSend() {
Cancellable wakeupTask;
synchronized (lock) {
if (state != State.WAITING_FOR_DATA) return;
state = State.CHECKING_FOR_DATA;
wakeupTask = this.wakeupTask;
this.wakeupTask = null;
// Delay the check to avoid creating lots of small files
checkTask = taskScheduler.schedule(this::checkForDataToSend,
ioExecutor, CHECK_DELAY_MS, MILLISECONDS);
}
// If we had scheduled a wakeup when data was due to be sent, cancel it
if (wakeupTask != null) wakeupTask.cancel();
}
@EventExecutor
private void onContactConnected() {
Cancellable wakeupTask = null, checkTask = null;
synchronized (lock) {
if (state == State.DESTROYED) return;
// If we're checking for data to send, waiting for data to send,
// or checking connectivity then wait until we disconnect from
// the contact before proceeding. If we're writing or uploading
// a file then continue
if (state == State.CHECKING_FOR_DATA ||
state == State.WAITING_FOR_DATA ||
state == State.CONNECTIVITY_CHECK) {
state = State.CONNECTED_TO_CONTACT;
wakeupTask = this.wakeupTask;
this.wakeupTask = null;
checkTask = this.checkTask;
this.checkTask = null;
}
}
if (wakeupTask != null) wakeupTask.cancel();
if (checkTask != null) checkTask.cancel();
}
@EventExecutor
private void onContactDisconnected() {
synchronized (lock) {
if (state != State.CONNECTED_TO_CONTACT) return;
state = State.CHECKING_FOR_DATA;
}
ioExecutor.execute(this::checkForDataToSend);
}
}

View File

@@ -1,23 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.ThreadSafe;
/**
* A worker that downloads files from a contact's mailbox.
*/
@ThreadSafe
@NotNullByDefault
interface MailboxWorker {
/**
* Asynchronously starts the worker.
*/
void start();
/**
* Destroys the worker and cancels any pending tasks or retries.
*/
void destroy();
}

View File

@@ -1,31 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
interface MailboxWorkerFactory {
MailboxWorker createUploadWorker(ConnectivityChecker connectivityChecker,
MailboxProperties properties, MailboxFolderId folderId,
ContactId contactId);
MailboxWorker createDownloadWorkerForContactMailbox(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties);
MailboxWorker createDownloadWorkerForOwnMailbox(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties);
MailboxWorker createContactListWorkerForOwnMailbox(
ConnectivityChecker connectivityChecker,
MailboxProperties properties);
}

View File

@@ -1,101 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class MailboxWorkerFactoryImpl implements MailboxWorkerFactory {
private final Executor ioExecutor;
private final DatabaseComponent db;
private final Clock clock;
private final TaskScheduler taskScheduler;
private final EventBus eventBus;
private final ConnectionRegistry connectionRegistry;
private final MailboxApiCaller mailboxApiCaller;
private final MailboxApi mailboxApi;
private final MailboxFileManager mailboxFileManager;
private final MailboxUpdateManager mailboxUpdateManager;
@Inject
MailboxWorkerFactoryImpl(@IoExecutor Executor ioExecutor,
DatabaseComponent db,
Clock clock,
TaskScheduler taskScheduler,
EventBus eventBus,
ConnectionRegistry connectionRegistry,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager,
MailboxUpdateManager mailboxUpdateManager) {
this.ioExecutor = ioExecutor;
this.db = db;
this.clock = clock;
this.taskScheduler = taskScheduler;
this.eventBus = eventBus;
this.connectionRegistry = connectionRegistry;
this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi;
this.mailboxFileManager = mailboxFileManager;
this.mailboxUpdateManager = mailboxUpdateManager;
}
@Override
public MailboxWorker createUploadWorker(
ConnectivityChecker connectivityChecker,
MailboxProperties properties, MailboxFolderId folderId,
ContactId contactId) {
MailboxUploadWorker worker = new MailboxUploadWorker(ioExecutor, db,
clock, taskScheduler, eventBus, connectionRegistry,
connectivityChecker, mailboxApiCaller, mailboxApi,
mailboxFileManager, properties, folderId, contactId);
eventBus.addListener(worker);
return worker;
}
@Override
public MailboxWorker createDownloadWorkerForContactMailbox(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties) {
return new ContactMailboxDownloadWorker(connectivityChecker,
reachabilityMonitor, mailboxApiCaller, mailboxApi,
mailboxFileManager, properties);
}
@Override
public MailboxWorker createDownloadWorkerForOwnMailbox(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties) {
return new OwnMailboxDownloadWorker(connectivityChecker,
reachabilityMonitor, mailboxApiCaller, mailboxApi,
mailboxFileManager, properties);
}
@Override
public MailboxWorker createContactListWorkerForOwnMailbox(
ConnectivityChecker connectivityChecker,
MailboxProperties properties) {
OwnMailboxContactListWorker worker = new OwnMailboxContactListWorker(
ioExecutor, db, eventBus, connectivityChecker, mailboxApiCaller,
mailboxApi, mailboxUpdateManager, properties);
eventBus.addListener(worker);
return worker;
}
}

View File

@@ -1,222 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Logger.getLogger;
@ThreadSafe
@NotNullByDefault
class OwnMailboxClient implements MailboxClient, ConnectivityObserver {
/**
* How often to check our own mailbox's connectivity.
* <p>
* Package access for testing.
*/
static final long CONNECTIVITY_CHECK_INTERVAL_MS = HOURS.toMillis(1);
private static final Logger LOG =
getLogger(OwnMailboxClient.class.getName());
private final MailboxWorkerFactory workerFactory;
private final ConnectivityChecker connectivityChecker;
private final TorReachabilityMonitor reachabilityMonitor;
private final TaskScheduler taskScheduler;
private final Executor ioExecutor;
private final MailboxProperties properties;
private final MailboxWorker contactListWorker;
private final Object lock = new Object();
/**
* Upload workers: one worker per contact assigned for upload.
*/
@GuardedBy("lock")
private final Map<ContactId, MailboxWorker> uploadWorkers = new HashMap<>();
/**
* Download worker: shared between all contacts assigned for download.
* Null if no contacts are assigned for download.
*/
@GuardedBy("lock")
@Nullable
private MailboxWorker downloadWorker = null;
/**
* IDs of contacts assigned for download, so that we know when to
* create/destroy the download worker.
*/
@GuardedBy("lock")
private final Set<ContactId> assignedForDownload = new HashSet<>();
/**
* Scheduled task for periodically checking whether the mailbox is
* reachable.
*/
@GuardedBy("lock")
@Nullable
private Cancellable connectivityTask = null;
@GuardedBy("lock")
private boolean destroyed = false;
OwnMailboxClient(MailboxWorkerFactory workerFactory,
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor,
TaskScheduler taskScheduler,
@IoExecutor Executor ioExecutor,
MailboxProperties properties) {
if (!properties.isOwner()) throw new IllegalArgumentException();
this.workerFactory = workerFactory;
this.connectivityChecker = connectivityChecker;
this.reachabilityMonitor = reachabilityMonitor;
this.taskScheduler = taskScheduler;
this.ioExecutor = ioExecutor;
this.properties = properties;
contactListWorker = workerFactory.createContactListWorkerForOwnMailbox(
connectivityChecker, properties);
}
@Override
public void start() {
LOG.info("Started");
checkConnectivity();
contactListWorker.start();
}
@Override
public void destroy() {
LOG.info("Destroyed");
List<MailboxWorker> uploadWorkers;
MailboxWorker downloadWorker;
Cancellable connectivityTask;
synchronized (lock) {
uploadWorkers = new ArrayList<>(this.uploadWorkers.values());
this.uploadWorkers.clear();
downloadWorker = this.downloadWorker;
this.downloadWorker = null;
connectivityTask = this.connectivityTask;
this.connectivityTask = null;
destroyed = true;
}
// Destroy the workers (with apologies to Mr Marx and Mr Engels)
for (MailboxWorker worker : uploadWorkers) worker.destroy();
if (downloadWorker != null) downloadWorker.destroy();
// If a connectivity check is scheduled, cancel it
if (connectivityTask != null) connectivityTask.cancel();
contactListWorker.destroy();
// The connectivity checker belongs to the client, so it should be
// destroyed. The Tor reachability monitor is shared between clients,
// so it should not be destroyed
connectivityChecker.destroy();
}
@Override
public void assignContactForUpload(ContactId contactId,
MailboxProperties properties, MailboxFolderId folderId) {
LOG.info("Contact assigned for upload");
if (!properties.isOwner()) throw new IllegalArgumentException();
MailboxWorker uploadWorker = workerFactory.createUploadWorker(
connectivityChecker, properties, folderId, contactId);
synchronized (lock) {
MailboxWorker old = uploadWorkers.put(contactId, uploadWorker);
if (old != null) throw new IllegalStateException();
}
uploadWorker.start();
}
@Override
public void deassignContactForUpload(ContactId contactId) {
LOG.info("Contact deassigned for upload");
MailboxWorker uploadWorker;
synchronized (lock) {
uploadWorker = uploadWorkers.remove(contactId);
}
if (uploadWorker != null) uploadWorker.destroy();
}
@Override
public void assignContactForDownload(ContactId contactId,
MailboxProperties properties, MailboxFolderId folderId) {
LOG.info("Contact assigned for download");
if (!properties.isOwner()) throw new IllegalArgumentException();
// Create a download worker if we don't already have one. The worker
// will use the API to discover which folders have files to download,
// so it doesn't need to track the set of assigned contacts
MailboxWorker toStart = null;
synchronized (lock) {
if (!assignedForDownload.add(contactId)) {
throw new IllegalStateException();
}
if (downloadWorker == null) {
toStart = workerFactory.createDownloadWorkerForOwnMailbox(
connectivityChecker, reachabilityMonitor, properties);
downloadWorker = toStart;
}
}
if (toStart != null) toStart.start();
}
@Override
public void deassignContactForDownload(ContactId contactId) {
LOG.info("Contact deassigned for download");
// If there are no more contacts assigned for download, destroy the
// download worker
MailboxWorker toDestroy = null;
synchronized (lock) {
if (!assignedForDownload.remove(contactId)) {
throw new IllegalStateException();
}
if (assignedForDownload.isEmpty()) {
toDestroy = downloadWorker;
downloadWorker = null;
}
}
if (toDestroy != null) toDestroy.destroy();
}
private void checkConnectivity() {
synchronized (lock) {
if (destroyed) return;
connectivityTask = null;
}
connectivityChecker.checkConnectivity(properties, this);
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
boolean removeObserver;
synchronized (lock) {
removeObserver = destroyed;
}
if (removeObserver) connectivityChecker.removeObserver(this);
}
@Override
public void onConnectivityCheckSucceeded() {
synchronized (lock) {
if (destroyed) return;
connectivityTask = taskScheduler.schedule(this::checkConnectivity,
ioExecutor, CONNECTIVITY_CHECK_INTERVAL_MS, MILLISECONDS);
}
}
}

View File

@@ -4,17 +4,14 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException; import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
@@ -31,7 +28,6 @@ class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
private final TransactionManager db; private final TransactionManager db;
private final MailboxSettingsManager mailboxSettingsManager; private final MailboxSettingsManager mailboxSettingsManager;
@Inject
OwnMailboxConnectivityChecker(Clock clock, OwnMailboxConnectivityChecker(Clock clock,
MailboxApiCaller mailboxApiCaller, MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi, MailboxApi mailboxApi,
@@ -59,13 +55,11 @@ class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
private boolean checkConnectivityAndStoreResult( private boolean checkConnectivityAndStoreResult(
MailboxProperties properties) throws DbException { MailboxProperties properties) throws DbException {
try { try {
LOG.info("Checking whether own mailbox is reachable"); if (!mailboxApi.checkStatus(properties)) throw new ApiException();
List<MailboxVersion> serverSupports =
mailboxApi.getServerSupports(properties);
LOG.info("Own mailbox is reachable"); LOG.info("Own mailbox is reachable");
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
db.transaction(false, txn -> mailboxSettingsManager db.transaction(false, txn -> mailboxSettingsManager
.recordSuccessfulConnection(txn, now, serverSupports)); .recordSuccessfulConnection(txn, now));
// Call the observers and cache the result // Call the observers and cache the result
onConnectivityCheckSucceeded(now); onConnectivityCheckSucceeded(now);
return false; // Don't retry return false; // Don't retry

View File

@@ -1,369 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class OwnMailboxContactListWorker
implements MailboxWorker, ConnectivityObserver, EventListener {
/**
* When the worker is started it waits for a connectivity check, then
* fetches the remote contact list and compares it to the local contact
* list.
* <p>
* Any contacts that are missing from the remote list are added to the
* mailbox's contact list, while any contacts that are missing from the
* local list are removed from the mailbox's contact list.
* <p>
* Once the remote contact list has been brought up to date, the worker
* waits for events indicating that contacts have been added or removed.
* Each time an event is received, the worker updates the mailbox's
* contact list and then goes back to waiting.
*/
private enum State {
CREATED,
CONNECTIVITY_CHECK,
FETCHING_CONTACT_LIST,
UPDATING_CONTACT_LIST,
WAITING_FOR_CHANGES,
DESTROYED
}
private static final Logger LOG =
getLogger(OwnMailboxContactListWorker.class.getName());
private final Executor ioExecutor;
private final DatabaseComponent db;
private final EventBus eventBus;
private final ConnectivityChecker connectivityChecker;
private final MailboxApiCaller mailboxApiCaller;
private final MailboxApi mailboxApi;
private final MailboxUpdateManager mailboxUpdateManager;
private final MailboxProperties mailboxProperties;
private final Object lock = new Object();
@GuardedBy("lock")
private State state = State.CREATED;
@GuardedBy("lock")
@Nullable
private Cancellable apiCall = null;
/**
* A queue of updates waiting to be applied to the remote contact list.
*/
@GuardedBy("lock")
private final Queue<Update> updates = new LinkedList<>();
OwnMailboxContactListWorker(@IoExecutor Executor ioExecutor,
DatabaseComponent db,
EventBus eventBus,
ConnectivityChecker connectivityChecker,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxUpdateManager mailboxUpdateManager,
MailboxProperties mailboxProperties) {
if (!mailboxProperties.isOwner()) throw new IllegalArgumentException();
this.ioExecutor = ioExecutor;
this.db = db;
this.connectivityChecker = connectivityChecker;
this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi;
this.mailboxUpdateManager = mailboxUpdateManager;
this.mailboxProperties = mailboxProperties;
this.eventBus = eventBus;
}
@Override
public void start() {
LOG.info("Started");
synchronized (lock) {
if (state != State.CREATED) return;
state = State.CONNECTIVITY_CHECK;
}
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
connectivityChecker.checkConnectivity(mailboxProperties, this);
boolean destroyed;
synchronized (lock) {
destroyed = state == State.DESTROYED;
}
if (destroyed) connectivityChecker.removeObserver(this);
}
@Override
public void destroy() {
LOG.info("Destroyed");
Cancellable apiCall;
synchronized (lock) {
state = State.DESTROYED;
apiCall = this.apiCall;
this.apiCall = null;
}
if (apiCall != null) apiCall.cancel();
connectivityChecker.removeObserver(this);
eventBus.removeListener(this);
}
@Override
public void onConnectivityCheckSucceeded() {
LOG.info("Connectivity check succeeded");
synchronized (lock) {
if (state != State.CONNECTIVITY_CHECK) return;
state = State.FETCHING_CONTACT_LIST;
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(this::apiCallFetchContactList));
}
}
@IoExecutor
private void apiCallFetchContactList() throws IOException, ApiException {
synchronized (lock) {
if (state != State.FETCHING_CONTACT_LIST) return;
}
LOG.info("Fetching remote contact list");
Collection<ContactId> remote =
mailboxApi.getContacts(mailboxProperties);
ioExecutor.execute(() -> loadLocalContactList(remote));
}
@IoExecutor
private void loadLocalContactList(Collection<ContactId> remote) {
synchronized (lock) {
if (state != State.FETCHING_CONTACT_LIST) return;
apiCall = null;
}
LOG.info("Loading local contact list");
try {
db.transaction(true, txn -> {
Collection<Contact> local = db.getContacts(txn);
// Handle the result on the event executor to avoid races with
// incoming events
txn.attach(() -> reconcileContactLists(local, remote));
});
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
@EventExecutor
private void reconcileContactLists(Collection<Contact> local,
Collection<ContactId> remote) {
Set<ContactId> localIds = new HashSet<>();
for (Contact c : local) localIds.add(c.getId());
remote = new HashSet<>(remote);
synchronized (lock) {
if (state != State.FETCHING_CONTACT_LIST) return;
for (ContactId c : localIds) {
if (!remote.contains(c)) updates.add(new Update(true, c));
}
for (ContactId c : remote) {
if (!localIds.contains(c)) updates.add(new Update(false, c));
}
if (updates.isEmpty()) {
LOG.info("Contact list is up to date");
state = State.WAITING_FOR_CHANGES;
} else {
if (LOG.isLoggable(INFO)) {
LOG.info(updates.size() + " updates to apply");
}
state = State.UPDATING_CONTACT_LIST;
ioExecutor.execute(this::updateContactList);
}
}
}
@IoExecutor
private void updateContactList() {
Update update;
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
update = updates.poll();
if (update == null) {
LOG.info("No more updates to process");
state = State.WAITING_FOR_CHANGES;
apiCall = null;
return;
}
}
if (update.add) loadMailboxProperties(update.contactId);
else removeContact(update.contactId);
}
@IoExecutor
private void loadMailboxProperties(ContactId c) {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
}
LOG.info("Loading mailbox properties for contact");
try {
MailboxUpdate mailboxUpdate = db.transactionWithResult(true, txn ->
mailboxUpdateManager.getLocalUpdate(txn, c));
if (mailboxUpdate instanceof MailboxUpdateWithMailbox) {
addContact(c, (MailboxUpdateWithMailbox) mailboxUpdate);
} else {
// Our own mailbox was concurrently unpaired. This worker will
// be destroyed soon, so we can stop here
LOG.info("Own mailbox was unpaired");
}
} catch (NoSuchContactException e) {
// Contact was removed concurrently. Move on to the next update.
// Later we may process a removal update for this contact, which
// was never added to the mailbox's contact list. The removal API
// call should fail safely with a TolerableFailureException
LOG.info("No such contact");
updateContactList();
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
@IoExecutor
private void addContact(ContactId c, MailboxUpdateWithMailbox withMailbox) {
MailboxProperties props = withMailbox.getMailboxProperties();
MailboxContact contact = new MailboxContact(c, props.getAuthToken(),
requireNonNull(props.getInboxId()),
requireNonNull(props.getOutboxId()));
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
apiCallAddContact(contact)));
}
}
@IoExecutor
private void apiCallAddContact(MailboxContact contact)
throws IOException, ApiException, TolerableFailureException {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
}
LOG.info("Adding contact to remote contact list");
mailboxApi.addContact(mailboxProperties, contact);
updateContactList();
}
@IoExecutor
private void removeContact(ContactId c) {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
apiCallRemoveContact(c)));
}
}
@IoExecutor
private void apiCallRemoveContact(ContactId c)
throws IOException, ApiException {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
}
LOG.info("Removing contact from remote contact list");
try {
mailboxApi.deleteContact(mailboxProperties, c);
} catch (TolerableFailureException e) {
// Catch this so we can continue to the next update
LOG.warning("Contact does not exist");
}
updateContactList();
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactAddedEvent) {
LOG.info("Contact added");
onContactAdded(((ContactAddedEvent) e).getContactId());
} else if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed");
onContactRemoved(((ContactRemovedEvent) e).getContactId());
}
}
@EventExecutor
private void onContactAdded(ContactId c) {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST &&
state != State.WAITING_FOR_CHANGES) {
return;
}
updates.add(new Update(true, c));
if (state == State.WAITING_FOR_CHANGES) {
state = State.UPDATING_CONTACT_LIST;
ioExecutor.execute(this::updateContactList);
}
}
}
@EventExecutor
private void onContactRemoved(ContactId c) {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST &&
state != State.WAITING_FOR_CHANGES) {
return;
}
updates.add(new Update(false, c));
if (state == State.WAITING_FOR_CHANGES) {
state = State.UPDATING_CONTACT_LIST;
ioExecutor.execute(this::updateContactList);
}
}
}
/**
* An update that should be applied to the remote contact list.
*/
private static class Update {
/**
* True if the contact should be added, false if the contact should be
* removed.
*/
private final boolean add;
private final ContactId contactId;
private Update(boolean add, ContactId contactId) {
this.add = add;
this.contactId = contactId;
}
}
}

View File

@@ -1,148 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.Collections.shuffle;
import static java.util.logging.Level.INFO;
@ThreadSafe
@NotNullByDefault
class OwnMailboxDownloadWorker extends MailboxDownloadWorker {
/**
* The maximum number of files that will be downloaded before checking
* again for folders with available files. This ensures that if a file
* arrives during a download cycle, its folder will be checked within a
* reasonable amount of time even if some other folder has a very large
* number of files.
* <p>
* Package access for testing.
*/
static final int MAX_ROUND_ROBIN_FILES = 1000;
OwnMailboxDownloadWorker(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor torReachabilityMonitor,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager,
MailboxProperties mailboxProperties) {
super(connectivityChecker, torReachabilityMonitor, mailboxApiCaller,
mailboxApi, mailboxFileManager, mailboxProperties);
if (!mailboxProperties.isOwner()) throw new IllegalArgumentException();
}
@Override
protected ApiCall createApiCallForDownloadCycle() {
return new SimpleApiCall(this::apiCallListFolders);
}
private void apiCallListFolders() throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
LOG.info("Listing folders with available files");
List<MailboxFolderId> folders =
mailboxApi.getFolders(mailboxProperties);
if (folders.isEmpty()) onDownloadCycleFinished();
else listNextFolder(new LinkedList<>(folders), new HashMap<>());
}
/**
* Removes the next folder from `queue` and starts a task to list the
* files in the folder and add them to `available`.
*/
private void listNextFolder(Queue<MailboxFolderId> queue,
Map<MailboxFolderId, Queue<MailboxFile>> available) {
synchronized (lock) {
if (state == State.DESTROYED) return;
MailboxFolderId folder = queue.remove();
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
apiCallListFolder(folder, queue, available)));
}
}
private void apiCallListFolder(MailboxFolderId folder,
Queue<MailboxFolderId> queue,
Map<MailboxFolderId, Queue<MailboxFile>> available)
throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
LOG.info("Listing folder");
try {
List<MailboxFile> files =
mailboxApi.getFiles(mailboxProperties, folder);
if (!files.isEmpty()) {
available.put(folder, new LinkedList<>(files));
}
} catch (TolerableFailureException e) {
LOG.warning("Folder does not exist");
}
if (queue.isEmpty()) {
LOG.info("Finished listing folders");
if (available.isEmpty()) onDownloadCycleFinished();
else createDownloadQueue(available);
} else {
listNextFolder(queue, available);
}
}
/**
* Visits the given folders in round-robin order to create a queue of up to
* {@link #MAX_ROUND_ROBIN_FILES} to download.
*/
private void createDownloadQueue(
Map<MailboxFolderId, Queue<MailboxFile>> available) {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
if (LOG.isLoggable(INFO)) {
LOG.info(available.size() + " folders have available files");
}
Queue<FolderFile> queue = createRoundRobinQueue(available);
if (LOG.isLoggable(INFO)) {
LOG.info("Downloading " + queue.size() + " files");
}
downloadNextFile(queue);
}
// Package access for testing
Queue<FolderFile> createRoundRobinQueue(
Map<MailboxFolderId, Queue<MailboxFile>> available) {
List<MailboxFolderId> roundRobin = new ArrayList<>(available.keySet());
// Shuffle the folders so we don't always favour the same folders
shuffle(roundRobin);
Queue<FolderFile> queue = new LinkedList<>();
while (queue.size() < MAX_ROUND_ROBIN_FILES && !available.isEmpty()) {
Iterator<MailboxFolderId> it = roundRobin.iterator();
while (queue.size() < MAX_ROUND_ROBIN_FILES && it.hasNext()) {
MailboxFolderId folder = it.next();
Queue<MailboxFile> files = available.get(folder);
MailboxFile file = files.remove();
queue.add(new FolderFile(folder, file.name));
if (files.isEmpty()) {
available.remove(folder);
it.remove();
}
}
}
return queue;
}
}

View File

@@ -16,20 +16,17 @@ import static org.briarproject.bramble.util.LogUtils.logException;
* Convenience class for making simple API calls that don't return values. * Convenience class for making simple API calls that don't return values.
*/ */
@NotNullByDefault @NotNullByDefault
class SimpleApiCall implements ApiCall { public abstract class SimpleApiCall implements ApiCall {
private static final Logger LOG = getLogger(SimpleApiCall.class.getName()); private static final Logger LOG = getLogger(SimpleApiCall.class.getName());
private final Attempt attempt; abstract void tryToCallApi()
throws IOException, ApiException, TolerableFailureException;
SimpleApiCall(Attempt attempt) {
this.attempt = attempt;
}
@Override @Override
public boolean callApi() { public boolean callApi() {
try { try {
attempt.tryToCallApi(); tryToCallApi();
return false; // Succeeded, don't retry return false; // Succeeded, don't retry
} catch (IOException | ApiException e) { } catch (IOException | ApiException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
@@ -39,17 +36,4 @@ class SimpleApiCall implements ApiCall {
return false; // Failed tolerably, don't retry return false; // Failed tolerably, don't retry
} }
} }
interface Attempt {
/**
* Makes a single attempt to call an API endpoint. If this method
* throws an {@link IOException} or an {@link ApiException}, the call
* will be retried. If it throws a {@link TolerableFailureException}
* or returns without throwing an exception, the call will not be
* retried.
*/
void tryToCallApi()
throws IOException, ApiException, TolerableFailureException;
}
} }

View File

@@ -38,12 +38,6 @@ interface TorReachabilityMonitor {
*/ */
void addOneShotObserver(TorReachabilityObserver o); void addOneShotObserver(TorReachabilityObserver o);
/**
* Removes an observer that was added via
* {@link #addOneShotObserver(TorReachabilityObserver)}.
*/
void removeObserver(TorReachabilityObserver o);
interface TorReachabilityObserver { interface TorReachabilityObserver {
void onTorReachable(); void onTorReachable();

View File

@@ -87,14 +87,6 @@ class TorReachabilityMonitorImpl
if (callNow) o.onTorReachable(); if (callNow) o.onTorReachable();
} }
@Override
public void removeObserver(TorReachabilityObserver o) {
synchronized (lock) {
if (destroyed) return;
observers.remove(o);
}
}
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof TransportActiveEvent) { if (e instanceof TransportActiveEvent) {

View File

@@ -1,17 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/**
* An interface for converting an onion address to an HTTP URL, allowing the
* conversion to be customised for integration tests.
*/
@NotNullByDefault
interface UrlConverter {
/**
* Converts a raw onion address, excluding the .onion suffix, into an
* HTTP URL.
*/
String convertOnionToBaseUrl(String onion);
}

View File

@@ -1,18 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.inject.Inject;
@NotNullByDefault
class UrlConverterImpl implements UrlConverter {
@Inject
UrlConverterImpl() {
}
@Override
public String convertOnionToBaseUrl(String onion) {
return "http://" + onion + ".onion";
}
}

View File

@@ -427,13 +427,7 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
BdfList descriptor = new BdfList(); BdfList descriptor = new BdfList();
descriptor.add(TRANSPORT_ID_BLUETOOTH); descriptor.add(TRANSPORT_ID_BLUETOOTH);
String address = getBluetoothAddress(); String address = getBluetoothAddress();
if (address != null) { if (address != null) descriptor.add(macToBytes(address));
try {
descriptor.add(macToBytes(address));
} catch (FormatException e) {
throw new RuntimeException();
}
}
return new BluetoothKeyAgreementListener(descriptor, ss); return new BluetoothKeyAgreementListener(descriptor, ss);
} }

View File

@@ -9,12 +9,14 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID; import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_LATENCY;
@NotNullByDefault @NotNullByDefault
public class MailboxPluginFactory implements SimplexPluginFactory { public class MailboxPluginFactory implements SimplexPluginFactory {
private static final long MAX_LATENCY = DAYS.toMillis(14);
@Inject @Inject
MailboxPluginFactory() { MailboxPluginFactory() {
} }

View File

@@ -106,8 +106,7 @@ class RemovableDriveManagerImpl
@Override @Override
public boolean isWriterTaskNeeded(ContactId c) throws DbException { public boolean isWriterTaskNeeded(ContactId c) throws DbException {
return db.transactionWithResult(true, txn -> return db.transactionWithResult(true, txn ->
db.containsAcksToSend(txn, c) || db.containsAnythingToSend(txn, c, MAX_LATENCY, true));
db.containsMessagesToSend(txn, c, MAX_LATENCY, true));
} }
@Override @Override

View File

@@ -285,7 +285,7 @@ class LanTcpPlugin extends TcpPlugin {
if (ip.length == 16) addrs.add(InetAddress.getByAddress(ip)); if (ip.length == 16) addrs.add(InetAddress.getByAddress(ip));
} }
return addrs; return addrs;
} catch (FormatException | UnknownHostException e) { } catch (IllegalArgumentException | UnknownHostException e) {
return emptyList(); return emptyList();
} }
} }

View File

@@ -27,7 +27,7 @@ public interface CircumventionProvider {
* Countries where bridge connections are likely to work. * Countries where bridge connections are likely to work.
* Should be a subset of {@link #BLOCKED} and the union of * Should be a subset of {@link #BLOCKED} and the union of
* {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and * {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and
* {@link #DPI_BRIDGES}. * {@link #MEEK_BRIDGES}.
*/ */
String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"}; String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
@@ -44,10 +44,10 @@ public interface CircumventionProvider {
String[] NON_DEFAULT_BRIDGES = {"BY", "RU", "TM"}; String[] NON_DEFAULT_BRIDGES = {"BY", "RU", "TM"};
/** /**
* Countries where vanilla bridges are blocked via DPI but non-default * Countries where obfs4 and vanilla bridges won't work and meek is needed.
* obfs4 bridges and meek may work. Should be a subset of {@link #BRIDGES}. * Should be a subset of {@link #BRIDGES}.
*/ */
String[] DPI_BRIDGES = {"CN", "IR"}; String[] MEEK_BRIDGES = {"CN", "IR"};
/** /**
* Returns true if vanilla Tor connections are blocked in the given country. * Returns true if vanilla Tor connections are blocked in the given country.

View File

@@ -14,6 +14,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
@@ -34,8 +35,8 @@ class CircumventionProviderImpl implements CircumventionProvider {
new HashSet<>(asList(DEFAULT_BRIDGES)); new HashSet<>(asList(DEFAULT_BRIDGES));
private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES = private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES =
new HashSet<>(asList(NON_DEFAULT_BRIDGES)); new HashSet<>(asList(NON_DEFAULT_BRIDGES));
private static final Set<String> DPI_COUNTRIES = private static final Set<String> MEEK_COUNTRIES =
new HashSet<>(asList(DPI_BRIDGES)); new HashSet<>(asList(MEEK_BRIDGES));
@Inject @Inject
CircumventionProviderImpl() { CircumventionProviderImpl() {
@@ -57,8 +58,8 @@ class CircumventionProviderImpl implements CircumventionProvider {
return asList(DEFAULT_OBFS4, VANILLA); return asList(DEFAULT_OBFS4, VANILLA);
} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) { } else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
return asList(NON_DEFAULT_OBFS4, VANILLA); return asList(NON_DEFAULT_OBFS4, VANILLA);
} else if (DPI_COUNTRIES.contains(countryCode)) { } else if (MEEK_COUNTRIES.contains(countryCode)) {
return asList(NON_DEFAULT_OBFS4, MEEK); return singletonList(MEEK);
} else { } else {
return asList(DEFAULT_OBFS4, VANILLA); return asList(DEFAULT_OBFS4, VANILLA);
} }

View File

@@ -65,7 +65,6 @@ import javax.annotation.concurrent.ThreadSafe;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
@@ -94,7 +93,9 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY; import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED; import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA; import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES; import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
import static org.briarproject.bramble.util.IoUtils.copyAndClose; import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
@@ -106,7 +107,7 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@ParametersNotNullByDefault @ParametersNotNullByDefault
abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
protected static final Logger LOG = getLogger(TorPlugin.class.getName()); private static final Logger LOG = getLogger(TorPlugin.class.getName());
private static final String[] EVENTS = { private static final String[] EVENTS = {
"CIRC", "CIRC",
@@ -123,8 +124,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final int COOKIE_POLLING_INTERVAL_MS = 200; private static final int COOKIE_POLLING_INTERVAL_MS = 200;
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}"); private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
protected final Executor ioExecutor; private final Executor ioExecutor, wakefulIoExecutor;
private final Executor wakefulIoExecutor;
private final Executor connectionStatusExecutor; private final Executor connectionStatusExecutor;
private final NetworkManager networkManager; private final NetworkManager networkManager;
private final LocationUtils locationUtils; private final LocationUtils locationUtils;
@@ -238,14 +238,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
// Load the settings // Load the settings
settings = callback.getSettings(); settings = callback.getSettings();
try { // Install or update the assets if necessary
// Install or update the assets if necessary if (!assetsAreUpToDate()) installAssets();
if (!assetsAreUpToDate()) installAssets();
// Start from the default config every time
extract(getConfigInputStream(), configFile);
} catch (IOException e) {
throw new PluginException(e);
}
if (cookieFile.exists() && !cookieFile.delete()) if (cookieFile.exists() && !cookieFile.delete())
LOG.warning("Old auth cookie not deleted"); LOG.warning("Old auth cookie not deleted");
// Start a new Tor process // Start a new Tor process
@@ -260,15 +254,34 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
Map<String, String> env = pb.environment(); Map<String, String> env = pb.environment();
env.put("HOME", torDirectory.getAbsolutePath()); env.put("HOME", torDirectory.getAbsolutePath());
pb.directory(torDirectory); pb.directory(torDirectory);
pb.redirectErrorStream(true);
try { try {
torProcess = pb.start(); torProcess = pb.start();
} catch (SecurityException | IOException e) { } catch (SecurityException | IOException e) {
throw new PluginException(e); throw new PluginException(e);
} }
// Log the process's standard output until it detaches
if (LOG.isLoggable(INFO)) {
Scanner stdout = new Scanner(torProcess.getInputStream());
Scanner stderr = new Scanner(torProcess.getErrorStream());
while (stdout.hasNextLine() || stderr.hasNextLine()) {
if (stdout.hasNextLine()) {
LOG.info(stdout.nextLine());
}
if (stderr.hasNextLine()) {
LOG.info(stderr.nextLine());
}
}
stdout.close();
stderr.close();
}
try { try {
// Wait for the Tor process to start // Wait for the process to detach or exit
waitForTorToStart(torProcess); int exit = torProcess.waitFor();
if (exit != 0) {
if (LOG.isLoggable(WARNING))
LOG.warning("Tor exited with value " + exit);
throw new PluginException();
}
// Wait for the auth cookie file to be created/updated // Wait for the auth cookie file to be created/updated
long start = clock.currentTimeMillis(); long start = clock.currentTimeMillis();
while (cookieFile.length() < 32) { while (cookieFile.length() < 32) {
@@ -307,7 +320,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
info = controlConnection.getInfo("status/circuit-established"); info = controlConnection.getInfo("status/circuit-established");
if ("1".equals(info)) { if ("1".equals(info)) {
LOG.info("Tor has already built a circuit"); LOG.info("Tor has already built a circuit");
state.setCircuitBuilt(true); state.getAndSetCircuitBuilt(true);
} }
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@@ -326,20 +339,25 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return doneFile.lastModified() > getLastUpdateTime(); return doneFile.lastModified() > getLastUpdateTime();
} }
private void installAssets() throws IOException { private void installAssets() throws PluginException {
// The done file may already exist from a previous installation try {
//noinspection ResultOfMethodCallIgnored // The done file may already exist from a previous installation
doneFile.delete(); //noinspection ResultOfMethodCallIgnored
// The GeoIP file may exist from a previous installation - we can doneFile.delete();
// save some space by deleting it. // The GeoIP file may exist from a previous installation - we can
// TODO: Remove after a reasonable migration period // save some space by deleting it.
// (added 2022-03-29) // TODO: Remove after a reasonable migration period
//noinspection ResultOfMethodCallIgnored // (added 2022-03-29)
geoIpFile.delete(); //noinspection ResultOfMethodCallIgnored
installTorExecutable(); geoIpFile.delete();
installObfs4Executable(); installTorExecutable();
if (!doneFile.createNewFile()) installObfs4Executable();
LOG.warning("Failed to create done file"); extract(getConfigInputStream(), configFile);
if (!doneFile.createNewFile())
LOG.warning("Failed to create done file");
} catch (IOException e) {
throw new PluginException(e);
}
} }
protected void extract(InputStream in, File dest) throws IOException { protected void extract(InputStream in, File dest) throws IOException {
@@ -379,7 +397,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return zin; return zin;
} }
private static void append(StringBuilder strb, String name, Object value) { private static void append(StringBuilder strb, String name, int value) {
strb.append(name); strb.append(name);
strb.append(" "); strb.append(" ");
strb.append(value); strb.append(value);
@@ -387,21 +405,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private InputStream getConfigInputStream() { private InputStream getConfigInputStream() {
File dataDirectory = new File(torDirectory, ".tor");
StringBuilder strb = new StringBuilder(); StringBuilder strb = new StringBuilder();
append(strb, "ControlPort", torControlPort); append(strb, "ControlPort", torControlPort);
append(strb, "CookieAuthentication", 1); append(strb, "CookieAuthentication", 1);
append(strb, "DataDirectory", dataDirectory.getAbsolutePath());
append(strb, "DisableNetwork", 1); append(strb, "DisableNetwork", 1);
append(strb, "RunAsDaemon", 1); append(strb, "RunAsDaemon", 1);
append(strb, "SafeSocks", 1); append(strb, "SafeSocks", 1);
append(strb, "SocksPort", torSocksPort); append(strb, "SocksPort", torSocksPort);
strb.append("GeoIPFile\n");
strb.append("GeoIPv6File\n");
append(strb, "ConnectionPadding", 0);
String obfs4Path = getObfs4ExecutableFile().getAbsolutePath();
append(strb, "ClientTransportPlugin obfs4 exec", obfs4Path);
append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path);
//noinspection CharsetObjectCanBeUsed //noinspection CharsetObjectCanBeUsed
return new ByteArrayInputStream( return new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("UTF-8"))); strb.toString().getBytes(Charset.forName("UTF-8")));
@@ -432,23 +442,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
protected void waitForTorToStart(Process torProcess)
throws InterruptedException, PluginException {
Scanner stdout = new Scanner(torProcess.getInputStream());
// Log the first line of stdout (contains Tor and library versions)
if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
// Read the process's stdout (and redirected stderr) until it detaches
while (stdout.hasNextLine()) stdout.nextLine();
stdout.close();
// Wait for the process to detach or exit
int exit = torProcess.waitFor();
if (exit != 0) {
if (LOG.isLoggable(WARNING))
LOG.warning("Tor exited with value " + exit);
throw new PluginException();
}
}
private void bind() { private void bind() {
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
// If there's already a port number stored in config, reuse it // If there's already a port number stored in config, reuse it
@@ -551,7 +544,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
protected void enableNetwork(boolean enable) throws IOException { protected void enableNetwork(boolean enable) throws IOException {
if (!state.enableNetwork(enable)) return; // Unchanged state.enableNetwork(enable);
try { try {
controlConnection.setConf("DisableNetwork", enable ? "0" : "1"); controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
@@ -559,20 +552,28 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
private void enableBridges(List<BridgeType> bridgeTypes) private void enableBridges(boolean enable, List<BridgeType> bridgeTypes)
throws IOException { throws IOException {
if (!state.setBridgeTypes(bridgeTypes)) return; // Unchanged
try { try {
if (bridgeTypes.isEmpty()) { if (enable) {
controlConnection.setConf("UseBridges", "0");
controlConnection.resetConf(singletonList("Bridge"));
} else {
Collection<String> conf = new ArrayList<>(); Collection<String> conf = new ArrayList<>();
conf.add("UseBridges 1"); conf.add("UseBridges 1");
File obfs4File = getObfs4ExecutableFile();
if (bridgeTypes.contains(MEEK)) {
conf.add("ClientTransportPlugin meek_lite exec " +
obfs4File.getAbsolutePath());
}
if (bridgeTypes.contains(DEFAULT_OBFS4) ||
bridgeTypes.contains(NON_DEFAULT_OBFS4)) {
conf.add("ClientTransportPlugin obfs4 exec " +
obfs4File.getAbsolutePath());
}
for (BridgeType bridgeType : bridgeTypes) { for (BridgeType bridgeType : bridgeTypes) {
conf.addAll(circumventionProvider.getBridges(bridgeType)); conf.addAll(circumventionProvider.getBridges(bridgeType));
} }
controlConnection.setConf(conf); controlConnection.setConf(conf);
} else {
controlConnection.setConf("UseBridges", "0");
} }
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@@ -586,6 +587,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (controlSocket != null && controlConnection != null) { if (controlSocket != null && controlConnection != null) {
try { try {
LOG.info("Stopping Tor"); LOG.info("Stopping Tor");
controlConnection.setConf("DisableNetwork", "1");
controlConnection.shutdownTor("TERM"); controlConnection.shutdownTor("TERM");
controlSocket.close(); controlSocket.close();
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
@@ -753,7 +755,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
public void circuitStatus(String status, String id, String path) { public void circuitStatus(String status, String id, String path) {
// In case of races between receiving CIRCUIT_ESTABLISHED and setting // In case of races between receiving CIRCUIT_ESTABLISHED and setting
// DisableNetwork, set our circuitBuilt flag if not already set // DisableNetwork, set our circuitBuilt flag if not already set
if (status.equals("BUILT") && state.setCircuitBuilt(true)) { if (status.equals("BUILT") && !state.getAndSetCircuitBuilt(true)) {
LOG.info("Circuit built"); LOG.info("Circuit built");
backoff.reset(); backoff.reset();
} }
@@ -800,13 +802,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
@Override
public void controlConnectionClosed() {
if (state.isTorRunning()) {
throw new RuntimeException("Control connection closed");
}
}
private String removeSeverity(String msg) { private String removeSeverity(String msg) {
return msg.replaceFirst("[^ ]+ ", ""); return msg.replaceFirst("[^ ]+ ", "");
} }
@@ -817,12 +812,12 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
state.setBootstrapped(); state.setBootstrapped();
backoff.reset(); backoff.reset();
} else if (msg.startsWith("CIRCUIT_ESTABLISHED")) { } else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
if (state.setCircuitBuilt(true)) { if (!state.getAndSetCircuitBuilt(true)) {
LOG.info("Circuit built"); LOG.info("Circuit built");
backoff.reset(); backoff.reset();
} }
} else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) { } else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
if (state.setCircuitBuilt(false)) { if (state.getAndSetCircuitBuilt(false)) {
LOG.info("Circuit not built"); LOG.info("Circuit not built");
// TODO: Disable and re-enable network to prompt Tor to rebuild // TODO: Disable and re-enable network to prompt Tor to rebuild
// its guard/bridge connections? This will also close any // its guard/bridge connections? This will also close any
@@ -913,8 +908,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
int reasonsDisabled = 0; int reasonsDisabled = 0;
boolean enableNetwork = false, enableConnectionPadding = false; boolean enableNetwork = false, enableBridges = false;
List<BridgeType> bridgeTypes = emptyList(); boolean enableConnectionPadding = false;
List<BridgeType> bridgeTypes =
circumventionProvider.getSuitableBridgeTypes(country);
if (!online) { if (!online) {
LOG.info("Disabling network, device is offline"); LOG.info("Disabling network, device is offline");
@@ -943,12 +940,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
enableNetwork = true; enableNetwork = true;
if (network == PREF_TOR_NETWORK_WITH_BRIDGES || if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
(automatic && bridgesWork)) { (automatic && bridgesWork)) {
if (ipv6Only) { if (ipv6Only) bridgeTypes = singletonList(MEEK);
bridgeTypes = singletonList(MEEK); enableBridges = true;
} else {
bridgeTypes = circumventionProvider
.getSuitableBridgeTypes(country);
}
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("Using bridge types " + bridgeTypes); LOG.info("Using bridge types " + bridgeTypes);
} }
@@ -968,9 +961,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
try { try {
if (enableNetwork) { if (enableNetwork) {
enableBridges(bridgeTypes); enableBridges(enableBridges, bridgeTypes);
enableConnectionPadding(enableConnectionPadding); enableConnectionPadding(enableConnectionPadding);
enableIpv6(ipv6Only); useIpv6(ipv6Only);
} }
enableNetwork(enableNetwork); enableNetwork(enableNetwork);
} catch (IOException e) { } catch (IOException e) {
@@ -980,7 +973,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void enableConnectionPadding(boolean enable) throws IOException { private void enableConnectionPadding(boolean enable) throws IOException {
if (!state.enableConnectionPadding(enable)) return; // Unchanged
try { try {
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0"); controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
@@ -988,11 +980,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
private void enableIpv6(boolean enable) throws IOException { private void useIpv6(boolean ipv6Only) throws IOException {
if (!state.enableIpv6(enable)) return; // Unchanged
try { try {
controlConnection.setConf("ClientUseIPv4", enable ? "0" : "1"); controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1");
controlConnection.setConf("ClientUseIPv6", enable ? "1" : "0"); controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0");
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -1007,8 +998,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
stopped = false, stopped = false,
networkInitialised = false, networkInitialised = false,
networkEnabled = false, networkEnabled = false,
paddingEnabled = false,
ipv6Enabled = false,
bootstrapped = false, bootstrapped = false,
circuitBuilt = false, circuitBuilt = false,
settingsChecked = false; settingsChecked = false;
@@ -1023,9 +1012,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@GuardedBy("this") @GuardedBy("this")
private int orConnectionsConnected = 0; private int orConnectionsConnected = 0;
@GuardedBy("this")
private List<BridgeType> bridgeTypes = emptyList();
private synchronized void setStarted() { private synchronized void setStarted() {
started = true; started = true;
callback.pluginStateChanged(getState()); callback.pluginStateChanged(getState());
@@ -1046,66 +1032,28 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private synchronized void setBootstrapped() { private synchronized void setBootstrapped() {
boolean wasBootstrapped = bootstrapped;
bootstrapped = true; bootstrapped = true;
if (!wasBootstrapped) callback.pluginStateChanged(getState());
}
/**
* Sets the `circuitBuilt` flag and returns true if the flag has
* changed.
*/
private synchronized boolean setCircuitBuilt(boolean built) {
if (built == circuitBuilt) return false; // Unchanged
circuitBuilt = built;
callback.pluginStateChanged(getState()); callback.pluginStateChanged(getState());
return true; // Changed
} }
/** private synchronized boolean getAndSetCircuitBuilt(boolean built) {
* Sets the `networkEnabled` flag and returns true if the flag has boolean old = circuitBuilt;
* changed. circuitBuilt = built;
*/ if (built != old) callback.pluginStateChanged(getState());
private synchronized boolean enableNetwork(boolean enable) { return old;
boolean wasInitialised = networkInitialised; }
boolean wasEnabled = networkEnabled;
private synchronized void enableNetwork(boolean enable) {
networkInitialised = true; networkInitialised = true;
networkEnabled = enable; networkEnabled = enable;
if (!enable) circuitBuilt = false; if (!enable) circuitBuilt = false;
if (!wasInitialised || enable != wasEnabled) { callback.pluginStateChanged(getState());
callback.pluginStateChanged(getState());
}
return enable != wasEnabled;
} }
/** private synchronized void setReasonsDisabled(int reasonsDisabled) {
* Sets the `paddingEnabled` flag and returns true if the flag has
* changed. Doesn't affect getState().
*/
private synchronized boolean enableConnectionPadding(boolean enable) {
if (enable == paddingEnabled) return false; // Unchanged
paddingEnabled = enable;
return true; // Changed
}
/**
* Sets the `ipv6Enabled` flag and returns true if the flag has
* changed. Doesn't affect getState().
*/
private synchronized boolean enableIpv6(boolean enable) {
if (enable == ipv6Enabled) return false; // Unchanged
ipv6Enabled = enable;
return true; // Changed
}
private synchronized void setReasonsDisabled(int reasons) {
boolean wasChecked = settingsChecked;
settingsChecked = true; settingsChecked = true;
int oldReasons = reasonsDisabled; this.reasonsDisabled = reasonsDisabled;
reasonsDisabled = reasons; callback.pluginStateChanged(getState());
if (!wasChecked || reasons != oldReasons) {
callback.pluginStateChanged(getState());
}
} }
// Doesn't affect getState() // Doesn't affect getState()
@@ -1120,17 +1068,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (serverSocket == ss) serverSocket = null; if (serverSocket == ss) serverSocket = null;
} }
/**
* Sets the list of bridge types being used and returns true if the
* list has changed. The list is empty if bridges are disabled.
* Doesn't affect getState().
*/
private synchronized boolean setBridgeTypes(List<BridgeType> types) {
if (types.equals(bridgeTypes)) return false; // Unchanged
bridgeTypes = types;
return true; // Changed
}
private synchronized State getState() { private synchronized State getState() {
if (!started || stopped || !settingsChecked) { if (!started || stopped || !settingsChecked) {
return STARTING_STOPPING; return STARTING_STOPPING;

View File

@@ -44,14 +44,12 @@ import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import static java.lang.Boolean.TRUE;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES; import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS; import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS;
@@ -234,19 +232,11 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
ContactRemovedEvent c = (ContactRemovedEvent) e; ContactRemovedEvent c = (ContactRemovedEvent) e;
if (c.getContactId().equals(contactId)) interrupt(); if (c.getContactId().equals(contactId)) interrupt();
} else if (e instanceof MessageSharedEvent) { } else if (e instanceof MessageSharedEvent) {
MessageSharedEvent m = (MessageSharedEvent) e; generateOffer();
// If the contact is present in the map (ie the value is not null)
// and the value is true, the message's group is shared with the
// contact and therefore the message may now be sendable
if (m.getGroupVisibility().get(contactId) == TRUE) {
generateOffer();
}
} else if (e instanceof GroupVisibilityUpdatedEvent) { } else if (e instanceof GroupVisibilityUpdatedEvent) {
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e; GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
if (g.getVisibility() == SHARED && if (g.getAffectedContacts().contains(contactId))
g.getAffectedContacts().contains(contactId)) {
generateOffer(); generateOffer();
}
} else if (e instanceof MessageRequestedEvent) { } else if (e instanceof MessageRequestedEvent) {
if (((MessageRequestedEvent) e).getContactId().equals(contactId)) if (((MessageRequestedEvent) e).getContactId().equals(contactId))
generateBatch(); generateBatch();
@@ -320,8 +310,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
Collection<Message> batch = Collection<Message> batch =
db.generateRequestedBatch(txn, contactId, db.generateRequestedBatch(txn, contactId,
BATCH_CAPACITY, maxLatency); BATCH_CAPACITY, maxLatency);
setNextSendTime(db.getNextSendTime(txn, contactId, setNextSendTime(db.getNextSendTime(txn, contactId));
maxLatency));
return batch; return batch;
}); });
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
@@ -364,8 +353,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
Offer o = db.transactionWithNullableResult(false, txn -> { Offer o = db.transactionWithNullableResult(false, txn -> {
Offer offer = db.generateOffer(txn, contactId, Offer offer = db.generateOffer(txn, contactId,
MAX_MESSAGE_IDS, maxLatency); MAX_MESSAGE_IDS, maxLatency);
setNextSendTime(db.getNextSendTime(txn, contactId, setNextSendTime(db.getNextSendTime(txn, contactId));
maxLatency));
return offer; return offer;
}); });
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))

View File

@@ -7,16 +7,14 @@ import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.DeferredSendHandler;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.SyncRecordWriter; import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.transport.StreamWriter; import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
@@ -31,7 +29,7 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LEN
/** /**
* A {@link SimplexOutgoingSession} for sending and acking messages via a * A {@link SimplexOutgoingSession} for sending and acking messages via a
* mailbox. The session uses a {@link OutgoingSessionRecord} to record the IDs * mailbox. The session uses a {@link DeferredSendHandler} to record the IDs
* of the messages sent and acked during the session so that they can be * of the messages sent and acked during the session so that they can be
* recorded in the DB as sent or acked after the file has been successfully * recorded in the DB as sent or acked after the file has been successfully
* uploaded to the mailbox. * uploaded to the mailbox.
@@ -43,7 +41,7 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
private static final Logger LOG = private static final Logger LOG =
getLogger(MailboxOutgoingSession.class.getName()); getLogger(MailboxOutgoingSession.class.getName());
private final OutgoingSessionRecord sessionRecord; private final DeferredSendHandler deferredSendHandler;
private final long initialCapacity; private final long initialCapacity;
MailboxOutgoingSession(DatabaseComponent db, MailboxOutgoingSession(DatabaseComponent db,
@@ -53,42 +51,36 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
long maxLatency, long maxLatency,
StreamWriter streamWriter, StreamWriter streamWriter,
SyncRecordWriter recordWriter, SyncRecordWriter recordWriter,
OutgoingSessionRecord sessionRecord, DeferredSendHandler deferredSendHandler,
long capacity) { long capacity) {
super(db, eventBus, contactId, transportId, maxLatency, streamWriter, super(db, eventBus, contactId, transportId, maxLatency, streamWriter,
recordWriter); recordWriter);
this.sessionRecord = sessionRecord; this.deferredSendHandler = deferredSendHandler;
this.initialCapacity = capacity; this.initialCapacity = capacity;
} }
@Override @Override
void sendAcks() throws DbException, IOException { void sendAcks() throws DbException, IOException {
List<MessageId> idsToAck = loadMessageIdsToAck(); while (!isInterrupted()) {
int idsSent = 0; Collection<MessageId> idsToAck = loadMessageIdsToAck();
while (idsSent < idsToAck.size() && !isInterrupted()) { if (idsToAck.isEmpty()) break;
int idsRemaining = idsToAck.size() - idsSent; recordWriter.writeAck(new Ack(idsToAck));
long capacity = getRemainingCapacity(); deferredSendHandler.onAckSent(idsToAck);
long idCapacity =
(capacity - RECORD_HEADER_BYTES) / MessageId.LENGTH;
if (idCapacity == 0) break; // Out of capacity
int idsInRecord = (int) min(idCapacity, MAX_MESSAGE_IDS);
int idsToSend = min(idsRemaining, idsInRecord);
List<MessageId> acked =
idsToAck.subList(idsSent, idsSent + idsToSend);
recordWriter.writeAck(new Ack(acked));
sessionRecord.onAckSent(acked);
LOG.info("Sent ack"); LOG.info("Sent ack");
idsSent += idsToSend;
} }
} }
private List<MessageId> loadMessageIdsToAck() throws DbException { private Collection<MessageId> loadMessageIdsToAck() throws DbException {
long idCapacity = (getRemainingCapacity() - RECORD_HEADER_BYTES)
/ MessageId.LENGTH;
if (idCapacity <= 0) return emptyList(); // Out of capacity
int maxMessageIds = (int) min(idCapacity, MAX_MESSAGE_IDS);
Collection<MessageId> ids = db.transactionWithResult(true, txn -> Collection<MessageId> ids = db.transactionWithResult(true, txn ->
db.getMessagesToAck(txn, contactId)); db.getMessagesToAck(txn, contactId, maxMessageIds));
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info(ids.size() + " messages to ack"); LOG.info(ids.size() + " messages to ack");
} }
return new ArrayList<>(ids); return ids;
} }
private long getRemainingCapacity() { private long getRemainingCapacity() {
@@ -104,7 +96,7 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
db.getMessageToSend(txn, contactId, m, maxLatency, false)); db.getMessageToSend(txn, contactId, m, maxLatency, false));
if (message == null) continue; // No longer shared if (message == null) continue; // No longer shared
recordWriter.writeMessage(message); recordWriter.writeMessage(message);
sessionRecord.onMessageSent(m); deferredSendHandler.onMessageSent(m);
LOG.info("Sent message"); LOG.info("Sent message");
} }
} }

View File

@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.Priority; import org.briarproject.bramble.api.sync.Priority;
import org.briarproject.bramble.api.sync.PriorityHandler; import org.briarproject.bramble.api.sync.PriorityHandler;
import org.briarproject.bramble.api.sync.SyncRecordReader; import org.briarproject.bramble.api.sync.SyncRecordReader;
@@ -26,8 +25,6 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_FILE_PAYLOAD_BYTES;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class SyncSessionFactoryImpl implements SyncSessionFactory { class SyncSessionFactoryImpl implements SyncSessionFactory {
@@ -76,18 +73,6 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
} }
} }
@Override
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
long maxLatency, StreamWriter streamWriter,
OutgoingSessionRecord sessionRecord) {
OutputStream out = streamWriter.getOutputStream();
SyncRecordWriter recordWriter =
recordWriterFactory.createRecordWriter(out);
return new MailboxOutgoingSession(db, eventBus, c, t, maxLatency,
streamWriter, recordWriter, sessionRecord,
MAX_FILE_PAYLOAD_BYTES);
}
@Override @Override
public SyncSession createDuplexOutgoingSession(ContactId c, TransportId t, public SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
long maxLatency, int maxIdleTime, StreamWriter streamWriter, long maxLatency, int maxIdleTime, StreamWriter streamWriter,

View File

@@ -13,24 +13,18 @@ d Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=T
d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0 d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cert=t8tA9q2AeGlmp/dO6oW9bkY5RqqmvqjArCEM9wjJoDnk6XtnaejkF0JTA7VamdyOzcvuBg iat-mode=0 n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cert=t8tA9q2AeGlmp/dO6oW9bkY5RqqmvqjArCEM9wjJoDnk6XtnaejkF0JTA7VamdyOzcvuBg iat-mode=0
n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0 n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
n Bridge obfs4 85.242.211.221:8042 A36A938DD7FDB8BACC846BA326EE0BA0D89A9252 cert=1AN6Pt1eFca3Y/WYD2TGAU3Al9cO4eouXE9SX63s66Z/ks3tVmgQ5GeXi1B5DOvx6Il7Zw iat-mode=0
n Bridge obfs4 74.104.165.202:9002 EF432018A6AA5D970B2F84E39CD30A147030141C cert=PhppfUusY85dHGvWtGTybZ1fED4DtbHmALkNMIOIYrAz1B4xN7/2a5gyiZe1epju1BOHVg iat-mode=0
n Bridge obfs4 70.34.242.31:443 7F026956402CDFF4BCBA8E11EE9C50E3FE0A2B72 cert=hP/KU7JATSfWH3HwS5Er/YLT0J+bRO3+s2fWx2yirrgf37EyrWvm/BQshoNje8WfUm6CBw iat-mode=0
n Bridge obfs4 93.95.226.151:41185 460B0CFFC0CF1D965F3DE064E08BA1915E7C916A cert=inluPzp5Jp5OzZar1eQb4dcQ/YlAj/v0kHAUCoCr3rmLt03+pVuVTjoH4mRy4+acXpn+Gw iat-mode=0 n Bridge obfs4 93.95.226.151:41185 460B0CFFC0CF1D965F3DE064E08BA1915E7C916A cert=inluPzp5Jp5OzZar1eQb4dcQ/YlAj/v0kHAUCoCr3rmLt03+pVuVTjoH4mRy4+acXpn+Gw iat-mode=0
n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0 n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0
n Bridge obfs4 83.97.179.29:1199 D83068BFAA28E71DB024B786E1E803BE14257127 cert=IduGtt05tM59Xmvo0oVNWgIRgY4OGPJjFP+y2oa6RMDHQBL/GRyFOOgX70iiazNAIJNkPw iat-mode=0
n Bridge obfs4 207.181.244.13:40132 37FE8D782F5DD2BAEEDAAB8257B701344676B6DD cert=f5Hbfn3ToMzH170cV8DfLly3vRynveidfOfDcbradIDtbLDX15V2yQ8eEH2CPKQJmQR2Hg iat-mode=0
n Bridge obfs4 76.255.201.112:8888 96CF36C2ECCFB7376AB6BE905BECD2C2AE8AEFCD cert=+q0pjaiM0JMqHL/BKqCRD+pjflaw/S406eUDF7CnFgamvQW3l2HVLJhQ6uX9P8zff0PLGg iat-mode=0 n Bridge obfs4 76.255.201.112:8888 96CF36C2ECCFB7376AB6BE905BECD2C2AE8AEFCD cert=+q0pjaiM0JMqHL/BKqCRD+pjflaw/S406eUDF7CnFgamvQW3l2HVLJhQ6uX9P8zff0PLGg iat-mode=0
n Bridge obfs4 65.108.159.114:14174 E1AD374BA9F34BD98862D128AC54D40C7DC138AE cert=YMkxMSBN2OOd99AkJpFaEUyKkdqZgJt9oVJEgO1QnT37n/Vc2yR4nhx4k4VkPLfEP1f4eg iat-mode=0 n Bridge obfs4 65.108.159.114:14174 E1AD374BA9F34BD98862D128AC54D40C7DC138AE cert=YMkxMSBN2OOd99AkJpFaEUyKkdqZgJt9oVJEgO1QnT37n/Vc2yR4nhx4k4VkPLfEP1f4eg iat-mode=0
n Bridge obfs4 185.177.207.138:8443 53716FE26F23C8C6A71A2BC5D9D8DC64747278C7 cert=6jcYVilMEzxdsWghSrQFpYYJlkkir/GPIXw/EnddUK3S8ToVpMG8u1SwMOEdEs735RrMHw iat-mode=0 n Bridge obfs4 185.177.207.138:8443 53716FE26F23C8C6A71A2BC5D9D8DC64747278C7 cert=6jcYVilMEzxdsWghSrQFpYYJlkkir/GPIXw/EnddUK3S8ToVpMG8u1SwMOEdEs735RrMHw iat-mode=0
n Bridge obfs4 5.252.176.61:9418 3E61130100AD827AB9CB33DAC052D9BC49A39509 cert=/aMyBPnKbQFISithD3i1KHUdpWeMpWG3SvUpq1YWCf2EQohFxQfw+646gW1knm4BI/DLRA iat-mode=0 n Bridge obfs4 176.123.2.253:1933 B855D141CE6C4DE0F7EA4AAED83EBA8373FA8191 cert=1rOoSaRagc6PPR//paIl+ukv1N+xWKCdBXMFxK0k/moEwH0lk5bURBrUDzIX35fVzaiicQ iat-mode=0
n Bridge obfs4 202.61.224.111:6902 A4F91299763DB925AE3BD29A0FC1A9821E5D9BAE cert=NBKm2MJ83wMvYShkqpD5RrbDtW5YpIZrFNnMw7Dj1XOM3plU60Bh4eziaQXe8fGtb8ZqKg iat-mode=0 v Bridge 135.181.113.164:54444 74AF4CCA614C454B7D3E81FF8BACD78CEBC7D7DE
n Bridge obfs4 87.121.72.109:9002 C8081D4731C953FA4AE166946E72B29153351E34 cert=bikAqxKV6Ch5gFCBTdPI28VeShYa1ZgkLmvc7YZNLWFsFZoaCULL/3AQKjpIfvSiJs5jGQ iat-mode=0
n Bridge obfs4 172.104.17.96:17900 B6B37AC96E163D0A5ECE55826D17B50B70F0A7F8 cert=gUz7svhPxoALgeN4lMYrXK7NBnaDqwu6SKRJOhaO9IIMBpnB8UhMCMKzzMho3b0RxWzBVg iat-mode=0
n Bridge obfs4 70.34.249.113:443 F441B16ABB1055794C2CE01821FC05047B2C8CFC cert=MauLNoyq8EwjY4Qe0oASYzs2hXdSjNgy+BtP9oo1naHhRsyKTtAZzeNv08RnzWjMJrTwcg iat-mode=0
n Bridge obfs4 85.212.104.44:27623 9000BDF7063F808E554C320545BCF264222CDFE9 cert=eKSXeJLnFv6RDAap5vM2sqsi8GIWhlMi5prs8hu0HTKWTH9KjZhhoj3Cg/gIuoL8n8TXGQ iat-mode=0
n Bridge obfs4 104.168.68.90:443 ED55B3C321E44EA7E50EF568C8A63CF75E89A58C cert=fgonxDvltTp8nmcOE9sUG94eOAALxETVVXAwnTZJLPpf7rjPuTp+abKl4VyFkxfcLRr5KQ iat-mode=0
n Bridge obfs4 158.247.207.151:443 6170ADBBB6C1859A8E7E4416BB8AB3AF471AE47F cert=Od4izlwLnXcq7LMSOJtnZLtklaUn+X+gPcBwN7RUEkk9rqxRRYNHW7as8g6+jheDsazxAQ iat-mode=0
n Bridge obfs4 45.142.181.131:42069 6EBCF6B02DA2B982F4080A7119D737366AFB74FA cert=9HyWH/BCwWzNirZdTQtluCgJk+gFhpOqydIuyQ1iDvpnqsAynKF+zcPE/INZFefm86UlBg iat-mode=0
n Bridge obfs4 85.214.28.204:47111 78A36E46BB082A471848239D3F4390A8F8C6084D cert=96sr3eaUFBDu4wFVAQIfNFElh0UNuutJ/3/Fh2Vu2PHfacQ8bwfY02bwG351U8BZpLnfUQ iat-mode=0
n Bridge obfs4 152.67.77.101:4096 B82DB9CDDF887AB8A859420E07DF298E30AF8A6E cert=21OWn3yFo+hulmQNAOtF5uwwOqWtdT5PrLhk8BG9DpOd0/k5DEkQEYPyDdXbS9nZ0E5BJA iat-mode=0
v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
v Bridge 213.108.108.145:17674 A39C0FE47963B6E8CFE9815549864DE544935A31 v Bridge 77.96.91.103:443 ED000A75B79A58F1D83A4D1675C2A9395B71BE8E
v Bridge 185.189.195.124:8199 A1F3EE78F9C2343668E68FEB84358A4C742831A5
v Bridge 135.23.182.26:11393 6E9A100D1E0570F0012735D64EEB4CB62AC258D9
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.connection; package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.connection.ConnectionRegistry; import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.connection.InterruptibleConnection; import org.briarproject.bramble.api.connection.InterruptibleConnection;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
@@ -58,10 +57,6 @@ public class ConnectionRegistryImplTest extends BrambleMockTestCase {
private final Priority high = private final Priority high =
new Priority(fromHexString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")); new Priority(fromHexString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"));
public ConnectionRegistryImplTest() throws FormatException {
// required for throws declaration
}
@Test @Test
public void testRegisterMultipleConnections() { public void testRegisterMultipleConnections() {
context.checking(new Expectations() {{ context.checking(new Expectations() {{

View File

@@ -10,8 +10,6 @@ import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule; import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule;
import org.briarproject.bramble.test.TestDnsModule;
import org.briarproject.bramble.test.TestSocksModule;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -22,9 +20,7 @@ import dagger.Component;
@Singleton @Singleton
@Component(modules = { @Component(modules = {
BrambleCoreIntegrationTestModule.class, BrambleCoreIntegrationTestModule.class,
BrambleCoreModule.class, BrambleCoreModule.class
TestDnsModule.class,
TestSocksModule.class
}) })
interface ContactExchangeIntegrationTestComponent interface ContactExchangeIntegrationTestComponent
extends BrambleCoreIntegrationTestEagerSingletons { extends BrambleCoreIntegrationTestEagerSingletons {

View File

@@ -17,6 +17,7 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.util.Collection; import java.util.Collection;
@@ -62,9 +63,13 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
private final long timestamp = System.currentTimeMillis(); private final long timestamp = System.currentTimeMillis();
private final boolean alice = new Random().nextBoolean(); private final boolean alice = new Random().nextBoolean();
private final ContactManagerImpl contactManager = private ContactManagerImpl contactManager;
new ContactManagerImpl(db, keyManager, identityManager,
pendingContactFactory); @Before
public void setUp() {
contactManager = new ContactManagerImpl(db, keyManager,
identityManager, pendingContactFactory);
}
@Test @Test
public void testAddContact() throws Exception { public void testAddContact() throws Exception {

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.bouncycastle.crypto.digests.Blake2bDigest; import org.bouncycastle.crypto.digests.Blake2bDigest;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.junit.Test; import org.junit.Test;
@@ -48,7 +47,7 @@ public class Blake2bDigestTest extends BrambleTestCase {
}; };
@Test @Test
public void testDigestWithKeyedTestVectors() throws FormatException { public void testDigestWithKeyedTestVectors() {
for (String[] keyedTestVector : KEYED_TEST_VECTORS) { for (String[] keyedTestVector : KEYED_TEST_VECTORS) {
byte[] input = StringUtils.fromHexString(keyedTestVector[0]); byte[] input = StringUtils.fromHexString(keyedTestVector[0]);
byte[] key = StringUtils.fromHexString(keyedTestVector[1]); byte[] key = StringUtils.fromHexString(keyedTestVector[1]);
@@ -64,8 +63,7 @@ public class Blake2bDigestTest extends BrambleTestCase {
} }
@Test @Test
public void testDigestWithKeyedTestVectorsAndRandomUpdate() public void testDigestWithKeyedTestVectorsAndRandomUpdate() {
throws FormatException {
Random random = new Random(); Random random = new Random();
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
for (String[] keyedTestVector : KEYED_TEST_VECTORS) { for (String[] keyedTestVector : KEYED_TEST_VECTORS) {
@@ -140,7 +138,7 @@ public class Blake2bDigestTest extends BrambleTestCase {
} }
@Test @Test
public void runSelfTest() throws FormatException { public void runSelfTest() {
Blake2bDigest testDigest = new Blake2bDigest(256); Blake2bDigest testDigest = new Blake2bDigest(256);
byte[] md = new byte[64]; byte[] md = new byte[64];

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.crypto.AgreementPublicKey; import org.briarproject.bramble.api.crypto.AgreementPublicKey;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
@@ -84,7 +83,7 @@ public class KeyAgreementTest extends BrambleTestCase {
} }
@Test @Test
public void testRfc7748TestVector() throws FormatException { public void testRfc7748TestVector() {
byte[] aPriv = parsePrivateKey(ALICE_PRIVATE); byte[] aPriv = parsePrivateKey(ALICE_PRIVATE);
byte[] aPub = fromHexString(ALICE_PUBLIC); byte[] aPub = fromHexString(ALICE_PUBLIC);
byte[] bPriv = parsePrivateKey(BOB_PRIVATE); byte[] bPriv = parsePrivateKey(BOB_PRIVATE);
@@ -98,8 +97,7 @@ public class KeyAgreementTest extends BrambleTestCase {
} }
@Test @Test
public void testDerivesSameSharedSecretFromEquivalentPublicKey() public void testDerivesSameSharedSecretFromEquivalentPublicKey() {
throws FormatException {
byte[] aPub = fromHexString(ALICE_PUBLIC); byte[] aPub = fromHexString(ALICE_PUBLIC);
byte[] bPriv = parsePrivateKey(BOB_PRIVATE); byte[] bPriv = parsePrivateKey(BOB_PRIVATE);
byte[] sharedSecret = fromHexString(SHARED_SECRET); byte[] sharedSecret = fromHexString(SHARED_SECRET);
@@ -170,7 +168,7 @@ public class KeyAgreementTest extends BrambleTestCase {
return crypto.deriveSharedSecret(label, publicKey, keyPair, inputs); return crypto.deriveSharedSecret(label, publicKey, keyPair, inputs);
} }
private byte[] parsePrivateKey(String hex) throws FormatException { private byte[] parsePrivateKey(String hex) {
// Private keys need to be clamped because curve25519-java does the // Private keys need to be clamped because curve25519-java does the
// clamping at key generation time, not multiplication time // clamping at key generation time, not multiplication time
return AgreementKeyParser.clamp(fromHexString(hex)); return AgreementKeyParser.clamp(fromHexString(hex));

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
@@ -16,11 +15,11 @@ public class XSalsa20Poly1305AuthenticatedCipherTest extends BrambleTestCase {
// Test vectors from the NaCl paper // Test vectors from the NaCl paper
// http://cr.yp.to/highspeed/naclcrypto-20090310.pdf // http://cr.yp.to/highspeed/naclcrypto-20090310.pdf
private final byte[] TEST_KEY = StringUtils.fromHexString( private static final byte[] TEST_KEY = StringUtils.fromHexString(
"1b27556473e985d462cd51197a9a46c76009549eac6474f206c4ee0844f68389"); "1b27556473e985d462cd51197a9a46c76009549eac6474f206c4ee0844f68389");
private final byte[] TEST_IV = StringUtils.fromHexString( private static final byte[] TEST_IV = StringUtils.fromHexString(
"69696ee955b62b73cd62bda875fc73d68219e0036b7a0b37"); "69696ee955b62b73cd62bda875fc73d68219e0036b7a0b37");
private final byte[] TEST_PLAINTEXT = StringUtils.fromHexString( private static final byte[] TEST_PLAINTEXT = StringUtils.fromHexString(
"be075fc53c81f2d5cf141316" + "be075fc53c81f2d5cf141316" +
"ebeb0c7b5228c52a4c62cbd4" + "ebeb0c7b5228c52a4c62cbd4" +
"4b66849b64244ffce5ecbaaf" + "4b66849b64244ffce5ecbaaf" +
@@ -32,7 +31,7 @@ public class XSalsa20Poly1305AuthenticatedCipherTest extends BrambleTestCase {
"048977eb48f59ffd4924ca1c" + "048977eb48f59ffd4924ca1c" +
"60902e52f0a089bc76897040" + "60902e52f0a089bc76897040" +
"e082f937763848645e0705"); "e082f937763848645e0705");
private final byte[] TEST_CIPHERTEXT = StringUtils.fromHexString( private static final byte[] TEST_CIPHERTEXT = StringUtils.fromHexString(
"f3ffc7703f9400e52a7dfb4b" + "f3ffc7703f9400e52a7dfb4b" +
"3d3305d98e993b9f48681273" + "3d3305d98e993b9f48681273" +
"c29650ba32fc76ce48332ea7" + "c29650ba32fc76ce48332ea7" +
@@ -47,10 +46,6 @@ public class XSalsa20Poly1305AuthenticatedCipherTest extends BrambleTestCase {
"a43d14a6599b1f654cb45a74" + "a43d14a6599b1f654cb45a74" +
"e355a5"); "e355a5");
public XSalsa20Poly1305AuthenticatedCipherTest() throws FormatException {
// required for throws declaration
}
@Test @Test
public void testEncrypt() throws Exception { public void testEncrypt() throws Exception {
SecretKey k = new SecretKey(TEST_KEY); SecretKey k = new SecretKey(TEST_KEY);

View File

@@ -618,12 +618,11 @@ public class BdfReaderImplTest extends BrambleTestCase {
r.readDictionary(); r.readDictionary();
} }
private void setContents(String hex) throws FormatException { private void setContents(String hex) {
setContents(hex, DEFAULT_MAX_BUFFER_SIZE); setContents(hex, DEFAULT_MAX_BUFFER_SIZE);
} }
private void setContents(String hex, int maxBufferSize) private void setContents(String hex, int maxBufferSize) {
throws FormatException {
ByteArrayInputStream in = new ByteArrayInputStream(fromHexString(hex)); ByteArrayInputStream in = new ByteArrayInputStream(fromHexString(hex));
r = new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT, maxBufferSize); r = new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT, maxBufferSize);
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.data;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@@ -16,8 +17,14 @@ import static org.junit.Assert.assertArrayEquals;
public class BdfWriterImplTest extends BrambleTestCase { public class BdfWriterImplTest extends BrambleTestCase {
private final ByteArrayOutputStream out = new ByteArrayOutputStream(); private ByteArrayOutputStream out = null;
private final BdfWriterImpl w = new BdfWriterImpl(out); private BdfWriterImpl w = null;
@Before
public void setUp() {
out = new ByteArrayOutputStream();
w = new BdfWriterImpl(out);
}
@Test @Test
public void testWriteNull() throws IOException { public void testWriteNull() throws IOException {

View File

@@ -70,7 +70,6 @@ import static java.util.Arrays.asList;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.HOURS;
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED; import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES; import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
@@ -284,16 +283,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
temporary, null); temporary, null);
oneOf(database).mergeMessageMetadata(txn, messageId, metadata); oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
// Broadcast events for message being added and changing state // The message was added, so the listeners should be called
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class))); oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
oneOf(eventBus).broadcast(with(any( oneOf(eventBus).broadcast(with(any(
MessageStateChangedEvent.class))); MessageStateChangedEvent.class)));
// If message is shared, get group visibility and broadcast event if (shared)
if (shared) {
oneOf(database).getGroupVisibility(txn, groupId);
will(returnValue(singletonMap(contactId, true)));
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class))); oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
}
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); eventExecutor, shutdownManager);
@@ -308,11 +303,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Check whether the contact is in the DB (which it's not) // Check whether the contact is in the DB (which it's not)
exactly(27).of(database).startTransaction(); exactly(25).of(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
exactly(27).of(database).containsContact(txn, contactId); exactly(25).of(database).containsContact(txn, contactId);
will(returnValue(false)); will(returnValue(false));
exactly(27).of(database).abortTransaction(txn); exactly(25).of(database).abortTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); eventExecutor, shutdownManager);
@@ -326,23 +321,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// Expected // Expected
} }
try {
db.transaction(true, transaction ->
db.containsAcksToSend(transaction, contactId));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try {
db.transaction(true, transaction ->
db.containsMessagesToSend(transaction, contactId,
123, true));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.generateAck(transaction, contactId, 123)); db.generateAck(transaction, contactId, 123));
@@ -394,7 +372,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
try { try {
db.transaction(true, transaction -> db.transaction(true, transaction ->
db.getMessagesToAck(transaction, contactId)); db.getMessagesToAck(transaction, contactId, 123));
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
@@ -699,11 +677,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Check whether the message is in the DB (which it's not) // Check whether the message is in the DB (which it's not)
exactly(16).of(database).startTransaction(); exactly(15).of(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
exactly(16).of(database).containsMessage(txn, messageId); exactly(15).of(database).containsMessage(txn, messageId);
will(returnValue(false)); will(returnValue(false));
exactly(16).of(database).abortTransaction(txn); exactly(15).of(database).abortTransaction(txn);
// Allow other checks to pass // Allow other checks to pass
allowing(database).containsContact(txn, contactId); allowing(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
@@ -727,14 +705,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// Expected // Expected
} }
try {
db.transaction(true, transaction ->
db.getGroupId(transaction, messageId));
fail();
} catch (NoSuchMessageException expected) {
// Expected
}
try { try {
db.transaction(true, transaction -> db.transaction(true, transaction ->
db.getMessage(transaction, messageId)); db.getMessage(transaction, messageId));
@@ -1409,7 +1379,14 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(txn)); will(returnValue(txn));
oneOf(database).containsContact(txn, contactId); oneOf(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).lowerAckFlag(txn, contactId, acked); // First message is still visible to the contact - flag lowered
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
will(returnValue(true));
// Second message is no longer visible - flag not lowered
oneOf(database).containsVisibleMessage(txn, contactId, messageId1);
will(returnValue(false));
oneOf(database)
.lowerAckFlag(txn, contactId, singletonList(messageId));
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
@@ -1886,16 +1863,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).containsMessage(txn, messageId); oneOf(database).containsMessage(txn, messageId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).getMessageDependents(txn, messageId); oneOf(database).getMessageDependents(txn, messageId);
// Broadcast events for message being added and changing state // broadcast for message added event
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class))); oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
oneOf(eventBus).broadcast(with(any( oneOf(eventBus).broadcast(with(any(
MessageStateChangedEvent.class))); MessageStateChangedEvent.class)));
// If message is shared, get group visibility and broadcast event if (shared)
if (shared) {
oneOf(database).getGroupVisibility(txn, groupId);
will(returnValue(singletonMap(contactId, true)));
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class))); oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
}
// endTransaction() // endTransaction()
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
// close() // close()

View File

@@ -168,7 +168,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(db.containsContact(txn, contactId)); assertTrue(db.containsContact(txn, contactId));
assertTrue(db.containsGroup(txn, groupId)); assertTrue(db.containsGroup(txn, groupId));
assertTrue(db.containsMessage(txn, messageId)); assertTrue(db.containsMessage(txn, messageId));
assertEquals(groupId, db.getGroupId(txn, messageId));
assertArrayEquals(message.getBody(), assertArrayEquals(message.getBody(),
db.getMessage(txn, messageId).getBody()); db.getMessage(txn, messageId).getBody());
@@ -379,9 +378,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Initially there should be nothing to send // Initially there should be nothing to send
assertFalse( assertFalse(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false)); db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertFalse( assertFalse(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true)); db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
// Add some messages to ack // Add some messages to ack
Message message1 = getMessage(groupId); Message message1 = getMessage(groupId);
@@ -390,7 +389,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message1, DELIVERED, true, false, contactId); db.addMessage(txn, message1, DELIVERED, true, false, contactId);
// Both message IDs should be returned // Both message IDs should be returned
assertTrue(db.containsAcksToSend(txn, contactId)); assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234); Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
assertEquals(asList(messageId, messageId1), ids); assertEquals(asList(messageId, messageId1), ids);
@@ -398,7 +400,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1)); db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
// No message IDs should be returned // No message IDs should be returned
assertFalse(db.containsAcksToSend(txn, contactId)); assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
assertEquals(emptyList(), db.getMessagesToAck(txn, contactId, 1234)); assertEquals(emptyList(), db.getMessagesToAck(txn, contactId, 1234));
// Raise the ack flag again // Raise the ack flag again
@@ -406,7 +411,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.raiseAckFlag(txn, contactId, messageId1); db.raiseAckFlag(txn, contactId, messageId1);
// Both message IDs should be returned // Both message IDs should be returned
assertTrue(db.containsAcksToSend(txn, contactId)); assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
ids = db.getMessagesToAck(txn, contactId, 1234); ids = db.getMessagesToAck(txn, contactId, 1234);
assertEquals(asList(messageId, messageId1), ids); assertEquals(asList(messageId, messageId1), ids);
@@ -2021,51 +2029,37 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message, UNKNOWN, false, false, null); db.addMessage(txn, message, UNKNOWN, false, false, null);
// There should be no messages to send // There should be no messages to send
assertEquals(Long.MAX_VALUE, assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// Share the group with the contact - still no messages to send // Share the group with the contact - still no messages to send
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
assertEquals(Long.MAX_VALUE, assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// Set the message's state to DELIVERED - still no messages to send // Set the message's state to DELIVERED - still no messages to send
db.setMessageState(txn, messageId, DELIVERED); db.setMessageState(txn, messageId, DELIVERED);
assertEquals(Long.MAX_VALUE, assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// Share the message - now it should be sendable immediately // Share the message - now it should be sendable immediately
db.setMessageShared(txn, messageId, true); db.setMessageShared(txn, messageId, true);
assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY)); assertEquals(0, db.getNextSendTime(txn, contactId));
// Mark the message as requested - it should still be sendable // Mark the message as requested - it should still be sendable
db.raiseRequestedFlag(txn, contactId, messageId); db.raiseRequestedFlag(txn, contactId, messageId);
assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY)); assertEquals(0, db.getNextSendTime(txn, contactId));
// Update the message's expiry time as though we sent it - now the // Update the message's expiry time as though we sent it - now the
// message should be sendable after one round-trip // message should be sendable after one round-trip
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY); db.updateRetransmissionData(txn, contactId, messageId, 1000);
assertEquals(now + MAX_LATENCY * 2, assertEquals(now + 2000, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// The message should be sendable immediately over a transport with
// lower latency
assertEquals(0L, db.getNextSendTime(txn, contactId, MAX_LATENCY - 1));
// Update the message's expiry time again - now it should be sendable // Update the message's expiry time again - now it should be sendable
// after two round-trips // after two round-trips
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY); db.updateRetransmissionData(txn, contactId, messageId, 1000);
assertEquals(now + MAX_LATENCY * 4, assertEquals(now + 4000, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// The message should be sendable immediately over a transport with
// lower latency
assertEquals(0L, db.getNextSendTime(txn, contactId, MAX_LATENCY - 1));
// Delete the message - there should be no messages to send // Delete the message - there should be no messages to send
db.deleteMessage(txn, messageId); db.deleteMessage(txn, messageId);
assertEquals(Long.MAX_VALUE, assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -2130,8 +2124,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY); db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
// The message should expire after 2 * MAX_LATENCY // The message should expire after 2 * MAX_LATENCY
assertEquals(now + MAX_LATENCY * 2, assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// Time: now + MAX_LATENCY * 2 - 1 // Time: now + MAX_LATENCY * 2 - 1
// The message should not yet be sendable // The message should not yet be sendable
@@ -2174,8 +2167,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY); db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
// The message should expire after 2 * MAX_LATENCY // The message should expire after 2 * MAX_LATENCY
assertEquals(now + MAX_LATENCY * 2, assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// The message should not be sendable via the same transport // The message should not be sendable via the same transport
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
@@ -2222,8 +2214,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY); db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
// The message should expire after 2 * MAX_LATENCY // The message should expire after 2 * MAX_LATENCY
assertEquals(now + MAX_LATENCY * 2, assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// Time: now + MAX_LATENCY * 2 - 1 // Time: now + MAX_LATENCY * 2 - 1
// The message should not yet be sendable // The message should not yet be sendable
@@ -2234,8 +2225,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Reset the retransmission times // Reset the retransmission times
db.resetUnackedMessagesToSend(txn, contactId); db.resetUnackedMessagesToSend(txn, contactId);
// The message should be sendable immediately // The message should have infinitely short expiry
assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY)); assertEquals(0, db.getNextSendTime(txn, contactId));
// The message should be sendable // The message should be sendable
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
@@ -2588,7 +2579,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private void assertNothingToSendLazily(Database<Connection> db, private void assertNothingToSendLazily(Database<Connection> db,
Connection txn) throws Exception { Connection txn) throws Exception {
assertFalse( assertFalse(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false)); db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
Collection<MessageId> ids = Collection<MessageId> ids =
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
assertTrue(ids.isEmpty()); assertTrue(ids.isEmpty());
@@ -2599,7 +2590,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private void assertOneMessageToSendLazily(Database<Connection> db, private void assertOneMessageToSendLazily(Database<Connection> db,
Connection txn) throws Exception { Connection txn) throws Exception {
assertTrue( assertTrue(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false)); db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
Collection<MessageId> ids = Collection<MessageId> ids =
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
assertEquals(singletonList(messageId), ids); assertEquals(singletonList(messageId), ids);
@@ -2610,7 +2601,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private void assertNothingToSendEagerly(Database<Connection> db, private void assertNothingToSendEagerly(Database<Connection> db,
Connection txn) throws Exception { Connection txn) throws Exception {
assertFalse( assertFalse(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true)); db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
Collection<MessageId> unacked = Collection<MessageId> unacked =
db.getUnackedMessagesToSend(txn, contactId); db.getUnackedMessagesToSend(txn, contactId);
assertTrue(unacked.isEmpty()); assertTrue(unacked.isEmpty());
@@ -2620,7 +2611,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private void assertOneMessageToSendEagerly(Database<Connection> db, private void assertOneMessageToSendEagerly(Database<Connection> db,
Connection txn) throws Exception { Connection txn) throws Exception {
assertTrue( assertTrue(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true)); db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
Collection<MessageId> unacked = Collection<MessageId> unacked =
db.getUnackedMessagesToSend(txn, contactId); db.getUnackedMessagesToSend(txn, contactId);
assertEquals(singletonList(messageId), unacked); assertEquals(singletonList(messageId), unacked);

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
@@ -40,8 +41,13 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
private final KeyPair handshakeKeyPair = private final KeyPair handshakeKeyPair =
new KeyPair(handshakePublicKey, handshakePrivateKey); new KeyPair(handshakePublicKey, handshakePrivateKey);
private final IdentityManagerImpl identityManager = private IdentityManagerImpl identityManager;
new IdentityManagerImpl(db, crypto, authorFactory, clock);
@Before
public void setUp() {
identityManager =
new IdentityManagerImpl(db, crypto, authorFactory, clock);
}
@Test @Test
public void testOpenDatabaseIdentityRegistered() throws Exception { public void testOpenDatabaseIdentityRegistered() throws Exception {

View File

@@ -5,18 +5,20 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook; import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent; import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.util.concurrent.atomic.AtomicBoolean;
import static junit.framework.TestCase.assertTrue;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPED; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
import static org.briarproject.bramble.api.system.Clock.MAX_REASONABLE_TIME_MS; import static org.briarproject.bramble.api.system.Clock.MAX_REASONABLE_TIME_MS;
@@ -29,18 +31,22 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
private final DatabaseComponent db = context.mock(DatabaseComponent.class); private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final EventBus eventBus = context.mock(EventBus.class); private final EventBus eventBus = context.mock(EventBus.class);
private final Clock clock = context.mock(Clock.class); private final Clock clock = context.mock(Clock.class);
private final OpenDatabaseHook hook = context.mock(OpenDatabaseHook.class);
private final Service service = context.mock(Service.class);
private final SecretKey dbKey = getSecretKey(); private final SecretKey dbKey = getSecretKey();
private final LifecycleManagerImpl lifecycleManager = private LifecycleManagerImpl lifecycleManager;
new LifecycleManagerImpl(db, eventBus, clock);
@Before
public void setUp() {
lifecycleManager = new LifecycleManagerImpl(db, eventBus, clock);
}
@Test @Test
public void testOpenDatabaseHooksAreCalledAtStartup() throws Exception { public void testOpenDatabaseHooksAreCalledAtStartup() throws Exception {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
AtomicBoolean called = new AtomicBoolean(false);
OpenDatabaseHook hook = transaction -> called.set(true);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
@@ -49,7 +55,6 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
will(returnValue(false)); will(returnValue(false));
oneOf(db).transaction(with(false), withDbRunnable(txn)); oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(db).removeTemporaryMessages(txn); oneOf(db).removeTemporaryMessages(txn);
oneOf(hook).onDatabaseOpened(txn);
allowing(eventBus).broadcast(with(any(LifecycleEvent.class))); allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
}}); }});
@@ -57,38 +62,7 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey)); assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
assertEquals(RUNNING, lifecycleManager.getLifecycleState()); assertEquals(RUNNING, lifecycleManager.getLifecycleState());
} assertTrue(called.get());
@Test
public void testServicesAreStartedAndStopped() throws Exception {
long now = System.currentTimeMillis();
Transaction txn = new Transaction(null, false);
context.checking(new DbExpectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(db).open(dbKey, lifecycleManager);
will(returnValue(false));
oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(db).removeTemporaryMessages(txn);
oneOf(service).startService();
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
}});
lifecycleManager.registerService(service);
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
context.assertIsSatisfied();
context.checking(new Expectations() {{
oneOf(db).close();
oneOf(service).stopService();
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
}});
lifecycleManager.stopServices();
assertEquals(STOPPED, lifecycleManager.getLifecycleState());
} }
@Test @Test
@@ -115,31 +89,6 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
assertEquals(STARTING, lifecycleManager.getLifecycleState()); assertEquals(STARTING, lifecycleManager.getLifecycleState());
} }
@Test
public void testSecondCallToStartServicesReturnsEarly() throws Exception {
long now = System.currentTimeMillis();
Transaction txn = new Transaction(null, false);
context.checking(new DbExpectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(db).open(dbKey, lifecycleManager);
will(returnValue(false));
oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(db).removeTemporaryMessages(txn);
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
}});
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
context.assertIsSatisfied();
// Calling startServices() again should not try to open the DB or
// start the services again
assertEquals(ALREADY_RUNNING, lifecycleManager.startServices(dbKey));
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
}
@Test @Test
public void testSecondCallToStopServicesReturnsEarly() throws Exception { public void testSecondCallToStopServicesReturnsEarly() throws Exception {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
@@ -152,7 +101,7 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
will(returnValue(false)); will(returnValue(false));
oneOf(db).transaction(with(false), withDbRunnable(txn)); oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(db).removeTemporaryMessages(txn); oneOf(db).removeTemporaryMessages(txn);
allowing(eventBus).broadcast(with(any(LifecycleEvent.class))); exactly(2).of(eventBus).broadcast(with(any(LifecycleEvent.class)));
}}); }});
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey)); assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
@@ -160,17 +109,17 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
context.assertIsSatisfied(); context.assertIsSatisfied();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(eventBus).broadcast(with(any(LifecycleEvent.class)));
oneOf(db).close(); oneOf(db).close();
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
}}); }});
lifecycleManager.stopServices(); lifecycleManager.stopServices();
assertEquals(STOPPED, lifecycleManager.getLifecycleState()); assertEquals(STOPPING, lifecycleManager.getLifecycleState());
context.assertIsSatisfied(); context.assertIsSatisfied();
// Calling stopServices() again should not broadcast another event or // Calling stopServices() again should not broadcast another event or
// try to close the DB again // try to close the DB again
lifecycleManager.stopServices(); lifecycleManager.stopServices();
assertEquals(STOPPED, lifecycleManager.getLifecycleState()); assertEquals(STOPPING, lifecycleManager.getLifecycleState());
} }
} }

View File

@@ -187,32 +187,6 @@ public class ConnectivityCheckerImplTest extends BrambleMockTestCase {
checker.onConnectivityCheckSucceeded(now); checker.onConnectivityCheckSucceeded(now);
} }
@Test
public void testCheckIsCancelledWhenObserverIsRemoved() {
ConnectivityCheckerImpl checker = createChecker();
// When checkConnectivity() is called a check should be started
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(mailboxApiCaller).retryWithBackoff(apiCall);
will(returnValue(task));
}});
checker.checkConnectivity(properties, observer1);
// When the observer is removed the check should be cancelled
context.checking(new Expectations() {{
oneOf(task).cancel();
}});
checker.removeObserver(observer1);
// If the check runs anyway (cancellation came too late) the observer
// should not be called
checker.onConnectivityCheckSucceeded(now);
}
private ConnectivityCheckerImpl createChecker() { private ConnectivityCheckerImpl createChecker() {
return new ConnectivityCheckerImpl(clock, mailboxApiCaller) { return new ConnectivityCheckerImpl(clock, mailboxApiCaller) {

View File

@@ -1,143 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.junit.Test;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
public class ContactMailboxClientTest extends BrambleMockTestCase {
private final MailboxWorkerFactory workerFactory =
context.mock(MailboxWorkerFactory.class);
private final ConnectivityChecker connectivityChecker =
context.mock(ConnectivityChecker.class);
private final TorReachabilityMonitor reachabilityMonitor =
context.mock(TorReachabilityMonitor.class);
private final MailboxWorker uploadWorker =
context.mock(MailboxWorker.class, "uploadWorker");
private final MailboxWorker downloadWorker =
context.mock(MailboxWorker.class, "downloadWorker");
private final MailboxProperties properties =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final MailboxFolderId inboxId =
requireNonNull(properties.getInboxId());
private final MailboxFolderId outboxId =
requireNonNull(properties.getOutboxId());
private final ContactId contactId = getContactId();
private final ContactMailboxClient client =
new ContactMailboxClient(workerFactory, connectivityChecker,
reachabilityMonitor);
@Test
public void testStartAndDestroyWithNoContactsAssigned() {
client.start();
expectDestroyConnectivityChecker();
client.destroy();
}
@Test
public void testAssignContactForUploadAndDestroyClient() {
client.start();
// When the contact is assigned, the worker should be created and
// started
expectCreateAndStartUploadWorker();
client.assignContactForUpload(contactId, properties, outboxId);
// When the client is destroyed, the worker should be destroyed
expectDestroyWorker(uploadWorker);
expectDestroyConnectivityChecker();
client.destroy();
}
@Test
public void testAssignAndDeassignContactForUpload() {
client.start();
// When the contact is assigned, the worker should be created and
// started
expectCreateAndStartUploadWorker();
client.assignContactForUpload(contactId, properties, outboxId);
// When the contact is deassigned, the worker should be destroyed
expectDestroyWorker(uploadWorker);
client.deassignContactForUpload(contactId);
context.assertIsSatisfied();
expectDestroyConnectivityChecker();
client.destroy();
}
@Test
public void testAssignContactForDownloadAndDestroyClient() {
client.start();
// When the contact is assigned, the worker should be created and
// started
expectCreateAndStartDownloadWorker();
client.assignContactForDownload(contactId, properties, inboxId);
// When the client is destroyed, the worker should be destroyed
expectDestroyWorker(downloadWorker);
expectDestroyConnectivityChecker();
client.destroy();
}
@Test
public void testAssignAndDeassignContactForDownload() {
client.start();
// When the contact is assigned, the worker should be created and
// started
expectCreateAndStartDownloadWorker();
client.assignContactForDownload(contactId, properties, inboxId);
// When the contact is deassigned, the worker should be destroyed
expectDestroyWorker(downloadWorker);
client.deassignContactForDownload(contactId);
context.assertIsSatisfied();
expectDestroyConnectivityChecker();
client.destroy();
}
private void expectCreateAndStartUploadWorker() {
context.checking(new Expectations() {{
oneOf(workerFactory).createUploadWorker(connectivityChecker,
properties, outboxId, contactId);
will(returnValue(uploadWorker));
oneOf(uploadWorker).start();
}});
}
private void expectCreateAndStartDownloadWorker() {
context.checking(new Expectations() {{
oneOf(workerFactory).createDownloadWorkerForContactMailbox(
connectivityChecker, reachabilityMonitor, properties);
will(returnValue(downloadWorker));
oneOf(downloadWorker).start();
}});
}
private void expectDestroyWorker(MailboxWorker worker) {
context.checking(new Expectations() {{
oneOf(worker).destroy();
}});
}
private void expectDestroyConnectivityChecker() {
context.checking(new Expectations() {{
oneOf(connectivityChecker).destroy();
}});
}
}

View File

@@ -1,136 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.junit.Assert.assertFalse;
public class ContactMailboxDownloadWorkerTest
extends MailboxDownloadWorkerTest<ContactMailboxDownloadWorker> {
public ContactMailboxDownloadWorkerTest() {
mailboxProperties = getMailboxProperties(false, CLIENT_SUPPORTS);
worker = new ContactMailboxDownloadWorker(connectivityChecker,
torReachabilityMonitor, mailboxApiCaller, mailboxApi,
mailboxFileManager, mailboxProperties);
}
@Test
public void testChecksConnectivityWhenStartedAndRemovesObserverWhenDestroyed() {
// When the worker is started it should start a connectivity check
expectStartConnectivityCheck();
worker.start();
// When the worker is destroyed it should remove the connectivity
// and reachability observers
expectRemoveObservers();
worker.destroy();
}
@Test
public void testChecksForFilesWhenConnectivityCheckSucceeds()
throws Exception {
// When the worker is started it should start a connectivity check
expectStartConnectivityCheck();
worker.start();
// When the connectivity check succeeds, a list-inbox task should be
// started for the first download cycle
AtomicReference<ApiCall> listTask = new AtomicReference<>();
expectStartTask(listTask);
worker.onConnectivityCheckSucceeded();
// When the list-inbox tasks runs and finds no files to download,
// it should add a Tor reachability observer
expectCheckForFiles(mailboxProperties.getInboxId(), emptyList());
expectAddReachabilityObserver();
assertFalse(listTask.get().callApi());
// When the reachability observer is called, a list-inbox task should
// be started for the second download cycle
expectStartTask(listTask);
worker.onTorReachable();
// When the list-inbox tasks runs and finds no files to download,
// it should finish the second download cycle
expectCheckForFiles(mailboxProperties.getInboxId(), emptyList());
assertFalse(listTask.get().callApi());
// When the worker is destroyed it should remove the connectivity
// and reachability observers
expectRemoveObservers();
worker.destroy();
}
@Test
public void testDownloadsFilesWhenConnectivityCheckSucceeds()
throws Exception {
// When the worker is started it should start a connectivity check
expectStartConnectivityCheck();
worker.start();
// When the connectivity check succeeds, a list-inbox task should be
// started for the first download cycle
AtomicReference<ApiCall> listTask = new AtomicReference<>();
expectStartTask(listTask);
worker.onConnectivityCheckSucceeded();
// When the list-inbox tasks runs and finds some files to download,
// it should start a download task for the first file
AtomicReference<ApiCall> downloadTask = new AtomicReference<>();
expectCheckForFiles(mailboxProperties.getInboxId(), files);
expectStartTask(downloadTask);
assertFalse(listTask.get().callApi());
// When the first download task runs it should download the file to the
// location provided by the file manager and start a delete task
AtomicReference<ApiCall> deleteTask = new AtomicReference<>();
expectDownloadFile(mailboxProperties.getInboxId(), file1);
expectStartTask(deleteTask);
assertFalse(downloadTask.get().callApi());
// When the first delete task runs it should delete the file, ignore
// the tolerable failure, and start a download task for the next file
expectDeleteFile(mailboxProperties.getInboxId(), file1, true);
expectStartTask(downloadTask);
assertFalse(deleteTask.get().callApi());
// When the second download task runs it should download the file to
// the location provided by the file manager and start a delete task
expectDownloadFile(mailboxProperties.getInboxId(), file2);
expectStartTask(deleteTask);
assertFalse(downloadTask.get().callApi());
// When the second delete task runs it should delete the file and
// start a list-inbox task to check for files that may have arrived
// since the first download cycle started
expectDeleteFile(mailboxProperties.getInboxId(), file2, false);
expectStartTask(listTask);
assertFalse(deleteTask.get().callApi());
// When the list-inbox tasks runs and finds no more files to download,
// it should add a Tor reachability observer
expectCheckForFiles(mailboxProperties.getInboxId(), emptyList());
expectAddReachabilityObserver();
assertFalse(listTask.get().callApi());
// When the reachability observer is called, a list-inbox task should
// be started for the second download cycle
expectStartTask(listTask);
worker.onTorReachable();
// When the list-inbox tasks runs and finds no more files to download,
// it should finish the second download cycle
expectCheckForFiles(mailboxProperties.getInboxId(), emptyList());
assertFalse(listTask.get().callApi());
// When the worker is destroyed it should remove the connectivity
// and reachability observers
expectRemoveObservers();
worker.destroy();
}
}

View File

@@ -68,10 +68,7 @@ public class MailboxApiTest extends BrambleTestCase {
return client; return client;
} }
}; };
// We aren't using a real onion address, so use the given address verbatim private final MailboxApiImpl api = new MailboxApiImpl(httpClientProvider);
private final UrlConverter urlConverter = onion -> onion;
private final MailboxApiImpl api = new MailboxApiImpl(httpClientProvider,
urlConverter);
private final MailboxAuthToken token = new MailboxAuthToken(getRandomId()); private final MailboxAuthToken token = new MailboxAuthToken(getRandomId());
private final MailboxAuthToken token2 = new MailboxAuthToken(getRandomId()); private final MailboxAuthToken token2 = new MailboxAuthToken(getRandomId());
@@ -630,7 +627,6 @@ public class MailboxApiTest extends BrambleTestCase {
server.enqueue(new MockResponse().setBody(invalidResponse3)); server.enqueue(new MockResponse().setBody(invalidResponse3));
server.enqueue(new MockResponse().setBody(invalidResponse4)); server.enqueue(new MockResponse().setBody(invalidResponse4));
server.enqueue(new MockResponse().setResponseCode(401)); server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(404));
server.enqueue(new MockResponse().setResponseCode(500)); server.enqueue(new MockResponse().setResponseCode(500));
server.start(); server.start();
String baseUrl = getBaseUrl(server); String baseUrl = getBaseUrl(server);
@@ -707,21 +703,13 @@ public class MailboxApiTest extends BrambleTestCase {
assertEquals("GET", request8.getMethod()); assertEquals("GET", request8.getMethod());
assertToken(request8, token); assertToken(request8, token);
// 404 not found // 500 internal server error
assertThrows(TolerableFailureException.class, () -> assertThrows(ApiException.class,
api.getFiles(properties, contactInboxId)); () -> api.getFiles(properties, contactInboxId));
RecordedRequest request9 = server.takeRequest(); RecordedRequest request9 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request9.getPath()); assertEquals("/files/" + contactInboxId, request9.getPath());
assertEquals("GET", request9.getMethod()); assertEquals("GET", request9.getMethod());
assertToken(request9, token); assertToken(request9, token);
// 500 internal server error
assertThrows(ApiException.class,
() -> api.getFiles(properties, contactInboxId));
RecordedRequest request10 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request10.getPath());
assertEquals("GET", request10.getMethod());
assertToken(request10, token);
} }
@Test @Test

View File

@@ -1,929 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
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.OwnMailboxConnectionStatusEvent;
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.RunAction;
import org.jmock.Expectations;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
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.Plugin.State.ENABLING;
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
public class MailboxClientManagerTest extends BrambleMockTestCase {
private final Executor eventExecutor =
context.mock(Executor.class, "eventExecutor");
private final Executor dbExecutor =
context.mock(Executor.class, "dbExecutor");
private final TransactionManager db =
context.mock(TransactionManager.class);
private final ContactManager contactManager =
context.mock(ContactManager.class);
private final PluginManager pluginManager =
context.mock(PluginManager.class);
private final MailboxSettingsManager mailboxSettingsManager =
context.mock(MailboxSettingsManager.class);
private final MailboxUpdateManager mailboxUpdateManager =
context.mock(MailboxUpdateManager.class);
private final MailboxClientFactory mailboxClientFactory =
context.mock(MailboxClientFactory.class);
private final TorReachabilityMonitor reachabilityMonitor =
context.mock(TorReachabilityMonitor.class);
private final DuplexPlugin plugin = context.mock(DuplexPlugin.class);
private final MailboxClient ownClient =
context.mock(MailboxClient.class, "ownClient");
private final MailboxClient contactClient =
context.mock(MailboxClient.class, "contactClient");
private final MailboxClientManager manager =
new MailboxClientManager(eventExecutor, dbExecutor, db,
contactManager, pluginManager, mailboxSettingsManager,
mailboxUpdateManager, mailboxClientFactory,
reachabilityMonitor);
private final Contact contact = getContact();
private final List<MailboxVersion> incompatibleVersions =
singletonList(new MailboxVersion(999, 0));
private final MailboxProperties ownProperties =
getMailboxProperties(true, CLIENT_SUPPORTS);
private final MailboxProperties localProperties =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final MailboxProperties remoteProperties =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final MailboxProperties remotePropertiesForNewMailbox =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final MailboxUpdate localUpdateWithoutMailbox =
new MailboxUpdate(CLIENT_SUPPORTS);
private final MailboxUpdate remoteUpdateWithoutMailbox =
new MailboxUpdate(CLIENT_SUPPORTS);
private final MailboxUpdate remoteUpdateWithIncompatibleClientVersions =
new MailboxUpdate(incompatibleVersions);
private final MailboxUpdateWithMailbox localUpdateWithMailbox =
new MailboxUpdateWithMailbox(CLIENT_SUPPORTS, localProperties);
private final MailboxUpdateWithMailbox remoteUpdateWithMailbox =
new MailboxUpdateWithMailbox(CLIENT_SUPPORTS, remoteProperties);
private final MailboxUpdateWithMailbox remoteUpdateWithNewMailbox =
new MailboxUpdateWithMailbox(CLIENT_SUPPORTS,
remotePropertiesForNewMailbox);
@Test
public void testLoadsMailboxUpdatesAtStartupWhenOffline() throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We're offline so there's nothing
// else to do.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithoutMailbox, null);
expectCheckPluginState(ENABLING);
manager.startService();
// At shutdown there should be no clients to destroy. The manager
// should destroy the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testLoadsMailboxUpdatesAtStartupWhenOnline() throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We're online but we don't have a
// mailbox and neither does the contact, so there's nothing else to do.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithoutMailbox, null);
expectCheckPluginState(ACTIVE);
manager.startService();
// At shutdown there should be no clients to destroy. The manager
// should destroy the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testAssignsContactToOurMailboxIfContactHasNoMailbox()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox but the contact
// doesn't.
//
// We're online, so the manager should create a client for our own
// mailbox and assign the contact to it for upload and download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithoutMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
expectAssignContactToOwnMailboxForUpload();
manager.startService();
// At shutdown the manager should destroy the client for our own
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testDoesNotAssignContactToOurMailboxIfContactHasNotSentUpdate()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox but the contact
// has never sent us an update, so the remote update is null.
//
// We're online, so the manager should create a client for our own
// mailbox. We don't know what API versions the contact supports,
// if any, so the contact should not be assigned to our mailbox.
expectLoadUpdates(localUpdateWithMailbox, null, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForOwnMailbox();
manager.startService();
// At shutdown the manager should destroy the client for our own
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testDoesNotAssignContactToOurMailboxIfContactHasIncompatibleClientVersions()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox but the contact
// doesn't. The contact's client API versions are incompatible with
// our mailbox.
//
// We're online, so the manager should create a client for our own
// mailbox. The contact's client API versions are incompatible with
// our mailbox, so the contact should not be assigned to our mailbox.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithIncompatibleClientVersions, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForOwnMailbox();
manager.startService();
// At shutdown the manager should destroy the client for our own
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testAssignsContactToContactMailboxIfWeHaveNoMailbox()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We don't have a mailbox but the
// contact does.
//
// We're online, so the manager should create a client for the
// contact's mailbox and assign the contact to it for upload and
// download.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithMailbox, null);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForDownload(remoteProperties);
expectAssignContactToContactMailboxForUpload(remoteProperties);
manager.startService();
// At shutdown the manager should destroy the client for the contact's
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testAssignsContactToBothMailboxesIfWeBothHaveMailboxes()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox and so does the
// contact.
//
// We're online, so the manager should create clients for the
// contact's mailbox and our mailbox. The manager should assign the
// contact to the contact's mailbox for upload and our mailbox for
// download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
manager.startService();
// At shutdown the manager should destroy the client for the contact's
// mailbox, the client for our own mailbox, and the reachability
// monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testCreatesClientsWhenTorBecomesActive() throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox but the contact
// doesn't. We're offline so there's nothing else to do.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithoutMailbox, ownProperties);
expectCheckPluginState(ENABLING);
manager.startService();
// When we come online, the manager should create a client for our own
// mailbox and assign the contact to it for upload and download.
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
expectAssignContactToOwnMailboxForUpload();
manager.eventOccurred(new TransportActiveEvent(ID));
// When we go offline, the manager should destroy the client for our
// own mailbox.
expectDestroyClientForOwnMailbox();
manager.eventOccurred(new TransportInactiveEvent(ID));
// At shutdown the manager should destroy the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testAssignsContactToOurMailboxWhenPaired() throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We're online but we don't have a
// mailbox and neither does the contact, so there's nothing else to do.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithoutMailbox, null);
expectCheckPluginState(ACTIVE);
manager.startService();
// When we pair a mailbox, the manager should create a client for our
// mailbox and assign the contact to our mailbox for upload and
// download.
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForUpload();
expectAssignContactToOwnMailboxForDownload();
manager.eventOccurred(new MailboxPairedEvent(ownProperties,
singletonMap(contact.getId(), localUpdateWithMailbox)));
// At shutdown the manager should destroy the client for our mailbox
// and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testReassignsContactToOurMailboxWhenPaired() throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. The contact has a mailbox but we
// don't.
//
// We're online, so the manager should create a client for the
// contact's mailbox and assign the contact to it for upload and
// download.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithMailbox, null);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForDownload(remoteProperties);
expectAssignContactToContactMailboxForUpload(remoteProperties);
manager.startService();
// When we pair a mailbox, the manager should create a client for our
// mailbox and reassign the contact to our mailbox for download.
expectCreateClientForOwnMailbox();
expectDeassignContactFromContactMailboxForDownload();
expectAssignContactToOwnMailboxForDownload();
manager.eventOccurred(new MailboxPairedEvent(ownProperties,
singletonMap(contact.getId(), localUpdateWithMailbox)));
// At shutdown the manager should destroy the client for the contact's
// mailbox, the client for our own mailbox, and the reachability
// monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testDoesNotAssignContactWhenPairedIfContactHasNotSentUpdate()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We don't have a mailbox and the
// contact has never sent us an update, so the remote update is null.
expectLoadUpdates(localUpdateWithoutMailbox, null, null);
expectCheckPluginState(ACTIVE);
manager.startService();
// When we pair a mailbox, the manager should create a client for our
// mailbox. We don't know whether the contact can use our mailbox, so
// the contact should not be assigned to our mailbox.
expectCreateClientForOwnMailbox();
manager.eventOccurred(new MailboxPairedEvent(ownProperties,
singletonMap(contact.getId(), localUpdateWithMailbox)));
// At shutdown the manager should destroy the client for our mailbox
// and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testDoesNotAssignContactWhenPairedIfContactHasIncompatibleClientVersions()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We don't have a mailbox and neither
// does the contact. The contact's client API versions are
// incompatible with our mailbox.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithIncompatibleClientVersions, null);
expectCheckPluginState(ACTIVE);
manager.startService();
// When we pair a mailbox, the manager should create a client for our
// mailbox. The contact's client API versions are incompatible with
// our mailbox, so the contact should not be assigned to our mailbox.
expectCreateClientForOwnMailbox();
manager.eventOccurred(new MailboxPairedEvent(ownProperties,
singletonMap(contact.getId(), localUpdateWithMailbox)));
// At shutdown the manager should destroy the client for our mailbox
// and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testReassignsContactToContactMailboxWhenUnpaired()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox and so does the
// contact.
//
// We're online, so the manager should create clients for the
// contact's mailbox and our mailbox. The manager should assign the
// contact to the contact's mailbox for upload and our mailbox for
// download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
manager.startService();
// When we unpair our mailbox, the manager should destroy the client
// for our mailbox and reassign the contact to the contact's mailbox
// for download.
expectDestroyClientForOwnMailbox();
expectAssignContactToContactMailboxForDownload(remoteProperties);
manager.eventOccurred(new MailboxUnpairedEvent(
singletonMap(contact.getId(), localUpdateWithoutMailbox)));
// At shutdown the manager should destroy the client for the contact's
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testDeassignsContactForUploadAndDownloadWhenContactRemoved()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox but the contact
// doesn't.
//
// We're online, so the manager should create a client for our mailbox.
// The manager should assign the contact to our mailbox for upload and
// download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithoutMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForUpload();
expectAssignContactToOwnMailboxForDownload();
manager.startService();
// When the contact is removed, the manager should deassign the contact
// from our mailbox for upload and download.
expectDeassignContactFromOwnMailboxForUpload();
expectDeassignContactFromOwnMailboxForDownload();
manager.eventOccurred(new ContactRemovedEvent(contact.getId()));
// At shutdown the manager should destroy the client for our mailbox
// and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testDeassignsContactForDownloadWhenContactRemoved()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox and so does the
// contact.
//
// We're online, so the manager should create clients for the
// contact's mailbox and our mailbox. The manager should assign the
// contact to the contact's mailbox for upload and our mailbox for
// download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
manager.startService();
// When the contact is removed, the manager should destroy the client
// for the contact's mailbox and deassign the contact from our mailbox
// for download.
expectDestroyClientForContactMailbox();
expectDeassignContactFromOwnMailboxForDownload();
manager.eventOccurred(new ContactRemovedEvent(contact.getId()));
// At shutdown the manager should destroy the client for our mailbox
// and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testAssignsContactToContactMailboxWhenContactPairsMailbox()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We're online but we don't have a
// mailbox and neither does the contact, so there's nothing else to do.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithoutMailbox, null);
expectCheckPluginState(ACTIVE);
manager.startService();
// When the contact pairs a mailbox, the manager should create a client
// for the contact's mailbox and assign the contact to the contact's
// mailbox for upload and download.
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectAssignContactToContactMailboxForDownload(remoteProperties);
manager.eventOccurred(new RemoteMailboxUpdateEvent(contact.getId(),
remoteUpdateWithMailbox));
// At shutdown the manager should destroy the client for the contact's
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testReassignsContactForUploadWhenContactPairsMailbox()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox but the contact
// doesn't.
//
// We're online, so the manager should create a client for our mailbox.
// The manager should assign the contact to our mailbox for upload and
// download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithoutMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForUpload();
expectAssignContactToOwnMailboxForDownload();
manager.startService();
// When the contact pairs a mailbox, the manager should create a client
// for the contact's mailbox and reassign the contact to the contact's
// mailbox for upload.
expectCreateClientForContactMailbox();
expectDeassignContactFromOwnMailboxForUpload();
expectAssignContactToContactMailboxForUpload(remoteProperties);
manager.eventOccurred(new RemoteMailboxUpdateEvent(contact.getId(),
remoteUpdateWithMailbox));
// At shutdown the manager should destroy the client for the contact's
// mailbox, the client for our own mailbox, and the reachability
// monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testReassignsContactForUploadWhenContactUnpairsMailbox()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox and so does the
// contact.
//
// We're online, so the manager should create clients for the
// contact's mailbox and our mailbox. The manager should assign the
// contact to the contact's mailbox for upload and our mailbox for
// download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
manager.startService();
// When the contact unpairs their mailbox, the manager should destroy
// the client for the contact's mailbox and reassign the contact to
// our mailbox for upload.
expectDestroyClientForContactMailbox();
expectAssignContactToOwnMailboxForUpload();
manager.eventOccurred(new RemoteMailboxUpdateEvent(contact.getId(),
remoteUpdateWithoutMailbox));
// At shutdown the manager should destroy the client for our mailbox
// and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testReassignsContactForUploadWhenContactReplacesMailbox()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox and so does the
// contact.
//
// We're online, so the manager should create clients for the
// contact's mailbox and our mailbox. The manager should assign the
// contact to the contact's mailbox for upload and our mailbox for
// download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
manager.startService();
// When the contact replaces their mailbox, the manager should replace
// the client for the contact's mailbox and assign the contact to
// the contact's new mailbox for upload.
expectDestroyClientForContactMailbox();
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(
remotePropertiesForNewMailbox);
manager.eventOccurred(new RemoteMailboxUpdateEvent(contact.getId(),
remoteUpdateWithNewMailbox));
// At shutdown the manager should destroy the client for the contact's
// mailbox, the client for our own mailbox, and the reachability
// monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testReassignsContactWhenContactReplacesMailbox()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We don't have a mailbox but the
// contact does.
//
// We're online, so the manager should create a client for the
// contact's mailbox and assign the contact to the contact's mailbox
// for upload and download.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithMailbox, null);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectAssignContactToContactMailboxForDownload(remoteProperties);
manager.startService();
// When the contact replaces their mailbox, the manager should replace
// the client for the contact's mailbox and assign the contact to
// the contact's new mailbox for upload and download.
expectDestroyClientForContactMailbox();
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(
remotePropertiesForNewMailbox);
expectAssignContactToContactMailboxForDownload(
remotePropertiesForNewMailbox);
manager.eventOccurred(new RemoteMailboxUpdateEvent(contact.getId(),
remoteUpdateWithNewMailbox));
// At shutdown the manager should destroy the client for the contact's
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testDoesNotReassignContactWhenRemotePropertiesAreUnchanged()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We don't have a mailbox but the
// contact does.
//
// We're online, so the manager should create a client for the
// contact's mailbox and assign the contact to the contact's mailbox
// for upload and download.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithMailbox, null);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectAssignContactToContactMailboxForDownload(remoteProperties);
manager.startService();
// When the contact sends an update with unchanged properties, the
// clients and assignments should not be affected.
manager.eventOccurred(new RemoteMailboxUpdateEvent(contact.getId(),
remoteUpdateWithMailbox));
// At shutdown the manager should destroy the client for the contact's
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testAssignsContactToOurMailboxWhenClientVersionsBecomeCompatible()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox but the contact
// doesn't. The contact's client API versions are incompatible with
// our mailbox.
//
// We're online, so the manager should create a client for our own
// mailbox. The contact's client API versions are incompatible with
// our mailbox, so the contact should not be assigned to our mailbox.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithIncompatibleClientVersions, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForOwnMailbox();
manager.startService();
// When the contact sends an update indicating that their client API
// versions are now compatible with our mailbox, the manager should
// assign the contact to our mailbox for upload and download.
expectAssignContactToOwnMailboxForUpload();
expectAssignContactToOwnMailboxForDownload();
manager.eventOccurred(new RemoteMailboxUpdateEvent(contact.getId(),
remoteUpdateWithoutMailbox));
// At shutdown the manager should destroy the client for our own
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testRecreatesClientsWhenOwnMailboxServerVersionsChange()
throws Exception {
long now = System.currentTimeMillis();
List<MailboxVersion> compatibleVersions =
new ArrayList<>(CLIENT_SUPPORTS);
compatibleVersions.add(new MailboxVersion(999, 0));
MailboxStatus mailboxStatus =
new MailboxStatus(now, now, 0, compatibleVersions);
// At startup the manager should load the local and remote updates
// and our own mailbox properties. The contact has a mailbox, so the
// remote update contains the properties received from the contact.
// We also have a mailbox, so the local update contains the properties
// we sent to the contact.
//
// We're online, so the manager should create clients for the
// contact's mailbox and our mailbox. The manager should assign the
// contact to the contact's mailbox for upload and our mailbox for
// download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
manager.startService();
// When we learn that our mailbox's API versions have changed, the
// manager should destroy and recreate the clients for our own mailbox
// and the contact's mailbox and assign the contact to the new clients.
expectDestroyClientForContactMailbox();
expectDestroyClientForOwnMailbox();
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
manager.eventOccurred(
new OwnMailboxConnectionStatusEvent(mailboxStatus));
// At shutdown the manager should destroy the client for the contact's
// mailbox, the client for our own mailbox, and the reachability
// monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
private void expectLoadUpdates(MailboxUpdate local,
@Nullable MailboxUpdate remote,
@Nullable MailboxProperties own) throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(reachabilityMonitor).start();
oneOf(dbExecutor).execute(with(any(Runnable.class)));
will(new RunAction());
oneOf(db).transaction(with(true), withDbRunnable(txn));
oneOf(contactManager).getContacts(txn);
will(returnValue(singletonList(contact)));
oneOf(mailboxUpdateManager).getLocalUpdate(txn, contact.getId());
will(returnValue(local));
oneOf(mailboxUpdateManager).getRemoteUpdate(txn, contact.getId());
will(returnValue(remote));
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
will(returnValue(own));
}});
}
private void expectCheckPluginState(State state) {
context.checking(new Expectations() {{
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).getState();
will(returnValue(state));
}});
}
private void expectCreateClientForOwnMailbox() {
context.checking(new Expectations() {{
oneOf(mailboxClientFactory).createOwnMailboxClient(
reachabilityMonitor, ownProperties);
will(returnValue(ownClient));
oneOf(ownClient).start();
}});
}
private void expectCreateClientForContactMailbox() {
context.checking(new Expectations() {{
oneOf(mailboxClientFactory)
.createContactMailboxClient(reachabilityMonitor);
will(returnValue(contactClient));
oneOf(contactClient).start();
}});
}
private void expectAssignContactToOwnMailboxForDownload() {
context.checking(new Expectations() {{
oneOf(ownClient).assignContactForDownload(contact.getId(),
ownProperties,
requireNonNull(localProperties.getOutboxId()));
}});
}
private void expectAssignContactToOwnMailboxForUpload() {
context.checking(new Expectations() {{
oneOf(ownClient).assignContactForUpload(contact.getId(),
ownProperties,
requireNonNull(localProperties.getInboxId()));
}});
}
private void expectAssignContactToContactMailboxForDownload(
MailboxProperties remoteProperties) {
context.checking(new Expectations() {{
oneOf(contactClient).assignContactForDownload(contact.getId(),
remoteProperties,
requireNonNull(remoteProperties.getInboxId()));
}});
}
private void expectAssignContactToContactMailboxForUpload(
MailboxProperties remoteProperties) {
context.checking(new Expectations() {{
oneOf(contactClient).assignContactForUpload(contact.getId(),
remoteProperties,
requireNonNull(remoteProperties.getOutboxId()));
}});
}
private void expectDeassignContactFromOwnMailboxForUpload() {
context.checking(new Expectations() {{
oneOf(ownClient).deassignContactForUpload(contact.getId());
}});
}
private void expectDeassignContactFromOwnMailboxForDownload() {
context.checking(new Expectations() {{
oneOf(ownClient).deassignContactForDownload(contact.getId());
}});
}
private void expectDeassignContactFromContactMailboxForDownload() {
context.checking(new Expectations() {{
oneOf(contactClient).deassignContactForDownload(contact.getId());
}});
}
private void expectRunTaskOnEventExecutor() {
context.checking(new Expectations() {{
oneOf(eventExecutor).execute(with(any(Runnable.class)));
will(new RunAction());
}});
}
private void expectDestroyClientForOwnMailbox() {
context.checking(new Expectations() {{
oneOf(ownClient).destroy();
}});
}
private void expectDestroyClientForContactMailbox() {
context.checking(new Expectations() {{
oneOf(contactClient).destroy();
}});
}
private void expectDestroyReachabilityMonitor() {
context.checking(new Expectations() {{
oneOf(reachabilityMonitor).destroy();
}});
}
}

View File

@@ -1,130 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.mailbox.MailboxFileId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.jmock.Expectations;
import org.jmock.lib.action.DoAllAction;
import org.junit.After;
import org.junit.Before;
import java.io.File;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Arrays.asList;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
abstract class MailboxDownloadWorkerTest<W extends MailboxDownloadWorker>
extends BrambleMockTestCase {
final ConnectivityChecker connectivityChecker =
context.mock(ConnectivityChecker.class);
final TorReachabilityMonitor torReachabilityMonitor =
context.mock(TorReachabilityMonitor.class);
final MailboxApiCaller mailboxApiCaller =
context.mock(MailboxApiCaller.class);
final MailboxApi mailboxApi = context.mock(MailboxApi.class);
final MailboxFileManager mailboxFileManager =
context.mock(MailboxFileManager.class);
private final Cancellable apiCall = context.mock(Cancellable.class);
private final long now = System.currentTimeMillis();
final MailboxFile file1 =
new MailboxFile(new MailboxFileId(getRandomId()), now - 1);
final MailboxFile file2 =
new MailboxFile(new MailboxFileId(getRandomId()), now);
final List<MailboxFile> files = asList(file1, file2);
private File testDir, tempFile;
MailboxProperties mailboxProperties;
W worker;
@Before
public void setUp() {
testDir = getTestDirectory();
tempFile = new File(testDir, "temp");
}
@After
public void tearDown() {
deleteTestDirectory(testDir);
}
void expectStartConnectivityCheck() {
context.checking(new Expectations() {{
oneOf(connectivityChecker).checkConnectivity(
with(mailboxProperties), with(worker));
}});
}
void expectStartTask(AtomicReference<ApiCall> task) {
context.checking(new Expectations() {{
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(task, ApiCall.class, 0),
returnValue(apiCall)
));
}});
}
void expectCheckForFoldersWithAvailableFiles(
List<MailboxFolderId> folderIds) throws Exception {
context.checking(new Expectations() {{
oneOf(mailboxApi).getFolders(mailboxProperties);
will(returnValue(folderIds));
}});
}
void expectCheckForFiles(MailboxFolderId folderId,
List<MailboxFile> files) throws Exception {
context.checking(new Expectations() {{
oneOf(mailboxApi).getFiles(mailboxProperties, folderId);
will(returnValue(files));
}});
}
void expectDownloadFile(MailboxFolderId folderId,
MailboxFile file)
throws Exception {
context.checking(new Expectations() {{
oneOf(mailboxFileManager).createTempFileForDownload();
will(returnValue(tempFile));
oneOf(mailboxApi).getFile(mailboxProperties, folderId, file.name,
tempFile);
oneOf(mailboxFileManager).handleDownloadedFile(tempFile);
}});
}
void expectDeleteFile(MailboxFolderId folderId, MailboxFile file,
boolean tolerableFailure) throws Exception {
context.checking(new Expectations() {{
oneOf(mailboxApi).deleteFile(mailboxProperties, folderId,
file.name);
if (tolerableFailure) {
will(throwException(new TolerableFailureException()));
}
}});
}
void expectAddReachabilityObserver() {
context.checking(new Expectations() {{
oneOf(torReachabilityMonitor).addOneShotObserver(worker);
}});
}
void expectRemoveObservers() {
context.checking(new Expectations() {{
oneOf(connectivityChecker).removeObserver(worker);
oneOf(torReachabilityMonitor).removeObserver(worker);
}});
}
}

Some files were not shown because too many files have changed in this diff Show More