Merge branch '474-manual-decline' into 'master'

Don't automatically respond to declined introductions

See merge request akwizgran/briar!777
This commit is contained in:
Torsten Grote
2018-04-28 13:46:40 +00:00
4 changed files with 71 additions and 142 deletions

View File

@@ -575,10 +575,6 @@ public class ConversationActivity extends BriarActivity
@Override @Override
public void onSuccess(String contactName) { public void onSuccess(String contactName) {
runOnUiThreadUnlessDestroyed(() -> { runOnUiThreadUnlessDestroyed(() -> {
// If the other introducee declined, we can no longer
// respond to the request
if (!m.isIntroducer() && !m.wasAccepted())
markRequestAnswered(m.getSessionId());
ConversationItem item = ConversationItem ConversationItem item = ConversationItem
.from(ConversationActivity.this, contactName, m); .from(ConversationActivity.this, contactName, m);
addConversationItem(item); addConversationItem(item);
@@ -631,26 +627,6 @@ public class ConversationActivity extends BriarActivity
}); });
} }
private void markRequestAnswered(SessionId sessionId) {
int size = adapter.getItemCount();
for (int i = 0; i < size; i++) {
ConversationItem item = adapter.getItemAt(i);
if (item instanceof ConversationRequestItem) {
ConversationRequestItem req = (ConversationRequestItem) item;
if (req.getSessionId().equals(sessionId)
&& !req.wasAnswered()) {
LOG.info("Marking request answered");
req.setAnswered(true);
int position = adapter.findItemPosition(req);
if (position != INVALID_POSITION)
adapter.notifyItemChanged(position, req);
// There shouldn't be more than one unanswered request
return;
}
}
}
}
private void markMessages(Collection<MessageId> messageIds, private void markMessages(Collection<MessageId> messageIds,
boolean sent, boolean seen) { boolean sent, boolean seen) {
runOnUiThreadUnlessDestroyed(() -> { runOnUiThreadUnlessDestroyed(() -> {

View File

@@ -46,6 +46,7 @@ import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_RESPONSE
import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_ACCEPTED; import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_ACCEPTED;
import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_DECLINED; import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_DECLINED;
import static org.briarproject.briar.introduction.IntroduceeState.REMOTE_ACCEPTED; import static org.briarproject.briar.introduction.IntroduceeState.REMOTE_ACCEPTED;
import static org.briarproject.briar.introduction.IntroduceeState.REMOTE_DECLINED;
import static org.briarproject.briar.introduction.IntroduceeState.START; import static org.briarproject.briar.introduction.IntroduceeState.START;
@Immutable @Immutable
@@ -94,6 +95,7 @@ class IntroduceeProtocolEngine
IntroduceeSession session, long timestamp) throws DbException { IntroduceeSession session, long timestamp) throws DbException {
switch (session.getState()) { switch (session.getState()) {
case AWAIT_RESPONSES: case AWAIT_RESPONSES:
case REMOTE_DECLINED:
case REMOTE_ACCEPTED: case REMOTE_ACCEPTED:
return onLocalAccept(txn, session, timestamp); return onLocalAccept(txn, session, timestamp);
case START: case START:
@@ -112,6 +114,7 @@ class IntroduceeProtocolEngine
IntroduceeSession session, long timestamp) throws DbException { IntroduceeSession session, long timestamp) throws DbException {
switch (session.getState()) { switch (session.getState()) {
case AWAIT_RESPONSES: case AWAIT_RESPONSES:
case REMOTE_DECLINED:
case REMOTE_ACCEPTED: case REMOTE_ACCEPTED:
return onLocalDecline(txn, session, timestamp); return onLocalDecline(txn, session, timestamp);
case START: case START:
@@ -133,6 +136,7 @@ class IntroduceeProtocolEngine
return onRemoteRequest(txn, session, m); return onRemoteRequest(txn, session, m);
case AWAIT_RESPONSES: case AWAIT_RESPONSES:
case LOCAL_DECLINED: case LOCAL_DECLINED:
case REMOTE_DECLINED:
case LOCAL_ACCEPTED: case LOCAL_ACCEPTED:
case REMOTE_ACCEPTED: case REMOTE_ACCEPTED:
case AWAIT_AUTH: case AWAIT_AUTH:
@@ -153,6 +157,7 @@ class IntroduceeProtocolEngine
case LOCAL_ACCEPTED: case LOCAL_ACCEPTED:
return onRemoteAccept(txn, session, m); return onRemoteAccept(txn, session, m);
case START: case START:
case REMOTE_DECLINED:
case REMOTE_ACCEPTED: case REMOTE_ACCEPTED:
case AWAIT_AUTH: case AWAIT_AUTH:
case AWAIT_ACTIVATE: case AWAIT_ACTIVATE:
@@ -172,6 +177,7 @@ class IntroduceeProtocolEngine
case LOCAL_ACCEPTED: case LOCAL_ACCEPTED:
return onRemoteDecline(txn, session, m); return onRemoteDecline(txn, session, m);
case START: case START:
case REMOTE_DECLINED:
case REMOTE_ACCEPTED: case REMOTE_ACCEPTED:
case AWAIT_AUTH: case AWAIT_AUTH:
case AWAIT_ACTIVATE: case AWAIT_ACTIVATE:
@@ -190,6 +196,7 @@ class IntroduceeProtocolEngine
case START: case START:
case AWAIT_RESPONSES: case AWAIT_RESPONSES:
case LOCAL_DECLINED: case LOCAL_DECLINED:
case REMOTE_DECLINED:
case LOCAL_ACCEPTED: case LOCAL_ACCEPTED:
case REMOTE_ACCEPTED: case REMOTE_ACCEPTED:
case AWAIT_ACTIVATE: case AWAIT_ACTIVATE:
@@ -208,6 +215,7 @@ class IntroduceeProtocolEngine
case START: case START:
case AWAIT_RESPONSES: case AWAIT_RESPONSES:
case LOCAL_DECLINED: case LOCAL_DECLINED:
case REMOTE_DECLINED:
case LOCAL_ACCEPTED: case LOCAL_ACCEPTED:
case REMOTE_ACCEPTED: case REMOTE_ACCEPTED:
case AWAIT_AUTH: case AWAIT_AUTH:
@@ -272,26 +280,28 @@ class IntroduceeProtocolEngine
transportPropertyManager.getLocalProperties(txn); transportPropertyManager.getLocalProperties(txn);
// Send a ACCEPT message // Send a ACCEPT message
long localTimestamp = long localTimestamp = Math.max(timestamp + 1, getLocalTimestamp(s));
Math.max(timestamp + 1, getLocalTimestamp(s));
Message sent = sendAcceptMessage(txn, s, localTimestamp, publicKey, Message sent = sendAcceptMessage(txn, s, localTimestamp, publicKey,
localTimestamp, transportProperties, true); localTimestamp, transportProperties, true);
// Track the message // Track the message
messageTracker.trackOutgoingMessage(txn, sent); messageTracker.trackOutgoingMessage(txn, sent);
// Determine the next state // Determine the next state
IntroduceeState state = switch (s.getState()) {
s.getState() == AWAIT_RESPONSES ? LOCAL_ACCEPTED : AWAIT_AUTH; case AWAIT_RESPONSES:
IntroduceeSession sNew = IntroduceeSession return IntroduceeSession.addLocalAccept(s, LOCAL_ACCEPTED, sent,
.addLocalAccept(s, state, sent, publicKey, privateKey, publicKey, privateKey, localTimestamp,
localTimestamp, transportProperties); transportProperties);
case REMOTE_DECLINED:
if (state == AWAIT_AUTH) { return IntroduceeSession.clear(s, START, sent.getId(),
// Move to the AWAIT_AUTH state localTimestamp, s.getLastRemoteMessageId());
return onLocalAuth(txn, sNew); case REMOTE_ACCEPTED:
return onLocalAuth(txn, IntroduceeSession.addLocalAccept(s,
AWAIT_AUTH, sent, publicKey, privateKey, localTimestamp,
transportProperties));
default:
throw new AssertionError();
} }
// Move to the LOCAL_ACCEPTED state
return sNew;
} }
private IntroduceeSession onLocalDecline(Transaction txn, private IntroduceeSession onLocalDecline(Transaction txn,
@@ -308,10 +318,9 @@ class IntroduceeProtocolEngine
// Move to the START or LOCAL_DECLINED state, if still awaiting response // Move to the START or LOCAL_DECLINED state, if still awaiting response
IntroduceeState state = IntroduceeState state =
s.getState() == REMOTE_ACCEPTED ? START : LOCAL_DECLINED; s.getState() == AWAIT_RESPONSES ? LOCAL_DECLINED : START;
return IntroduceeSession return IntroduceeSession.clear(s, state, sent.getId(),
.clear(s, state, sent.getId(), sent.getTimestamp(), sent.getTimestamp(), s.getLastRemoteMessageId());
s.getLastRemoteMessageId());
} }
private IntroduceeSession onRemoteAccept(Transaction txn, private IntroduceeSession onRemoteAccept(Transaction txn,
@@ -357,22 +366,10 @@ class IntroduceeProtocolEngine
broadcastIntroductionResponseReceivedEvent(txn, s, broadcastIntroductionResponseReceivedEvent(txn, s,
s.getIntroducer().getId(), s.getRemote().author, m); s.getIntroducer().getId(), s.getRemote().author, m);
if (s.getState() == AWAIT_RESPONSES) { // Move to REMOTE_DECLINED state
// Mark the request message unavailable to answer return IntroduceeSession.clear(s, REMOTE_DECLINED,
markRequestsUnavailableToAnswer(txn, s); s.getLastLocalMessageId(), s.getLocalTimestamp(),
m.getMessageId());
// Send a DECLINE message
Message sent =
sendDeclineMessage(txn, s, getLocalTimestamp(s), false);
// Move back to START state
return IntroduceeSession.clear(s, START, sent.getId(),
sent.getTimestamp(), m.getMessageId());
} else {
// Move back to START state
return IntroduceeSession.clear(s, START, s.getLastLocalMessageId(),
s.getLocalTimestamp(), m.getMessageId());
}
} }
private IntroduceeSession onRemoteResponseWhenDeclined(Transaction txn, private IntroduceeSession onRemoteResponseWhenDeclined(Transaction txn,
@@ -385,6 +382,17 @@ class IntroduceeProtocolEngine
if (isInvalidDependency(s, m.getPreviousMessageId())) if (isInvalidDependency(s, m.getPreviousMessageId()))
return abort(txn, s); return abort(txn, s);
// Mark the response visible in the UI
markMessageVisibleInUi(txn, m.getMessageId());
// Track the incoming message
messageTracker
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
// Broadcast IntroductionResponseReceivedEvent
broadcastIntroductionResponseReceivedEvent(txn, s,
s.getIntroducer().getId(), s.getRemote().author, m);
// Move to START state // Move to START state
return IntroduceeSession.clear(s, START, s.getLastLocalMessageId(), return IntroduceeSession.clear(s, START, s.getLastLocalMessageId(),
s.getLocalTimestamp(), m.getMessageId()); s.getLocalTimestamp(), m.getMessageId());
@@ -525,9 +533,8 @@ class IntroduceeProtocolEngine
txn.attach(new IntroductionAbortedEvent(s.getSessionId())); txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
// Reset the session back to initial state // Reset the session back to initial state
return IntroduceeSession return IntroduceeSession.clear(s, START, sent.getId(),
.clear(s, START, sent.getId(), sent.getTimestamp(), sent.getTimestamp(), s.getLastRemoteMessageId());
s.getLastRemoteMessageId());
} }
private boolean isInvalidDependency(IntroduceeSession s, private boolean isInvalidDependency(IntroduceeSession s,

View File

@@ -12,10 +12,11 @@ enum IntroduceeState implements State {
START(0), START(0),
AWAIT_RESPONSES(1), AWAIT_RESPONSES(1),
LOCAL_DECLINED(2), LOCAL_DECLINED(2),
LOCAL_ACCEPTED(3), REMOTE_DECLINED(3),
REMOTE_ACCEPTED(4), LOCAL_ACCEPTED(4),
AWAIT_AUTH(5), REMOTE_ACCEPTED(5),
AWAIT_ACTIVATE(6); AWAIT_AUTH(6),
AWAIT_ACTIVATE(7);
private final int value; private final int value;

View File

@@ -17,7 +17,6 @@ import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
@@ -39,15 +38,12 @@ import org.junit.Test;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.test.TestPluginConfigModule.TRANSPORT_ID; import static org.briarproject.bramble.test.TestPluginConfigModule.TRANSPORT_ID;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.briarproject.bramble.test.TestUtils.getTransportProperties; import static org.briarproject.bramble.test.TestUtils.getTransportProperties;
import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap; import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap;
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID; import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
@@ -86,9 +82,6 @@ public class IntroductionIntegrationTest
private IntroduceeListener listener1; private IntroduceeListener listener1;
private IntroduceeListener listener2; private IntroduceeListener listener2;
private static final Logger LOG =
Logger.getLogger(IntroductionIntegrationTest.class.getName());
interface StateVisitor { interface StateVisitor {
AcceptMessage visit(AcceptMessage response); AcceptMessage visit(AcceptMessage response);
} }
@@ -374,11 +367,10 @@ public class IntroductionIntegrationTest
assertEquals(2, assertEquals(2,
introductionManager0.getIntroductionMessages(contactId2From0) introductionManager0.getIntroductionMessages(contactId2From0)
.size()); .size());
// introducee1 also sees the decline response from introducee2
assertEquals(3, assertEquals(3,
introductionManager1.getIntroductionMessages(contactId0From1) introductionManager1.getIntroductionMessages(contactId0From1)
.size()); .size());
assertEquals(2, assertEquals(3,
introductionManager2.getIntroductionMessages(contactId0From2) introductionManager2.getIntroductionMessages(contactId0From2)
.size()); .size());
assertFalse(listener0.aborted); assertFalse(listener0.aborted);
@@ -386,58 +378,6 @@ public class IntroductionIntegrationTest
assertFalse(listener2.aborted); assertFalse(listener2.aborted);
} }
@Test
public void testIntroductionSessionDelayedFirstDecline() throws Exception {
addListeners(false, false);
// make introduction
long time = clock.currentTimeMillis();
introductionManager0
.makeIntroduction(contact1From0, contact2From0, null, time);
// sync request messages
sync0To1(1, true);
sync0To2(1, true);
// wait for requests to arrive
eventWaiter.await(TIMEOUT, 2);
assertTrue(listener1.requestReceived);
assertTrue(listener2.requestReceived);
// sync first response
sync1To0(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.response1Received);
// sync fake transport properties back to 1, so Message ACK can arrive
// and the assertDefaultUiMessages() check at the end will not fail
TransportProperties tp = new TransportProperties(
Collections.singletonMap("key", "value"));
c0.getTransportPropertyManager()
.mergeLocalProperties(getTransportId(), tp);
sync0To1(1, true);
// sync second response
sync2To0(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.response2Received);
// sync first forwarded response
sync0To2(1, true);
// note how the second response will not be forwarded anymore
assertFalse(contactManager1
.contactExists(author2.getId(), author1.getId()));
assertFalse(contactManager2
.contactExists(author1.getId(), author2.getId()));
assertDefaultUiMessages();
assertFalse(listener0.aborted);
assertFalse(listener1.aborted);
assertFalse(listener2.aborted);
}
@Test @Test
public void testResponseAndAuthInOneSync() throws Exception { public void testResponseAndAuthInOneSync() throws Exception {
addListeners(true, true); addListeners(true, true);
@@ -467,9 +407,8 @@ public class IntroductionIntegrationTest
assertTrue(listener2.requestReceived); assertTrue(listener2.requestReceived);
// answer request manually // answer request manually
introductionManager2 introductionManager2.respondToIntroduction(contactId0From2,
.respondToIntroduction(contactId0From2, listener2.sessionId, time, listener2.sessionId, time, true);
true);
// sync second response and AUTH // sync second response and AUTH
sync2To0(2, true); sync2To0(2, true);
@@ -479,7 +418,7 @@ public class IntroductionIntegrationTest
// Forward AUTH // Forward AUTH
sync0To1(1, true); sync0To1(1, true);
// Second AUTH and ACTIATE and forward them // Second AUTH and ACTIVATE and forward them
sync1To0(2, true); sync1To0(2, true);
sync0To2(2, true); sync0To2(2, true);
@@ -495,13 +434,12 @@ public class IntroductionIntegrationTest
} }
/** /**
* When an introducee declines an introduction, * When an introducee declines an introduction, the other introducee needs
* the other introducee needs to respond before returning to START state, * to respond before returning to the START state, otherwise a subsequent
* otherwise a subsequent attempt at introducing the same contacts will fail * attempt at introducing the same contacts will fail.
*/ */
@Test @Test
public void testAutomaticSecondDecline() throws Exception { public void testIntroductionSessionManualDecline() throws Exception {
// introducee1 declines automatically and introducee2 doesn't answer
addListeners(false, true); addListeners(false, true);
listener2.answerRequests = false; listener2.answerRequests = false;
@@ -547,9 +485,18 @@ public class IntroductionIntegrationTest
// assert that introducee2 is in correct state // assert that introducee2 is in correct state
introduceeSession = getIntroduceeSession(c2); introduceeSession = getIntroduceeSession(c2);
assertEquals(IntroduceeState.REMOTE_DECLINED,
introduceeSession.getState());
// answer request manually
introductionManager2.respondToIntroduction(contactId0From2,
listener2.sessionId, time, false);
// now introducee2 should have returned to the START state
introduceeSession = getIntroduceeSession(c2);
assertEquals(IntroduceeState.START, introduceeSession.getState()); assertEquals(IntroduceeState.START, introduceeSession.getState());
// second response should be an immediate automatic DECLINE // sync second response
sync2To0(1, true); sync2To0(1, true);
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.response2Received); assertTrue(listener0.response2Received);
@@ -562,7 +509,7 @@ public class IntroductionIntegrationTest
introduceeSession = getIntroduceeSession(c1); introduceeSession = getIntroduceeSession(c1);
assertEquals(LOCAL_DECLINED, introduceeSession.getState()); assertEquals(LOCAL_DECLINED, introduceeSession.getState());
// forward automatic decline // forward second response
sync0To1(1, true); sync0To1(1, true);
// introducee1 can finally move to the START // introducee1 can finally move to the START
@@ -579,16 +526,14 @@ public class IntroductionIntegrationTest
introductionManager0.getIntroductionMessages(contactId2From0) introductionManager0.getIntroductionMessages(contactId2From0)
.size()); .size());
assertGroupCount(messageTracker0, g2.getId(), 2, 1); assertGroupCount(messageTracker0, g2.getId(), 2, 1);
assertEquals(2, assertEquals(3,
introductionManager1.getIntroductionMessages(contactId0From1) introductionManager1.getIntroductionMessages(contactId0From1)
.size()); .size());
assertGroupCount(messageTracker1, g1.getId(), 2, 1); assertGroupCount(messageTracker1, g1.getId(), 3, 2);
// the automatic DECLINE is invisible in the UI assertEquals(3,
// so there's just the remote REQUEST and remote DECLINE
assertEquals(2,
introductionManager2.getIntroductionMessages(contactId0From2) introductionManager2.getIntroductionMessages(contactId0From2)
.size()); .size());
assertGroupCount(messageTracker2, g2.getId(), 2, 2); assertGroupCount(messageTracker2, g2.getId(), 3, 2);
assertFalse(listener0.aborted); assertFalse(listener0.aborted);
assertFalse(listener1.aborted); assertFalse(listener1.aborted);