Commit 249e1e28 authored by Torsten Grote's avatar Torsten Grote

Merge branch '1580-offline-state' into 'master'

Add offline state for pending contacts

Closes #1580

See merge request !1138
parents 32e8ea98 f0cea28a
Pipeline #3580 passed with stage
in 10 minutes and 39 seconds
...@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.contact; ...@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.contact;
public enum PendingContactState { public enum PendingContactState {
WAITING_FOR_CONNECTION, WAITING_FOR_CONNECTION,
OFFLINE,
CONNECTING, CONNECTING,
ADDING_CONTACT, ADDING_CONTACT,
FAILED FAILED
......
...@@ -147,7 +147,6 @@ class ContactManagerImpl implements ContactManager, EventListener { ...@@ -147,7 +147,6 @@ class ContactManagerImpl implements ContactManager, EventListener {
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
} }
states.put(p.getId(), WAITING_FOR_CONNECTION);
return p; return p;
} }
......
...@@ -66,6 +66,7 @@ import static java.util.logging.Level.WARNING; ...@@ -66,6 +66,7 @@ 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.contact.PendingContactState.ADDING_CONTACT; import static org.briarproject.bramble.api.contact.PendingContactState.ADDING_CONTACT;
import static org.briarproject.bramble.api.contact.PendingContactState.FAILED; import static org.briarproject.bramble.api.contact.PendingContactState.FAILED;
import static org.briarproject.bramble.api.contact.PendingContactState.OFFLINE;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION; import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNull; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNull;
import static org.briarproject.bramble.rendezvous.RendezvousConstants.POLLING_INTERVAL_MS; import static org.briarproject.bramble.rendezvous.RendezvousConstants.POLLING_INTERVAL_MS;
...@@ -158,9 +159,7 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener { ...@@ -158,9 +159,7 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
private void addPendingContact(PendingContact p) { private void addPendingContact(PendingContact p) {
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
long expiry = p.getTimestamp() + RENDEZVOUS_TIMEOUT_MS; long expiry = p.getTimestamp() + RENDEZVOUS_TIMEOUT_MS;
if (expiry > now) { if (expiry <= now) {
broadcastState(p.getId(), WAITING_FOR_CONNECTION);
} else {
broadcastState(p.getId(), FAILED); broadcastState(p.getId(), FAILED);
return; return;
} }
...@@ -180,9 +179,13 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener { ...@@ -180,9 +179,13 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
for (PluginState ps : pluginStates.values()) { for (PluginState ps : pluginStates.values()) {
RendezvousEndpoint endpoint = RendezvousEndpoint endpoint =
createEndpoint(ps.plugin, p.getId(), cs); createEndpoint(ps.plugin, p.getId(), cs);
if (endpoint != null) if (endpoint != null) {
requireNull(ps.endpoints.put(p.getId(), endpoint)); requireNull(ps.endpoints.put(p.getId(), endpoint));
cs.numEndpoints++;
}
} }
if (cs.numEndpoints == 0) broadcastState(p.getId(), OFFLINE);
else broadcastState(p.getId(), WAITING_FOR_CONNECTION);
} catch (DbException | GeneralSecurityException e) { } catch (DbException | GeneralSecurityException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
...@@ -328,9 +331,14 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener { ...@@ -328,9 +331,14 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
TransportId t = plugin.getId(); TransportId t = plugin.getId();
Map<PendingContactId, RendezvousEndpoint> endpoints = new HashMap<>(); Map<PendingContactId, RendezvousEndpoint> endpoints = new HashMap<>();
for (Entry<PendingContactId, CryptoState> e : cryptoStates.entrySet()) { for (Entry<PendingContactId, CryptoState> e : cryptoStates.entrySet()) {
RendezvousEndpoint endpoint = PendingContactId p = e.getKey();
createEndpoint(plugin, e.getKey(), e.getValue()); CryptoState cs = e.getValue();
if (endpoint != null) endpoints.put(e.getKey(), endpoint); RendezvousEndpoint endpoint = createEndpoint(plugin, p, cs);
if (endpoint != null) {
endpoints.put(p, endpoint);
if (++cs.numEndpoints == 1)
broadcastState(p, WAITING_FOR_CONNECTION);
}
} }
requireNull(pluginStates.put(t, new PluginState(plugin, endpoints))); requireNull(pluginStates.put(t, new PluginState(plugin, endpoints)));
} }
...@@ -344,8 +352,11 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener { ...@@ -344,8 +352,11 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
private void removeTransport(TransportId t) { private void removeTransport(TransportId t) {
PluginState ps = pluginStates.remove(t); PluginState ps = pluginStates.remove(t);
if (ps != null) { if (ps != null) {
for (RendezvousEndpoint endpoint : ps.endpoints.values()) { for (Entry<PendingContactId, RendezvousEndpoint> e :
tryToClose(endpoint, LOG, INFO); ps.endpoints.entrySet()) {
tryToClose(e.getValue(), LOG, INFO);
CryptoState cs = cryptoStates.get(e.getKey());
if (--cs.numEndpoints == 0) broadcastState(e.getKey(), OFFLINE);
} }
} }
} }
...@@ -391,6 +402,8 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener { ...@@ -391,6 +402,8 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
private final boolean alice; private final boolean alice;
private final long expiry; private final long expiry;
private int numEndpoints = 0;
private CryptoState(SecretKey rendezvousKey, boolean alice, private CryptoState(SecretKey rendezvousKey, boolean alice,
long expiry) { long expiry) {
this.rendezvousKey = rendezvousKey; this.rendezvousKey = rendezvousKey;
......
...@@ -6,11 +6,15 @@ import org.briarproject.bramble.api.contact.ContactManager; ...@@ -6,11 +6,15 @@ import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactState; import org.briarproject.bramble.api.contact.PendingContactState;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent; import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.Identity; import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestDatabaseConfigModule; import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.bramble.test.TestDuplexTransportConnection; import org.briarproject.bramble.test.TestDuplexTransportConnection;
...@@ -27,7 +31,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; ...@@ -27,7 +31,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static junit.framework.TestCase.assertNotNull; import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertNull; import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.fail; import static junit.framework.TestCase.fail;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION; import static org.briarproject.bramble.api.contact.PendingContactState.OFFLINE;
import static org.briarproject.bramble.test.TestDuplexTransportConnection.createPair; import static org.briarproject.bramble.test.TestDuplexTransportConnection.createPair;
import static org.briarproject.bramble.test.TestPluginConfigModule.DUPLEX_TRANSPORT_ID; import static org.briarproject.bramble.test.TestPluginConfigModule.DUPLEX_TRANSPORT_ID;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
...@@ -188,9 +192,14 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { ...@@ -188,9 +192,14 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase {
private PendingContact addPendingContact( private PendingContact addPendingContact(
ContactExchangeIntegrationTestComponent local, ContactExchangeIntegrationTestComponent local,
ContactExchangeIntegrationTestComponent remote) throws Exception { ContactExchangeIntegrationTestComponent remote) throws Exception {
EventWaiter waiter = new EventWaiter();
local.getEventBus().addListener(waiter);
String link = remote.getContactManager().getHandshakeLink(); String link = remote.getContactManager().getHandshakeLink();
String alias = remote.getIdentityManager().getLocalAuthor().getName(); String alias = remote.getIdentityManager().getLocalAuthor().getName();
return local.getContactManager().addPendingContact(link, alias); PendingContact pendingContact =
local.getContactManager().addPendingContact(link, alias);
waiter.latch.await(TIMEOUT, MILLISECONDS);
return pendingContact;
} }
private void assertContacts(boolean verified, private void assertContacts(boolean verified,
...@@ -237,7 +246,7 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { ...@@ -237,7 +246,7 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase {
assertEquals(1, pairs.size()); assertEquals(1, pairs.size());
Pair<PendingContact, PendingContactState> pair = Pair<PendingContact, PendingContactState> pair =
pairs.iterator().next(); pairs.iterator().next();
assertEquals(WAITING_FOR_CONNECTION, pair.getSecond()); assertEquals(OFFLINE, pair.getSecond());
PendingContact pendingContact = pair.getFirst(); PendingContact pendingContact = pair.getFirst();
assertEquals(expectedIdentity.getLocalAuthor().getName(), assertEquals(expectedIdentity.getLocalAuthor().getName(),
pendingContact.getAlias()); pendingContact.getAlias());
...@@ -261,4 +270,19 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { ...@@ -261,4 +270,19 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase {
tearDown(bob); tearDown(bob);
deleteTestDirectory(testDir); deleteTestDirectory(testDir);
} }
@NotNullByDefault
private static class EventWaiter implements EventListener {
private final CountDownLatch latch = new CountDownLatch(1);
@Override
public void eventOccurred(Event e) {
if (e instanceof PendingContactStateChangedEvent) {
PendingContactStateChangedEvent p =
(PendingContactStateChangedEvent) e;
if (p.getPendingContactState() == OFFLINE) latch.countDown();
}
}
}
} }
...@@ -45,6 +45,7 @@ import static java.util.Collections.singletonList; ...@@ -45,6 +45,7 @@ import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.api.contact.PendingContactState.ADDING_CONTACT; import static org.briarproject.bramble.api.contact.PendingContactState.ADDING_CONTACT;
import static org.briarproject.bramble.api.contact.PendingContactState.FAILED; import static org.briarproject.bramble.api.contact.PendingContactState.FAILED;
import static org.briarproject.bramble.api.contact.PendingContactState.OFFLINE;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION; import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.rendezvous.RendezvousConstants.POLLING_INTERVAL_MS; import static org.briarproject.bramble.rendezvous.RendezvousConstants.POLLING_INTERVAL_MS;
import static org.briarproject.bramble.rendezvous.RendezvousConstants.RENDEZVOUS_TIMEOUT_MS; import static org.briarproject.bramble.rendezvous.RendezvousConstants.RENDEZVOUS_TIMEOUT_MS;
...@@ -120,7 +121,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase { ...@@ -120,7 +121,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
will(returnValue(beforeExpiry)); will(returnValue(beforeExpiry));
oneOf(eventBus).broadcast(with(new PredicateMatcher<>( oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
PendingContactStateChangedEvent.class, e -> PendingContactStateChangedEvent.class, e ->
e.getPendingContactState() == WAITING_FOR_CONNECTION))); e.getPendingContactState() == OFFLINE)));
// Capture the poll task // Capture the poll task
oneOf(scheduler).scheduleAtFixedRate(with(any(Runnable.class)), oneOf(scheduler).scheduleAtFixedRate(with(any(Runnable.class)),
with(POLLING_INTERVAL_MS), with(POLLING_INTERVAL_MS), with(POLLING_INTERVAL_MS), with(POLLING_INTERVAL_MS),
...@@ -184,7 +185,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase { ...@@ -184,7 +185,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
context.assertIsSatisfied(); context.assertIsSatisfied();
// Add the pending contact - endpoint should be created and polled // Add the pending contact - endpoint should be created and polled
expectAddUnexpiredPendingContact(beforeExpiry); expectAddPendingContact(beforeExpiry, WAITING_FOR_CONNECTION);
expectDeriveRendezvousKey(); expectDeriveRendezvousKey();
expectCreateEndpoint(); expectCreateEndpoint();
...@@ -205,9 +206,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase { ...@@ -205,9 +206,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
context.assertIsSatisfied(); context.assertIsSatisfied();
// Remove the pending contact - endpoint should be closed // Remove the pending contact - endpoint should be closed
context.checking(new Expectations() {{ expectCloseEndpoint();
oneOf(rendezvousEndpoint).close();
}});
rendezvousPoller.eventOccurred( rendezvousPoller.eventOccurred(
new PendingContactRemovedEvent(pendingContact.getId())); new PendingContactRemovedEvent(pendingContact.getId()));
...@@ -238,7 +237,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase { ...@@ -238,7 +237,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
context.assertIsSatisfied(); context.assertIsSatisfied();
// Add the pending contact - endpoint should be created and polled // Add the pending contact - endpoint should be created and polled
expectAddUnexpiredPendingContact(beforeExpiry); expectAddPendingContact(beforeExpiry, WAITING_FOR_CONNECTION);
expectDeriveRendezvousKey(); expectDeriveRendezvousKey();
expectCreateEndpoint(); expectCreateEndpoint();
...@@ -260,10 +259,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase { ...@@ -260,10 +259,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
// Run the poll task - pending contact expires, endpoint is closed // Run the poll task - pending contact expires, endpoint is closed
expectPendingContactExpires(afterExpiry); expectPendingContactExpires(afterExpiry);
expectCloseEndpoint();
context.checking(new Expectations() {{
oneOf(rendezvousEndpoint).close();
}});
capturePollTask.get().run(); capturePollTask.get().run();
context.assertIsSatisfied(); context.assertIsSatisfied();
...@@ -289,7 +285,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase { ...@@ -289,7 +285,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
context.assertIsSatisfied(); context.assertIsSatisfied();
// Add the pending contact - no endpoints should be created yet // Add the pending contact - no endpoints should be created yet
expectAddUnexpiredPendingContact(beforeExpiry); expectAddPendingContact(beforeExpiry, OFFLINE);
expectDeriveRendezvousKey(); expectDeriveRendezvousKey();
rendezvousPoller.eventOccurred( rendezvousPoller.eventOccurred(
...@@ -299,14 +295,14 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase { ...@@ -299,14 +295,14 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
// Enable the transport - endpoint should be created // Enable the transport - endpoint should be created
expectGetPlugin(); expectGetPlugin();
expectCreateEndpoint(); expectCreateEndpoint();
expectStateChangedEvent(WAITING_FOR_CONNECTION);
rendezvousPoller.eventOccurred(new TransportEnabledEvent(transportId)); rendezvousPoller.eventOccurred(new TransportEnabledEvent(transportId));
context.assertIsSatisfied(); context.assertIsSatisfied();
// Disable the transport - endpoint should be closed // Disable the transport - endpoint should be closed
context.checking(new Expectations() {{ expectCloseEndpoint();
oneOf(rendezvousEndpoint).close(); expectStateChangedEvent(OFFLINE);
}});
rendezvousPoller.eventOccurred(new TransportDisabledEvent(transportId)); rendezvousPoller.eventOccurred(new TransportDisabledEvent(transportId));
context.assertIsSatisfied(); context.assertIsSatisfied();
...@@ -482,13 +478,14 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase { ...@@ -482,13 +478,14 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
return capturePollTask; return capturePollTask;
} }
private void expectAddUnexpiredPendingContact(long now) { private void expectAddPendingContact(long now,
PendingContactState initialState) {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
will(returnValue(now)); will(returnValue(now));
oneOf(eventBus).broadcast(with(new PredicateMatcher<>( oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
PendingContactStateChangedEvent.class, e -> PendingContactStateChangedEvent.class, e ->
e.getPendingContactState() == WAITING_FOR_CONNECTION))); e.getPendingContactState() == initialState)));
}}); }});
} }
...@@ -546,7 +543,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase { ...@@ -546,7 +543,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
will(returnValue(now)); will(returnValue(now));
oneOf(eventBus).broadcast(with(new PredicateMatcher<>( oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
PendingContactStateChangedEvent.class, e -> PendingContactStateChangedEvent.class, e ->
e.getPendingContactState() == WAITING_FOR_CONNECTION))); e.getPendingContactState() == OFFLINE)));
// Capture the poll task // Capture the poll task
oneOf(scheduler).scheduleAtFixedRate(with(any(Runnable.class)), oneOf(scheduler).scheduleAtFixedRate(with(any(Runnable.class)),
with(POLLING_INTERVAL_MS), with(POLLING_INTERVAL_MS), with(POLLING_INTERVAL_MS), with(POLLING_INTERVAL_MS),
...@@ -576,4 +573,10 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase { ...@@ -576,4 +573,10 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
e.getPendingContactState() == state))); e.getPendingContactState() == state)));
}}); }});
} }
private void expectCloseEndpoint() throws Exception {
context.checking(new Expectations() {{
oneOf(rendezvousEndpoint).close();
}});
}
} }
...@@ -52,6 +52,11 @@ class PendingContactViewHolder extends ViewHolder { ...@@ -52,6 +52,11 @@ class PendingContactViewHolder extends ViewHolder {
.getColor(status.getContext(), R.color.briar_yellow); .getColor(status.getContext(), R.color.briar_yellow);
status.setText(R.string.waiting_for_contact_to_come_online); status.setText(R.string.waiting_for_contact_to_come_online);
break; break;
case OFFLINE:
color = ContextCompat
.getColor(status.getContext(), R.color.briar_yellow);
status.setText("");
break;
case CONNECTING: case CONNECTING:
status.setText(R.string.connecting); status.setText(R.string.connecting);
break; break;
......
...@@ -131,6 +131,7 @@ This will return a JSON array of pending contacts and their states: ...@@ -131,6 +131,7 @@ This will return a JSON array of pending contacts and their states:
The state can be one of these values: The state can be one of these values:
* `waiting_for_connection` * `waiting_for_connection`
* `offline`
* `connecting` * `connecting`
* `adding_contact` * `adding_contact`
* `failed` * `failed`
......
...@@ -16,6 +16,7 @@ internal fun PendingContact.output() = JsonDict( ...@@ -16,6 +16,7 @@ internal fun PendingContact.output() = JsonDict(
internal fun PendingContactState.output() = when(this) { internal fun PendingContactState.output() = when(this) {
WAITING_FOR_CONNECTION -> "waiting_for_connection" WAITING_FOR_CONNECTION -> "waiting_for_connection"
OFFLINE -> "offline"
CONNECTING -> "connecting" CONNECTING -> "connecting"
ADDING_CONTACT -> "adding_contact" ADDING_CONTACT -> "adding_contact"
FAILED -> "failed" FAILED -> "failed"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment