diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/Supplier.java b/bramble-api/src/main/java/org/briarproject/bramble/api/Supplier.java deleted file mode 100644 index 7a83bddb6..000000000 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/Supplier.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.briarproject.bramble.api; - -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; - -@NotNullByDefault -public interface Supplier { - - T get(); -} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxProperties.java b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxProperties.java index 714dbca71..5692c9031 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxProperties.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxProperties.java @@ -4,6 +4,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import java.util.List; +import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @Immutable @@ -14,13 +15,36 @@ public class MailboxProperties { private final MailboxAuthToken authToken; private final boolean owner; private final List serverSupports; + @Nullable + private final MailboxFolderId inboxId; // Null for own mailbox + @Nullable + private final MailboxFolderId outboxId; // Null for own mailbox + /** + * Constructor for properties used by the mailbox's owner. + */ public MailboxProperties(String baseUrl, MailboxAuthToken authToken, - boolean owner, List serverSupports) { + List serverSupports) { this.baseUrl = baseUrl; this.authToken = authToken; - this.owner = owner; + this.owner = true; this.serverSupports = serverSupports; + this.inboxId = null; + this.outboxId = null; + } + + /** + * Constructor for properties used by a contact of the mailbox's owner. + */ + public MailboxProperties(String baseUrl, MailboxAuthToken authToken, + List serverSupports, MailboxFolderId inboxId, + MailboxFolderId outboxId) { + this.baseUrl = baseUrl; + this.authToken = authToken; + this.owner = false; + this.serverSupports = serverSupports; + this.inboxId = inboxId; + this.outboxId = outboxId; } public String getBaseUrl() { @@ -43,4 +67,14 @@ public class MailboxProperties { public List getServerSupports() { return serverSupports; } + + @Nullable + public MailboxFolderId getInboxId() { + return inboxId; + } + + @Nullable + public MailboxFolderId getOutboxId() { + return outboxId; + } } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxUpdateWithMailbox.java b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxUpdateWithMailbox.java index 5e3110afa..87cad6b59 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxUpdateWithMailbox.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxUpdateWithMailbox.java @@ -9,48 +9,22 @@ import javax.annotation.concurrent.Immutable; @Immutable @NotNullByDefault public class MailboxUpdateWithMailbox extends MailboxUpdate { - private final List serverSupports; - private final String onion; - private final MailboxAuthToken authToken; - private final MailboxFolderId inboxId; - private final MailboxFolderId outboxId; + + private final MailboxProperties properties; public MailboxUpdateWithMailbox(List clientSupports, - List serverSupports, String onion, - MailboxAuthToken authToken, MailboxFolderId inboxId, - MailboxFolderId outboxId - ) { + MailboxProperties properties) { super(clientSupports, true); - this.serverSupports = serverSupports; - this.onion = onion; - this.authToken = authToken; - this.inboxId = inboxId; - this.outboxId = outboxId; + if (properties.isOwner()) throw new IllegalArgumentException(); + this.properties = properties; } public MailboxUpdateWithMailbox(MailboxUpdateWithMailbox o, List newClientSupports) { - this(newClientSupports, o.serverSupports, o.onion, o.authToken, - o.inboxId, o.outboxId); + this(newClientSupports, o.getMailboxProperties()); } - public String getOnion() { - return onion; - } - - public MailboxAuthToken getAuthToken() { - return authToken; - } - - public MailboxFolderId getInboxId() { - return inboxId; - } - - public MailboxFolderId getOutboxId() { - return outboxId; - } - - public List getServerSupports() { - return serverSupports; + public MailboxProperties getMailboxProperties() { + return properties; } } diff --git a/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java b/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java index 0b9c646a4..e2ae3cb91 100644 --- a/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java +++ b/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java @@ -20,9 +20,12 @@ import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.Identity; import org.briarproject.bramble.api.identity.LocalAuthor; +import org.briarproject.bramble.api.mailbox.MailboxAuthToken; +import org.briarproject.bramble.api.mailbox.MailboxFolderId; import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxUpdate; import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox; +import org.briarproject.bramble.api.mailbox.MailboxVersion; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.sync.ClientId; @@ -223,6 +226,19 @@ public class TestUtils { getAgreementPublicKey(), verified); } + public static MailboxProperties getMailboxProperties(boolean owner, + List serverSupports) { + String baseUrl = "http://" + getRandomString(56) + ".onion"; // TODO + MailboxAuthToken authToken = new MailboxAuthToken(getRandomId()); + if (owner) { + return new MailboxProperties(baseUrl, authToken, serverSupports); + } + MailboxFolderId inboxId = new MailboxFolderId(getRandomId()); + MailboxFolderId outboxId = new MailboxFolderId(getRandomId()); + return new MailboxProperties(baseUrl, authToken, serverSupports, + inboxId, outboxId); + } + public static void writeBytes(File file, byte[] bytes) throws IOException { FileOutputStream outputStream = new FileOutputStream(file); @@ -275,7 +291,7 @@ public class TestUtils { return Math.sqrt(getVariance(samples)); } - public static boolean isOptionalTestEnabled(Class testClass) { + public static boolean isOptionalTestEnabled(Class testClass) { String optionalTests = System.getenv("OPTIONAL_TESTS"); return optionalTests != null && asList(optionalTests.split(",")).contains(testClass.getName()); @@ -292,11 +308,8 @@ public class TestUtils { MailboxUpdateWithMailbox am = (MailboxUpdateWithMailbox) a; MailboxUpdateWithMailbox bm = (MailboxUpdateWithMailbox) b; return am.getClientSupports().equals(bm.getClientSupports()) && - am.getServerSupports().equals(bm.getServerSupports()) && - am.getOnion().equals(bm.getOnion()) && - am.getAuthToken().equals(bm.getAuthToken()) && - am.getInboxId().equals(bm.getInboxId()) && - am.getOutboxId().equals(bm.getOutboxId()); + mailboxPropertiesEqual(am.getMailboxProperties(), + bm.getMailboxProperties()); } return false; } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/client/ClientHelperImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/client/ClientHelperImpl.java index 584986c0f..a13f80fde 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/client/ClientHelperImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/client/ClientHelperImpl.java @@ -25,6 +25,7 @@ import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.AuthorFactory; import org.briarproject.bramble.api.mailbox.MailboxAuthToken; import org.briarproject.bramble.api.mailbox.MailboxFolderId; +import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxUpdate; import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox; import org.briarproject.bramble.api.mailbox.MailboxVersion; @@ -455,9 +456,11 @@ class ClientHelperImpl implements ClientHelper { checkLength(inboxId, UniqueId.LENGTH); byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID); checkLength(outboxId, UniqueId.LENGTH); - return new MailboxUpdateWithMailbox(clientSupportsList, - serverSupportsList, onion, new MailboxAuthToken(authToken), + String baseUrl = "http://" + onion + ".onion"; // TODO + MailboxProperties props = new MailboxProperties(baseUrl, + new MailboxAuthToken(authToken), serverSupportsList, new MailboxFolderId(inboxId), new MailboxFolderId(outboxId)); + return new MailboxUpdateWithMailbox(clientSupportsList, props); } @Override diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/ApiCall.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/ApiCall.java new file mode 100644 index 000000000..9c47161ab --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/ApiCall.java @@ -0,0 +1,21 @@ +package org.briarproject.bramble.mailbox; + +import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException; + +/** + * An interface for calling an API endpoint with the option to retry the call. + */ +interface ApiCall { + + /** + * This method makes a synchronous call to an API endpoint and returns + * true if the call should be retried, in which case the method may be + * called again on the same {@link ApiCall} instance after a delay. + * + * @return True if the API call needs to be retried, or false if the API + * call succeeded or {@link TolerableFailureException failed tolerably}. + */ + @IoExecutor + boolean callApi(); +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/ConnectivityChecker.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/ConnectivityChecker.java new file mode 100644 index 000000000..921557027 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/ConnectivityChecker.java @@ -0,0 +1,32 @@ +package org.briarproject.bramble.mailbox; + +import org.briarproject.bramble.api.mailbox.MailboxProperties; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.ThreadSafe; + +/** + * An interface for checking whether a mailbox is reachable. + */ +@ThreadSafe +@NotNullByDefault +interface ConnectivityChecker { + + /** + * Destroys the checker. Any current connectivity check is cancelled. + */ + void destroy(); + + /** + * Starts a connectivity check if needed and calls the given observer when + * the check succeeds. If a check is already running then the observer is + * called when the check succeeds. If a connectivity check has recently + * succeeded then the observer is called immediately. + */ + void checkConnectivity(MailboxProperties properties, + ConnectivityObserver o); + + interface ConnectivityObserver { + void onConnectivityCheckSucceeded(); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/ConnectivityCheckerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/ConnectivityCheckerImpl.java new file mode 100644 index 000000000..1468c5db1 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/ConnectivityCheckerImpl.java @@ -0,0 +1,111 @@ +package org.briarproject.bramble.mailbox; + +import org.briarproject.bramble.api.Cancellable; +import org.briarproject.bramble.api.mailbox.MailboxProperties; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.system.Clock; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; +import javax.annotation.concurrent.ThreadSafe; + +@ThreadSafe +@NotNullByDefault +abstract class ConnectivityCheckerImpl implements ConnectivityChecker { + + /** + * If no more than this much time has elapsed since the last connectivity + * check succeeded, consider the result to be fresh and don't check again. + *

+ * Package access for testing. + */ + static final long CONNECTIVITY_CHECK_FRESHNESS_MS = 10_000; + + private final Object lock = new Object(); + + protected final Clock clock; + private final MailboxApiCaller mailboxApiCaller; + + @GuardedBy("lock") + private boolean destroyed = false; + + @GuardedBy("lock") + @Nullable + private Cancellable connectivityCheck = null; + + @GuardedBy("lock") + private long lastConnectivityCheckSucceeded = 0; + + @GuardedBy("lock") + private final List connectivityObservers = + new ArrayList<>(); + + /** + * Creates an {@link ApiCall} for checking whether the mailbox is + * reachable. The {@link ApiCall} should call + * {@link #onConnectivityCheckSucceeded(long)} if the check succeeds. + */ + abstract ApiCall createConnectivityCheckTask(MailboxProperties properties); + + ConnectivityCheckerImpl(Clock clock, MailboxApiCaller mailboxApiCaller) { + this.clock = clock; + this.mailboxApiCaller = mailboxApiCaller; + } + + @Override + public void destroy() { + synchronized (lock) { + destroyed = true; + connectivityObservers.clear(); + if (connectivityCheck != null) { + connectivityCheck.cancel(); + connectivityCheck = null; + } + } + } + + @Override + public void checkConnectivity(MailboxProperties properties, + ConnectivityObserver o) { + boolean callNow = false; + synchronized (lock) { + if (destroyed) return; + if (connectivityCheck == null) { + // No connectivity check is running + long now = clock.currentTimeMillis(); + if (now - lastConnectivityCheckSucceeded + > CONNECTIVITY_CHECK_FRESHNESS_MS) { + // The last connectivity check is stale, start a new one + connectivityObservers.add(o); + ApiCall task = + createConnectivityCheckTask(properties); + connectivityCheck = mailboxApiCaller.retryWithBackoff(task); + } else { + // The last connectivity check is fresh + callNow = true; + } + } else { + // A connectivity check is running, wait for it to succeed + connectivityObservers.add(o); + } + } + if (callNow) o.onConnectivityCheckSucceeded(); + } + + protected void onConnectivityCheckSucceeded(long now) { + List observers; + synchronized (lock) { + if (destroyed) return; + connectivityCheck = null; + lastConnectivityCheckSucceeded = now; + observers = new ArrayList<>(connectivityObservers); + connectivityObservers.clear(); + } + for (ConnectivityObserver o : observers) { + o.onConnectivityCheckSucceeded(); + } + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/ContactMailboxConnectivityChecker.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/ContactMailboxConnectivityChecker.java new file mode 100644 index 000000000..8759aa595 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/ContactMailboxConnectivityChecker.java @@ -0,0 +1,39 @@ +package org.briarproject.bramble.mailbox; + +import org.briarproject.bramble.api.mailbox.MailboxProperties; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.mailbox.MailboxApi.ApiException; + +import java.io.IOException; + +import javax.annotation.concurrent.ThreadSafe; + +@ThreadSafe +@NotNullByDefault +class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl { + + private final MailboxApi mailboxApi; + + ContactMailboxConnectivityChecker(Clock clock, + MailboxApiCaller mailboxApiCaller, MailboxApi mailboxApi) { + super(clock, mailboxApiCaller); + this.mailboxApi = mailboxApi; + } + + @Override + ApiCall createConnectivityCheckTask(MailboxProperties properties) { + if (properties.isOwner()) throw new IllegalArgumentException(); + return new SimpleApiCall() { + @Override + void tryToCallApi() throws IOException, ApiException { + if (!mailboxApi.checkStatus(properties)) { + throw new ApiException(); + } + // Call the observers and cache the result + onConnectivityCheckSucceeded(clock.currentTimeMillis()); + } + }; + } + +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApiCaller.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApiCaller.java index 7280eb0a1..a3888e01c 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApiCaller.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApiCaller.java @@ -1,10 +1,8 @@ package org.briarproject.bramble.mailbox; import org.briarproject.bramble.api.Cancellable; -import org.briarproject.bramble.api.Supplier; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException; import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.MINUTES; @@ -23,15 +21,12 @@ interface MailboxApiCaller { long MAX_RETRY_INTERVAL_MS = DAYS.toMillis(1); /** - * Asynchronously calls the given supplier, automatically retrying at - * increasing intervals until the supplier returns false. The returned - * {@link Cancellable} can be used to cancel any future retries. + * Asynchronously calls the given API call on the {@link IoExecutor}, + * automatically retrying at increasing intervals until the API call + * returns false or retries are cancelled. * - * @param supplier A wrapper for an API call. The supplier's - * {@link Supplier#get() get()} method will be called on the - * {@link IoExecutor}. It should return true if the API call needs to be - * retried, or false if the API call succeeded or - * {@link TolerableFailureException failed tolerably}. + * @return A {@link Cancellable} that can be used to cancel any future + * retries. */ - Cancellable retryWithBackoff(Supplier supplier); + Cancellable retryWithBackoff(ApiCall apiCall); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApiCallerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApiCallerImpl.java index 9b476eb98..fd625b084 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApiCallerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApiCallerImpl.java @@ -1,7 +1,6 @@ package org.briarproject.bramble.mailbox; import org.briarproject.bramble.api.Cancellable; -import org.briarproject.bramble.api.Supplier; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.system.TaskScheduler; @@ -31,15 +30,15 @@ class MailboxApiCallerImpl implements MailboxApiCaller { } @Override - public Cancellable retryWithBackoff(Supplier supplier) { - Task task = new Task(supplier); + public Cancellable retryWithBackoff(ApiCall apiCall) { + Task task = new Task(apiCall); task.start(); return task; } private class Task implements Cancellable { - private final Supplier supplier; + private final ApiCall apiCall; private final Object lock = new Object(); @GuardedBy("lock") @@ -52,8 +51,8 @@ class MailboxApiCallerImpl implements MailboxApiCaller { @GuardedBy("lock") private long retryIntervalMs = MIN_RETRY_INTERVAL_MS; - private Task(Supplier supplier) { - this.supplier = supplier; + private Task(ApiCall apiCall) { + this.apiCall = apiCall; } private void start() { @@ -68,8 +67,8 @@ class MailboxApiCallerImpl implements MailboxApiCaller { synchronized (lock) { if (cancelled) return; } - // The supplier returns true if we should retry - if (supplier.get()) { + // The call returns true if we should retry + if (apiCall.callApi()) { synchronized (lock) { if (cancelled) return; scheduledTask = taskScheduler.schedule(this::callApi, diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApiImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApiImpl.java index e96015f9c..d864c7502 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApiImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApiImpl.java @@ -95,7 +95,7 @@ class MailboxApiImpl implements MailboxApi { } return new MailboxProperties(properties.getBaseUrl(), MailboxAuthToken.fromString(tokenNode.textValue()), - true, parseServerSupports(node)); + parseServerSupports(node)); } catch (JacksonException | InvalidMailboxIdException e) { throw new ApiException(); } @@ -127,7 +127,6 @@ class MailboxApiImpl implements MailboxApi { @Override public boolean checkStatus(MailboxProperties properties) throws IOException, ApiException { - if (!properties.isOwner()) throw new IllegalArgumentException(); Response response = sendGetRequest(properties, "/status"); if (response.code() == 401) throw new ApiException(); return response.isSuccessful(); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImpl.java index 4d7eea749..91f0cd0b0 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImpl.java @@ -177,11 +177,10 @@ class MailboxPairingTaskImpl implements MailboxPairingTask { LOG.info("QR code is valid"); byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33); String onion = crypto.encodeOnion(onionPubKey); - String baseUrl = "http://" + onion + ".onion"; + String baseUrl = "http://" + onion + ".onion"; // TODO byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65); MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes); - return new MailboxProperties(baseUrl, setupToken, true, - new ArrayList<>()); + return new MailboxProperties(baseUrl, setupToken, new ArrayList<>()); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImpl.java index a1831bd75..9f05e2be7 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImpl.java @@ -31,6 +31,7 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager { // Package access for testing static final String SETTINGS_NAMESPACE = "mailbox"; + // TODO: This currently stores the base URL, not the 56-char onion address static final String SETTINGS_KEY_ONION = "onion"; static final String SETTINGS_KEY_TOKEN = "token"; static final String SETTINGS_KEY_SERVER_SUPPORTS = "serverSupports"; @@ -70,7 +71,7 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager { } try { MailboxAuthToken tokenId = MailboxAuthToken.fromString(token); - return new MailboxProperties(onion, tokenId, true, serverSupports); + return new MailboxProperties(onion, tokenId, serverSupports); } catch (InvalidMailboxIdException e) { throw new DbException(e); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxUpdateManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxUpdateManagerImpl.java index 174b979e3..a0794cf94 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxUpdateManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxUpdateManagerImpl.java @@ -242,11 +242,14 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager, private void createAndSendUpdateWithMailbox(Transaction txn, Contact c, List serverSupports, String ownOnion) throws DbException { - MailboxUpdate u = new MailboxUpdateWithMailbox( - clientSupports, serverSupports, ownOnion, + String baseUrl = "http://" + ownOnion + ".onion"; // TODO + MailboxProperties properties = new MailboxProperties(baseUrl, new MailboxAuthToken(crypto.generateUniqueId().getBytes()), + serverSupports, new MailboxFolderId(crypto.generateUniqueId().getBytes()), new MailboxFolderId(crypto.generateUniqueId().getBytes())); + MailboxUpdate u = + new MailboxUpdateWithMailbox(clientSupports, properties); Group g = getContactGroup(c); storeMessageReplaceLatest(txn, g.getId(), u); } @@ -325,11 +328,12 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager, BdfList serverSupports = new BdfList(); if (u.hasMailbox()) { MailboxUpdateWithMailbox um = (MailboxUpdateWithMailbox) u; - serverSupports = encodeSupportsList(um.getServerSupports()); - dict.put(PROP_KEY_ONION, um.getOnion()); - dict.put(PROP_KEY_AUTHTOKEN, um.getAuthToken().getBytes()); - dict.put(PROP_KEY_INBOXID, um.getInboxId().getBytes()); - dict.put(PROP_KEY_OUTBOXID, um.getOutboxId().getBytes()); + MailboxProperties properties = um.getMailboxProperties(); + serverSupports = encodeSupportsList(properties.getServerSupports()); + dict.put(PROP_KEY_ONION, properties.getOnion()); + dict.put(PROP_KEY_AUTHTOKEN, properties.getAuthToken()); + dict.put(PROP_KEY_INBOXID, properties.getInboxId()); + dict.put(PROP_KEY_OUTBOXID, properties.getOutboxId()); } return BdfList.of(version, encodeSupportsList(u.getClientSupports()), serverSupports, dict); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/OwnMailboxConnectivityChecker.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/OwnMailboxConnectivityChecker.java new file mode 100644 index 000000000..6abe9e607 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/OwnMailboxConnectivityChecker.java @@ -0,0 +1,75 @@ +package org.briarproject.bramble.mailbox; + +import org.briarproject.bramble.api.db.DbException; +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.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.mailbox.MailboxApi.ApiException; + +import java.io.IOException; +import java.util.logging.Logger; + +import javax.annotation.concurrent.ThreadSafe; + +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.LogUtils.logException; + +@ThreadSafe +@NotNullByDefault +class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl { + + private static final Logger LOG = + getLogger(OwnMailboxConnectivityChecker.class.getName()); + + private final MailboxApi mailboxApi; + private final TransactionManager db; + private final MailboxSettingsManager mailboxSettingsManager; + + OwnMailboxConnectivityChecker(Clock clock, + MailboxApiCaller mailboxApiCaller, + MailboxApi mailboxApi, + TransactionManager db, + MailboxSettingsManager mailboxSettingsManager) { + super(clock, mailboxApiCaller); + this.mailboxApi = mailboxApi; + this.db = db; + this.mailboxSettingsManager = mailboxSettingsManager; + } + + @Override + ApiCall createConnectivityCheckTask(MailboxProperties properties) { + if (!properties.isOwner()) throw new IllegalArgumentException(); + return () -> { + try { + return checkConnectivityAndStoreResult(properties); + } catch (DbException e) { + logException(LOG, WARNING, e); + return true; // Retry + } + }; + } + + private boolean checkConnectivityAndStoreResult( + MailboxProperties properties) throws DbException { + try { + if (!mailboxApi.checkStatus(properties)) throw new ApiException(); + LOG.info("Own mailbox is reachable"); + long now = clock.currentTimeMillis(); + db.transaction(false, txn -> mailboxSettingsManager + .recordSuccessfulConnection(txn, now)); + // Call the observers and cache the result + onConnectivityCheckSucceeded(now); + return false; // Don't retry + } catch (IOException | ApiException e) { + LOG.warning("Own mailbox is unreachable"); + logException(LOG, WARNING, e); + long now = clock.currentTimeMillis(); + db.transaction(false, txn -> mailboxSettingsManager + .recordFailedConnectionAttempt(txn, now)); + } + return true; // Retry + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/SimpleApiCall.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/SimpleApiCall.java index 14c508b33..7c3e6f4b0 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/SimpleApiCall.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/SimpleApiCall.java @@ -1,6 +1,5 @@ package org.briarproject.bramble.mailbox; -import org.briarproject.bramble.api.Supplier; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.mailbox.MailboxApi.ApiException; import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException; @@ -17,17 +16,17 @@ import static org.briarproject.bramble.util.LogUtils.logException; * Convenience class for making simple API calls that don't return values. */ @NotNullByDefault -public abstract class SimpleApiCall implements Supplier { +public abstract class SimpleApiCall implements ApiCall { private static final Logger LOG = getLogger(SimpleApiCall.class.getName()); - abstract void callApi() + abstract void tryToCallApi() throws IOException, ApiException, TolerableFailureException; @Override - public Boolean get() { + public boolean callApi() { try { - callApi(); + tryToCallApi(); return false; // Succeeded, don't retry } catch (IOException | ApiException e) { logException(LOG, WARNING, e); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/client/ClientHelperImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/client/ClientHelperImplTest.java index 9e316b07d..7c39128a1 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/client/ClientHelperImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/client/ClientHelperImplTest.java @@ -21,8 +21,7 @@ import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.AuthorFactory; -import org.briarproject.bramble.api.mailbox.MailboxAuthToken; -import org.briarproject.bramble.api.mailbox.MailboxFolderId; +import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxUpdate; import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox; import org.briarproject.bramble.api.mailbox.MailboxVersion; @@ -52,6 +51,7 @@ import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_ONION; import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_OUTBOXID; import static org.briarproject.bramble.test.TestUtils.getAuthor; +import static org.briarproject.bramble.test.TestUtils.getMailboxProperties; import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomId; @@ -111,22 +111,18 @@ public class ClientHelperImplTest extends BrambleMockTestCase { someServerSupports = BdfList.of(BdfList.of(1, 0)); validMailboxUpdateWithMailbox = new MailboxUpdateWithMailbox( singletonList(new MailboxVersion(1, 0)), - singletonList(new MailboxVersion(1, 0)), - "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd", - new MailboxAuthToken(getRandomId()), - new MailboxFolderId(getRandomId()), - new MailboxFolderId(getRandomId())); + getMailboxProperties(false, + singletonList(new MailboxVersion(1, 0)))); } private BdfDictionary getValidMailboxUpdateWithMailboxDict() { BdfDictionary dict = new BdfDictionary(); - dict.put(PROP_KEY_ONION, validMailboxUpdateWithMailbox.getOnion()); - dict.put(PROP_KEY_AUTHTOKEN, validMailboxUpdateWithMailbox - .getAuthToken().getBytes()); - dict.put(PROP_KEY_INBOXID, validMailboxUpdateWithMailbox.getInboxId() - .getBytes()); - dict.put(PROP_KEY_OUTBOXID, validMailboxUpdateWithMailbox.getOutboxId() - .getBytes()); + MailboxProperties properties = + validMailboxUpdateWithMailbox.getMailboxProperties(); + dict.put(PROP_KEY_ONION, properties.getOnion()); + dict.put(PROP_KEY_AUTHTOKEN, properties.getAuthToken()); + dict.put(PROP_KEY_INBOXID, properties.getInboxId()); + dict.put(PROP_KEY_OUTBOXID, properties.getOutboxId()); return dict; } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/ConnectivityCheckerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/ConnectivityCheckerImplTest.java new file mode 100644 index 000000000..448b36ea9 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/ConnectivityCheckerImplTest.java @@ -0,0 +1,201 @@ +package org.briarproject.bramble.mailbox; + +import org.briarproject.bramble.api.Cancellable; +import org.briarproject.bramble.api.mailbox.MailboxProperties; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver; +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.jmock.Expectations; +import org.junit.Test; + +import javax.annotation.Nonnull; + +import static org.briarproject.bramble.mailbox.ConnectivityCheckerImpl.CONNECTIVITY_CHECK_FRESHNESS_MS; +import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS; +import static org.briarproject.bramble.test.TestUtils.getMailboxProperties; + +public class ConnectivityCheckerImplTest extends BrambleMockTestCase { + + private final Clock clock = context.mock(Clock.class); + private final MailboxApiCaller mailboxApiCaller = + context.mock(MailboxApiCaller.class); + private final ApiCall apiCall = context.mock(ApiCall.class); + private final Cancellable task = context.mock(Cancellable.class); + private final ConnectivityObserver observer1 = + context.mock(ConnectivityObserver.class, "1"); + private final ConnectivityObserver observer2 = + context.mock(ConnectivityObserver.class, "2"); + + private final MailboxProperties properties = + getMailboxProperties(true, CLIENT_SUPPORTS); + private final long now = System.currentTimeMillis(); + + @Test + public void testFirstObserverStartsCheck() { + 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 check succeeds the observer should be called + context.checking(new Expectations() {{ + oneOf(observer1).onConnectivityCheckSucceeded(); + }}); + + checker.onConnectivityCheckSucceeded(now); + + // The observer should not be called again when subsequent checks + // succeed + checker.onConnectivityCheckSucceeded(now); + } + + @Test + public void testObserverIsAddedToExistingCheck() { + 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 checkConnectivity() is called again before the first check + // succeeds, the observer should be added to the existing check + checker.checkConnectivity(properties, observer2); + + // When the check succeeds both observers should be called + context.checking(new Expectations() {{ + oneOf(observer1).onConnectivityCheckSucceeded(); + oneOf(observer2).onConnectivityCheckSucceeded(); + }}); + + checker.onConnectivityCheckSucceeded(now); + + // The observers should not be called again when subsequent checks + // succeed + checker.onConnectivityCheckSucceeded(now); + } + + @Test + public void testFreshResultIsReused() { + 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 check succeeds the observer should be called + context.checking(new Expectations() {{ + oneOf(observer1).onConnectivityCheckSucceeded(); + }}); + + checker.onConnectivityCheckSucceeded(now); + + // When checkConnectivity() is called again within + // CONNECTIVITY_CHECK_FRESHNESS_MS the observer should be called with + // the result of the recent check + context.checking(new Expectations() {{ + oneOf(clock).currentTimeMillis(); + will(returnValue(now + CONNECTIVITY_CHECK_FRESHNESS_MS)); + oneOf(observer2).onConnectivityCheckSucceeded(); + }}); + + checker.checkConnectivity(properties, observer2); + } + + @Test + public void testStaleResultIsNotReused() { + 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 check succeeds the observer should be called + context.checking(new Expectations() {{ + oneOf(observer1).onConnectivityCheckSucceeded(); + }}); + + checker.onConnectivityCheckSucceeded(now); + + // When checkConnectivity() is called again after more than + // CONNECTIVITY_CHECK_FRESHNESS_MS another check should be started + context.checking(new Expectations() {{ + oneOf(clock).currentTimeMillis(); + will(returnValue(now + CONNECTIVITY_CHECK_FRESHNESS_MS + 1)); + oneOf(mailboxApiCaller).retryWithBackoff(apiCall); + will(returnValue(task)); + }}); + + checker.checkConnectivity(properties, observer2); + + // When the check succeeds the observer should be called + context.checking(new Expectations() {{ + oneOf(observer2).onConnectivityCheckSucceeded(); + }}); + + checker.onConnectivityCheckSucceeded( + now + CONNECTIVITY_CHECK_FRESHNESS_MS + 1); + } + + @Test + public void testCheckIsCancelledWhenCheckerIsDestroyed() { + 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 checker is destroyed the check should be cancelled + context.checking(new Expectations() {{ + oneOf(task).cancel(); + }}); + + checker.destroy(); + + // If the check runs anyway (cancellation came too late) the observer + // should not be called + checker.onConnectivityCheckSucceeded(now); + } + + private ConnectivityCheckerImpl createChecker() { + + return new ConnectivityCheckerImpl(clock, mailboxApiCaller) { + @Override + @Nonnull + protected ApiCall createConnectivityCheckTask( + @Nonnull MailboxProperties properties) { + return apiCall; + } + }; + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/ContactMailboxConnectivityCheckerTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/ContactMailboxConnectivityCheckerTest.java new file mode 100644 index 000000000..2378e3d1b --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/ContactMailboxConnectivityCheckerTest.java @@ -0,0 +1,98 @@ +package org.briarproject.bramble.mailbox; + +import org.briarproject.bramble.api.Cancellable; +import org.briarproject.bramble.api.mailbox.MailboxProperties; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver; +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.Test; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicReference; + +import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS; +import static org.briarproject.bramble.test.TestUtils.getMailboxProperties; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class ContactMailboxConnectivityCheckerTest extends BrambleMockTestCase { + + private final Clock clock = context.mock(Clock.class); + private final MailboxApiCaller mailboxApiCaller = + context.mock(MailboxApiCaller.class); + private final MailboxApi mailboxApi = context.mock(MailboxApi.class); + private final Cancellable task = context.mock(Cancellable.class); + private final ConnectivityObserver observer = + context.mock(ConnectivityObserver.class); + + private final MailboxProperties properties = + getMailboxProperties(false, CLIENT_SUPPORTS); + private final long now = System.currentTimeMillis(); + + @Test + public void testObserverIsCalledWhenCheckSucceeds() throws Exception { + ContactMailboxConnectivityChecker checker = createChecker(); + AtomicReference apiCall = new AtomicReference<>(null); + + // When checkConnectivity() is called a check should be started + context.checking(new Expectations() {{ + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class))); + will(new DoAllAction( + new CaptureArgumentAction<>(apiCall, ApiCall.class, 0), + returnValue(task) + )); + }}); + + checker.checkConnectivity(properties, observer); + + // When the check succeeds the observer should be called + context.checking(new Expectations() {{ + oneOf(mailboxApi).checkStatus(properties); + will(returnValue(true)); + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(observer).onConnectivityCheckSucceeded(); + }}); + + // The call should not be retried + assertFalse(apiCall.get().callApi()); + } + + @Test + public void testObserverIsNotCalledWhenCheckFails() throws Exception { + ContactMailboxConnectivityChecker checker = createChecker(); + AtomicReference apiCall = new AtomicReference<>(null); + + // When checkConnectivity() is called a check should be started + context.checking(new Expectations() {{ + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class))); + will(new DoAllAction( + new CaptureArgumentAction<>(apiCall, ApiCall.class, 0), + returnValue(task) + )); + }}); + + checker.checkConnectivity(properties, observer); + + // When the check fails, the observer should not be called + context.checking(new Expectations() {{ + oneOf(mailboxApi).checkStatus(properties); + will(throwException(new IOException())); + }}); + + // The call should be retried + assertTrue(apiCall.get().callApi()); + } + + private ContactMailboxConnectivityChecker createChecker() { + return new ContactMailboxConnectivityChecker(clock, mailboxApiCaller, + mailboxApi); + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxApiCallerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxApiCallerImplTest.java index 10bb91745..2612fe16a 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxApiCallerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxApiCallerImplTest.java @@ -1,7 +1,6 @@ package org.briarproject.bramble.mailbox; import org.briarproject.bramble.api.Cancellable; -import org.briarproject.bramble.api.Supplier; import org.briarproject.bramble.api.system.TaskScheduler; import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.CaptureArgumentAction; @@ -23,8 +22,7 @@ public class MailboxApiCallerImplTest extends BrambleMockTestCase { private final TaskScheduler taskScheduler = context.mock(TaskScheduler.class); private final Executor ioExecutor = context.mock(Executor.class); - private final BooleanSupplier supplier = - context.mock(BooleanSupplier.class); + private final ApiCall apiCall = context.mock(ApiCall.class); private final Cancellable scheduledTask = context.mock(Cancellable.class); private final MailboxApiCallerImpl caller = @@ -39,12 +37,12 @@ public class MailboxApiCallerImplTest extends BrambleMockTestCase { will(new CaptureArgumentAction<>(runnable, Runnable.class, 0)); }}); - caller.retryWithBackoff(supplier); + caller.retryWithBackoff(apiCall); - // When the task runs, the supplier should be called. The supplier + // When the task runs, the API call should be called. The call // returns false, so no retries should be scheduled context.checking(new Expectations() {{ - oneOf(supplier).get(); + oneOf(apiCall).callApi(); will(returnValue(false)); }}); @@ -60,12 +58,12 @@ public class MailboxApiCallerImplTest extends BrambleMockTestCase { will(new CaptureArgumentAction<>(runnable, Runnable.class, 0)); }}); - Cancellable returned = caller.retryWithBackoff(supplier); + Cancellable returned = caller.retryWithBackoff(apiCall); - // When the task runs, the supplier should be called. The supplier + // When the task runs, the API call should be called. The call // returns true, so a retry should be scheduled context.checking(new Expectations() {{ - oneOf(supplier).get(); + oneOf(apiCall).callApi(); will(returnValue(true)); oneOf(taskScheduler).schedule(with(any(Runnable.class)), with(ioExecutor), with(MIN_RETRY_INTERVAL_MS), @@ -90,7 +88,7 @@ public class MailboxApiCallerImplTest extends BrambleMockTestCase { returned.cancel(); // If the scheduled task runs anyway (cancellation came too late), - // the supplier should not be called and no further tries should be + // the API call should not be called and no further tries should be // scheduled runnable.get().run(); } @@ -114,13 +112,13 @@ public class MailboxApiCallerImplTest extends BrambleMockTestCase { will(new CaptureArgumentAction<>(runnable, Runnable.class, 0)); }}); - caller.retryWithBackoff(supplier); + caller.retryWithBackoff(apiCall); - // Each time the task runs, the supplier returns true, so a retry + // Each time the task runs, the API call returns true, so a retry // should be scheduled with a longer interval for (long interval : expectedIntervals) { context.checking(new Expectations() {{ - oneOf(supplier).get(); + oneOf(apiCall).callApi(); will(returnValue(true)); oneOf(taskScheduler).schedule(with(any(Runnable.class)), with(ioExecutor), with(interval), with(MILLISECONDS)); @@ -134,8 +132,4 @@ public class MailboxApiCallerImplTest extends BrambleMockTestCase { runnable.get().run(); } } - - // Reify the generic type to mollify jMock - private interface BooleanSupplier extends Supplier { - } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxApiTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxApiTest.java index de5075c0b..757af0760 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxApiTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxApiTest.java @@ -35,7 +35,9 @@ import okio.Buffer; import static java.util.Collections.singletonList; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS; import static org.briarproject.bramble.test.TestUtils.getContactId; +import static org.briarproject.bramble.test.TestUtils.getMailboxProperties; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.mailboxPropertiesEqual; @@ -102,9 +104,9 @@ public class MailboxApiTest extends BrambleTestCase { String baseUrl = getBaseUrl(server); List versions = singletonList(new MailboxVersion(1, 0)); MailboxProperties properties = - new MailboxProperties(baseUrl, token, true, new ArrayList<>()); + new MailboxProperties(baseUrl, token, new ArrayList<>()); MailboxProperties properties2 = - new MailboxProperties(baseUrl, token2, true, new ArrayList<>()); + new MailboxProperties(baseUrl, token2, new ArrayList<>()); RecordedRequest request; @@ -199,9 +201,9 @@ public class MailboxApiTest extends BrambleTestCase { server.start(); String baseUrl = getBaseUrl(server); MailboxProperties properties = - new MailboxProperties(baseUrl, token, true, new ArrayList<>()); + new MailboxProperties(baseUrl, token, new ArrayList<>()); MailboxProperties properties2 = - new MailboxProperties(baseUrl, token2, true, new ArrayList<>()); + new MailboxProperties(baseUrl, token2, new ArrayList<>()); // valid response with valid token mailboxPropertiesEqual(properties2, api.setup(properties)); @@ -282,7 +284,7 @@ public class MailboxApiTest extends BrambleTestCase { @Test public void testSetupOnlyForOwner() { MailboxProperties properties = - new MailboxProperties("", token, false, new ArrayList<>()); + getMailboxProperties(false, CLIENT_SUPPORTS); assertThrows( IllegalArgumentException.class, () -> api.setup(properties) @@ -298,9 +300,9 @@ public class MailboxApiTest extends BrambleTestCase { server.start(); String baseUrl = getBaseUrl(server); MailboxProperties properties = - new MailboxProperties(baseUrl, token, true, new ArrayList<>()); + new MailboxProperties(baseUrl, token, new ArrayList<>()); MailboxProperties properties2 = - new MailboxProperties(baseUrl, token2, true, new ArrayList<>()); + new MailboxProperties(baseUrl, token2, new ArrayList<>()); assertTrue(api.checkStatus(properties)); RecordedRequest request1 = server.takeRequest(); @@ -318,16 +320,6 @@ public class MailboxApiTest extends BrambleTestCase { assertToken(request3, token); } - @Test - public void testStatusOnlyForOwner() { - MailboxProperties properties = - new MailboxProperties("", token, false, new ArrayList<>()); - assertThrows( - IllegalArgumentException.class, - () -> api.checkStatus(properties) - ); - } - @Test public void testWipe() throws Exception { MockWebServer server = new MockWebServer(); @@ -338,9 +330,9 @@ public class MailboxApiTest extends BrambleTestCase { server.start(); String baseUrl = getBaseUrl(server); MailboxProperties properties = - new MailboxProperties(baseUrl, token, true, new ArrayList<>()); + new MailboxProperties(baseUrl, token, new ArrayList<>()); MailboxProperties properties2 = - new MailboxProperties(baseUrl, token2, true, new ArrayList<>()); + new MailboxProperties(baseUrl, token2, new ArrayList<>()); api.wipeMailbox(properties); RecordedRequest request1 = server.takeRequest(); @@ -370,7 +362,7 @@ public class MailboxApiTest extends BrambleTestCase { @Test public void testWipeOnlyForOwner() { MailboxProperties properties = - new MailboxProperties("", token, false, new ArrayList<>()); + getMailboxProperties(false, CLIENT_SUPPORTS); assertThrows(IllegalArgumentException.class, () -> api.wipeMailbox(properties)); } @@ -384,7 +376,7 @@ public class MailboxApiTest extends BrambleTestCase { server.start(); String baseUrl = getBaseUrl(server); MailboxProperties properties = - new MailboxProperties(baseUrl, token, true, new ArrayList<>()); + new MailboxProperties(baseUrl, token, new ArrayList<>()); // contact gets added as expected api.addContact(properties, mailboxContact); @@ -416,7 +408,7 @@ public class MailboxApiTest extends BrambleTestCase { @Test public void testAddContactOnlyForOwner() { MailboxProperties properties = - new MailboxProperties("", token, false, new ArrayList<>()); + getMailboxProperties(false, CLIENT_SUPPORTS); assertThrows(IllegalArgumentException.class, () -> api.addContact(properties, mailboxContact)); } @@ -431,7 +423,7 @@ public class MailboxApiTest extends BrambleTestCase { server.start(); String baseUrl = getBaseUrl(server); MailboxProperties properties = - new MailboxProperties(baseUrl, token, true, new ArrayList<>()); + new MailboxProperties(baseUrl, token, new ArrayList<>()); // contact gets deleted as expected api.deleteContact(properties, contactId); @@ -468,7 +460,7 @@ public class MailboxApiTest extends BrambleTestCase { @Test public void testDeleteContactOnlyForOwner() { MailboxProperties properties = - new MailboxProperties("", token, false, new ArrayList<>()); + getMailboxProperties(false, CLIENT_SUPPORTS); assertThrows(IllegalArgumentException.class, () -> api.deleteContact(properties, contactId)); } @@ -495,7 +487,7 @@ public class MailboxApiTest extends BrambleTestCase { server.start(); String baseUrl = getBaseUrl(server); MailboxProperties properties = - new MailboxProperties(baseUrl, token, true, new ArrayList<>()); + new MailboxProperties(baseUrl, token, new ArrayList<>()); // valid response with two contacts assertEquals(singletonList(contactId), api.getContacts(properties)); @@ -560,7 +552,7 @@ public class MailboxApiTest extends BrambleTestCase { @Test public void testGetContactsOnlyForOwner() { MailboxProperties properties = - new MailboxProperties("", token, false, new ArrayList<>()); + getMailboxProperties(false, CLIENT_SUPPORTS); assertThrows( IllegalArgumentException.class, () -> api.getContacts(properties) @@ -580,7 +572,7 @@ public class MailboxApiTest extends BrambleTestCase { server.start(); String baseUrl = getBaseUrl(server); MailboxProperties properties = - new MailboxProperties(baseUrl, token, true, new ArrayList<>()); + new MailboxProperties(baseUrl, token, new ArrayList<>()); // file gets uploaded as expected api.addFile(properties, contactInboxId, file); @@ -639,7 +631,7 @@ public class MailboxApiTest extends BrambleTestCase { server.start(); String baseUrl = getBaseUrl(server); MailboxProperties properties = - new MailboxProperties(baseUrl, token, true, new ArrayList<>()); + new MailboxProperties(baseUrl, token, new ArrayList<>()); // valid response with one file List received1 = api.getFiles(properties, contactInboxId); @@ -735,7 +727,7 @@ public class MailboxApiTest extends BrambleTestCase { server.start(); String baseUrl = getBaseUrl(server); MailboxProperties properties = - new MailboxProperties(baseUrl, token, true, new ArrayList<>()); + new MailboxProperties(baseUrl, token, new ArrayList<>()); // file gets downloaded as expected api.getFile(properties, contactOutboxId, name, file1); @@ -779,7 +771,7 @@ public class MailboxApiTest extends BrambleTestCase { server.start(); String baseUrl = getBaseUrl(server); MailboxProperties properties = - new MailboxProperties(baseUrl, token, true, new ArrayList<>()); + new MailboxProperties(baseUrl, token, new ArrayList<>()); // file gets deleted as expected api.deleteFile(properties, contactInboxId, name); @@ -843,7 +835,7 @@ public class MailboxApiTest extends BrambleTestCase { server.start(); String baseUrl = getBaseUrl(server); MailboxProperties properties = - new MailboxProperties(baseUrl, token, true, new ArrayList<>()); + new MailboxProperties(baseUrl, token, new ArrayList<>()); // valid response with one folders assertEquals(singletonList(id1), api.getFolders(properties)); @@ -912,7 +904,7 @@ public class MailboxApiTest extends BrambleTestCase { @Test public void testGetFoldersOnlyForOwner() { MailboxProperties properties = - new MailboxProperties("", token, false, new ArrayList<>()); + getMailboxProperties(false, CLIENT_SUPPORTS); assertThrows(IllegalArgumentException.class, () -> api.getFolders(properties)); } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxIntegrationTest.java index 4c0333c2a..71ec2198b 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxIntegrationTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxIntegrationTest.java @@ -91,7 +91,7 @@ public class MailboxIntegrationTest extends BrambleTestCase { if (ownerProperties != null) return; MailboxProperties setupProperties = new MailboxProperties( - URL_BASE, SETUP_TOKEN, true, new ArrayList<>()); + URL_BASE, SETUP_TOKEN, new ArrayList<>()); ownerProperties = api.setup(setupProperties); } @@ -108,13 +108,29 @@ public class MailboxIntegrationTest extends BrambleTestCase { // new setup doesn't work as mailbox is stopping MailboxProperties setupProperties = new MailboxProperties( - URL_BASE, SETUP_TOKEN, true, new ArrayList<>()); + URL_BASE, SETUP_TOKEN, new ArrayList<>()); assertThrows(ApiException.class, () -> api.setup(setupProperties)); } @Test public void testStatus() throws Exception { + // Owner calls status endpoint assertTrue(api.checkStatus(ownerProperties)); + + // Owner adds contact + ContactId contactId = new ContactId(1); + MailboxContact contact = getMailboxContact(contactId); + MailboxProperties contactProperties = new MailboxProperties( + ownerProperties.getBaseUrl(), contact.token, + new ArrayList<>(), contact.inboxId, contact.outboxId); + api.addContact(ownerProperties, contact); + + // Contact calls status endpoint + assertTrue(api.checkStatus(contactProperties)); + + // Owner deletes contact again to leave clean state for other tests + api.deleteContact(ownerProperties, contactId); + assertEquals(emptyList(), api.getContacts(ownerProperties)); } @Test @@ -151,8 +167,8 @@ public class MailboxIntegrationTest extends BrambleTestCase { ContactId contactId = new ContactId(1); MailboxContact contact = getMailboxContact(contactId); MailboxProperties contactProperties = new MailboxProperties( - ownerProperties.getBaseUrl(), contact.token, false, - new ArrayList<>()); + ownerProperties.getBaseUrl(), contact.token, + new ArrayList<>(), contact.inboxId, contact.outboxId); api.addContact(ownerProperties, contact); // upload a file for our contact diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImplTest.java index 9d43995c2..46117fb42 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImplTest.java @@ -57,7 +57,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase { private final String onion = getRandomString(56); private final byte[] onionBytes = getRandomBytes(32); - private final String onionAddress = "http://" + onion + ".onion"; + private final String baseUrl = "http://" + onion + ".onion"; // TODO private final MailboxAuthToken setupToken = new MailboxAuthToken(getRandomId()); private final MailboxAuthToken ownerToken = @@ -65,9 +65,9 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase { private final String validPayload = getValidPayload(); private final long time = System.currentTimeMillis(); private final MailboxProperties setupProperties = new MailboxProperties( - onionAddress, setupToken, true, new ArrayList<>()); + baseUrl, setupToken, new ArrayList<>()); private final MailboxProperties ownerProperties = new MailboxProperties( - onionAddress, ownerToken, true, new ArrayList<>()); + baseUrl, ownerToken, new ArrayList<>()); @Test public void testInitialQrCodeReceivedState() { diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImplTest.java index fe8d34cce..78bb64faf 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImplTest.java @@ -97,7 +97,7 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase { expectedSettings.put(SETTINGS_KEY_TOKEN, token.toString()); expectedSettings.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, serverSupportsInts); - MailboxProperties properties = new MailboxProperties(onion, token, true, + MailboxProperties properties = new MailboxProperties(onion, token, serverSupports); context.checking(new Expectations() {{ diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxUpdateManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxUpdateManagerImplTest.java index 36b49b00a..4c659d125 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxUpdateManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxUpdateManagerImplTest.java @@ -11,8 +11,6 @@ import org.briarproject.bramble.api.data.MetadataParser; import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Transaction; -import org.briarproject.bramble.api.mailbox.MailboxAuthToken; -import org.briarproject.bramble.api.mailbox.MailboxFolderId; import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxUpdate; @@ -48,6 +46,7 @@ import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getGroup; +import static org.briarproject.bramble.test.TestUtils.getMailboxProperties; import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.hasEvent; @@ -82,6 +81,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase { private final List someServerSupportsList; private final BdfList someServerSupports; private final BdfList emptyServerSupports = new BdfList(); + private final MailboxProperties updateProps; private final MailboxUpdateWithMailbox updateWithMailbox; private final MailboxUpdate updateNoMailbox; private final MailboxProperties ownProps; @@ -108,22 +108,16 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase { updateNoMailbox = new MailboxUpdate(someClientSupportsList); - ownProps = new MailboxProperties("http://bar.onion", - new MailboxAuthToken(getRandomId()), true, - someServerSupportsList); + updateProps = getMailboxProperties(false, someServerSupportsList); + ownProps = new MailboxProperties(updateProps.getBaseUrl(), + updateProps.getAuthToken(), someServerSupportsList); updateWithMailbox = new MailboxUpdateWithMailbox(someClientSupportsList, - someServerSupportsList, ownProps.getOnion(), - new MailboxAuthToken(getRandomId()), - new MailboxFolderId(getRandomId()), - new MailboxFolderId(getRandomId())); + updateProps); propsDict = new BdfDictionary(); - propsDict.put(PROP_KEY_ONION, updateWithMailbox.getOnion()); - propsDict.put(PROP_KEY_AUTHTOKEN, updateWithMailbox.getAuthToken() - .getBytes()); - propsDict.put(PROP_KEY_INBOXID, updateWithMailbox.getInboxId() - .getBytes()); - propsDict.put(PROP_KEY_OUTBOXID, updateWithMailbox.getOutboxId() - .getBytes()); + propsDict.put(PROP_KEY_ONION, updateProps.getOnion()); + propsDict.put(PROP_KEY_AUTHTOKEN, updateProps.getAuthToken()); + propsDict.put(PROP_KEY_INBOXID, updateProps.getInboxId()); + propsDict.put(PROP_KEY_OUTBOXID, updateProps.getOutboxId()); } private MailboxUpdateManagerImpl createInstance( @@ -219,11 +213,11 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase { oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn); will(returnValue(ownProps)); oneOf(crypto).generateUniqueId(); - will(returnValue(updateWithMailbox.getAuthToken())); + will(returnValue(updateProps.getAuthToken())); oneOf(crypto).generateUniqueId(); - will(returnValue(updateWithMailbox.getInboxId())); + will(returnValue(updateProps.getInboxId())); oneOf(crypto).generateUniqueId(); - will(returnValue(updateWithMailbox.getOutboxId())); + will(returnValue(updateProps.getOutboxId())); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, MAJOR_VERSION, contact); will(returnValue(contactGroup)); @@ -478,11 +472,11 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase { oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn); will(returnValue(ownProps)); oneOf(crypto).generateUniqueId(); - will(returnValue(updateWithMailbox.getAuthToken())); + will(returnValue(updateProps.getAuthToken())); oneOf(crypto).generateUniqueId(); - will(returnValue(updateWithMailbox.getInboxId())); + will(returnValue(updateProps.getInboxId())); oneOf(crypto).generateUniqueId(); - will(returnValue(updateWithMailbox.getOutboxId())); + will(returnValue(updateProps.getOutboxId())); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, MAJOR_VERSION, contact); will(returnValue(contactGroup)); @@ -668,11 +662,11 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase { oneOf(db).getContacts(txn); will(returnValue(contacts)); oneOf(crypto).generateUniqueId(); - will(returnValue(updateWithMailbox.getAuthToken())); + will(returnValue(updateProps.getAuthToken())); oneOf(crypto).generateUniqueId(); - will(returnValue(updateWithMailbox.getInboxId())); + will(returnValue(updateProps.getInboxId())); oneOf(crypto).generateUniqueId(); - will(returnValue(updateWithMailbox.getOutboxId())); + will(returnValue(updateProps.getOutboxId())); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, MAJOR_VERSION, contact); will(returnValue(contactGroup)); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxUpdateValidatorTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxUpdateValidatorTest.java index 8c489dccb..e7227da5f 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxUpdateValidatorTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxUpdateValidatorTest.java @@ -6,8 +6,7 @@ import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfEntry; import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.MetadataEncoder; -import org.briarproject.bramble.api.mailbox.MailboxAuthToken; -import org.briarproject.bramble.api.mailbox.MailboxFolderId; +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; @@ -24,8 +23,8 @@ import java.util.List; import static java.util.Collections.singletonList; import static org.briarproject.bramble.test.TestUtils.getGroup; +import static org.briarproject.bramble.test.TestUtils.getMailboxProperties; import static org.briarproject.bramble.test.TestUtils.getMessage; -import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.junit.Assert.assertEquals; public class MailboxUpdateValidatorTest extends BrambleMockTestCase { @@ -35,7 +34,6 @@ public class MailboxUpdateValidatorTest extends BrambleMockTestCase { private final BdfDictionary bdfDict; private final BdfList emptyServerSupports; private final BdfList someClientSupports; - private final List someClientSupportsList; private final BdfList someServerSupports; private final MailboxUpdateWithMailbox updateMailbox; private final MailboxUpdate updateNoMailbox; @@ -49,17 +47,15 @@ public class MailboxUpdateValidatorTest extends BrambleMockTestCase { // {@link ClientHelper#parseAndValidateMailboxUpdate(BdfList, BdfList, BdfDictionary)} emptyServerSupports = new BdfList(); someClientSupports = BdfList.of(BdfList.of(1, 0)); - someClientSupportsList = singletonList(new MailboxVersion(1, 0)); + List someClientSupportsList = + singletonList(new MailboxVersion(1, 0)); someServerSupports = BdfList.of(BdfList.of(1, 0)); bdfDict = BdfDictionary.of(new BdfEntry("foo", "bar")); + MailboxProperties props = getMailboxProperties(false, + singletonList(new MailboxVersion(1, 0))); updateMailbox = new MailboxUpdateWithMailbox( - singletonList(new MailboxVersion(1, 0)), - singletonList(new MailboxVersion(1, 0)), - "baz", - new MailboxAuthToken(getRandomId()), - new MailboxFolderId(getRandomId()), - new MailboxFolderId(getRandomId())); + singletonList(new MailboxVersion(1, 0)), props); updateNoMailbox = new MailboxUpdate(someClientSupportsList); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/OwnMailboxConnectivityCheckerTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/OwnMailboxConnectivityCheckerTest.java new file mode 100644 index 000000000..c94d87903 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/OwnMailboxConnectivityCheckerTest.java @@ -0,0 +1,117 @@ +package org.briarproject.bramble.mailbox; + +import org.briarproject.bramble.api.Cancellable; +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.system.Clock; +import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver; +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.briarproject.bramble.test.CaptureArgumentAction; +import org.briarproject.bramble.test.DbExpectations; +import org.jmock.Expectations; +import org.jmock.lib.action.DoAllAction; +import org.junit.Test; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicReference; + +import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS; +import static org.briarproject.bramble.test.TestUtils.getMailboxProperties; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class OwnMailboxConnectivityCheckerTest extends BrambleMockTestCase { + + private final Clock clock = context.mock(Clock.class); + private final MailboxApiCaller mailboxApiCaller = + context.mock(MailboxApiCaller.class); + private final MailboxApi mailboxApi = context.mock(MailboxApi.class); + private final TransactionManager db = + context.mock(TransactionManager.class); + private final MailboxSettingsManager mailboxSettingsManager = + context.mock(MailboxSettingsManager.class); + private final Cancellable task = context.mock(Cancellable.class); + private final ConnectivityObserver observer = + context.mock(ConnectivityObserver.class); + + private final MailboxProperties properties = + getMailboxProperties(true, CLIENT_SUPPORTS); + private final long now = System.currentTimeMillis(); + + @Test + public void testObserverIsCalledWhenCheckSucceeds() throws Exception { + OwnMailboxConnectivityChecker checker = createChecker(); + AtomicReference apiCall = new AtomicReference<>(null); + Transaction txn = new Transaction(null, false); + + // When checkConnectivity() is called a check should be started + context.checking(new Expectations() {{ + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class))); + will(new DoAllAction( + new CaptureArgumentAction<>(apiCall, ApiCall.class, 0), + returnValue(task) + )); + }}); + + checker.checkConnectivity(properties, observer); + + // When the check succeeds, the success should be recorded in the DB + // and the observer should be called + context.checking(new DbExpectations() {{ + oneOf(mailboxApi).checkStatus(properties); + will(returnValue(true)); + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(db).transaction(with(false), withDbRunnable(txn)); + oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, now); + oneOf(observer).onConnectivityCheckSucceeded(); + }}); + + // The call should not be retried + assertFalse(apiCall.get().callApi()); + } + + @Test + public void testObserverIsNotCalledWhenCheckFails() throws Exception { + OwnMailboxConnectivityChecker checker = createChecker(); + AtomicReference apiCall = new AtomicReference<>(null); + Transaction txn = new Transaction(null, false); + + // When checkConnectivity() is called a check should be started + context.checking(new Expectations() {{ + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class))); + will(new DoAllAction( + new CaptureArgumentAction<>(apiCall, ApiCall.class, 0), + returnValue(task) + )); + }}); + + checker.checkConnectivity(properties, observer); + + // When the check fails, the failure should be recorded in the DB and + // the observer should not be called + context.checking(new DbExpectations() {{ + oneOf(mailboxApi).checkStatus(properties); + will(throwException(new IOException())); + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(db).transaction(with(false), withDbRunnable(txn)); + oneOf(mailboxSettingsManager) + .recordFailedConnectionAttempt(txn, now); + }}); + + // The call should be retried + assertTrue(apiCall.get().callApi()); + } + + private OwnMailboxConnectivityChecker createChecker() { + return new OwnMailboxConnectivityChecker(clock, mailboxApiCaller, + mailboxApi, db, mailboxSettingsManager); + } +}