Fix introduction response messages in UI and some minor fixes

This commit is contained in:
Torsten Grote
2018-04-27 15:24:13 -03:00
parent 55a329a879
commit 5f6af4e40f
7 changed files with 213 additions and 20 deletions

View File

@@ -124,6 +124,9 @@ abstract class ConversationItem {
text = ctx.getString(
R.string.introduction_response_accepted_sent,
ir.getName());
text += "\n\n" + ctx.getString(
R.string.introduction_response_accepted_sent_info,
ir.getName());
} else {
text = ctx.getString(
R.string.introduction_response_declined_sent,

View File

@@ -156,6 +156,7 @@
<string name="introduction_request_exists_received">%1$s has asked to introduce you to %2$s, but %2$s is already in your contact list. Since %1$s might not know that, you can still respond:</string>
<string name="introduction_request_answered_received">%1$s has asked to introduce you to %2$s.</string>
<string name="introduction_response_accepted_sent">You accepted the introduction to %1$s.</string>
<string name="introduction_response_accepted_sent_info">Before %1$s gets added to your contacts, they need to accept the introduction as well. This might take some time.</string>
<string name="introduction_response_declined_sent">You declined the introduction to %1$s.</string>
<string name="introduction_response_accepted_received">%1$s accepted the introduction to %2$s.</string>
<string name="introduction_response_declined_received">%1$s declined the introduction to %2$s.</string>

View File

@@ -25,6 +25,9 @@ public interface IntroductionManager extends ConversationClient {
*/
int CLIENT_VERSION = 1;
/**
* Returns true if both contacts can be introduced at this moment.
*/
boolean canIntroduce(Contact c1, Contact c2) throws DbException;
/**

View File

@@ -144,15 +144,15 @@ abstract class AbstractProtocolEngine<S extends Session>
}
}
void broadcastIntroductionResponseReceivedEvent(Transaction txn,
Session s, AuthorId sender, AbstractIntroductionMessage m)
void broadcastIntroductionResponseReceivedEvent(Transaction txn, Session s,
AuthorId sender, Author otherAuthor, AbstractIntroductionMessage m)
throws DbException {
AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId();
Contact c = contactManager.getContact(txn, sender, localAuthorId);
IntroductionResponse response =
new IntroductionResponse(s.getSessionId(), m.getMessageId(),
m.getGroupId(), s.getRole(), m.getTimestamp(), false,
false, false, false, c.getAuthor().getName(),
false, false, false, otherAuthor.getName(),
m instanceof AcceptMessage);
IntroductionResponseReceivedEvent e =
new IntroductionResponseReceivedEvent(c.getId(), response);

View File

@@ -355,7 +355,7 @@ class IntroduceeProtocolEngine
// Broadcast IntroductionResponseReceivedEvent
broadcastIntroductionResponseReceivedEvent(txn, s,
s.getIntroducer().getId(), m);
s.getIntroducer().getId(), s.getRemote().author, m);
if (s.getState() == AWAIT_RESPONSES) {
// Mark the request message unavailable to answer

View File

@@ -261,19 +261,24 @@ class IntroducerProtocolEngine
// Create the next state
IntroducerState state = AWAIT_AUTHS;
Introducee introduceeA, introduceeB;
Author sender, other;
if (senderIsAlice) {
if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_B;
introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId());
introduceeB = new Introducee(s.getIntroduceeB(), sent);
sender = introduceeA.author;
other = introduceeB.author;
} else {
if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_A;
introduceeA = new Introducee(s.getIntroduceeA(), sent);
introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId());
sender = introduceeB.author;
other = introduceeA.author;
}
// Broadcast IntroductionResponseReceivedEvent
Author sender = senderIsAlice ? introduceeA.author : introduceeB.author;
broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(), m);
broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(),
other, m);
// Move to the next state
return new IntroducerSession(s.getSessionId(), state,
@@ -313,17 +318,22 @@ class IntroducerProtocolEngine
m.getTransportProperties(), false);
Introducee introduceeA, introduceeB;
Author sender, other;
if (senderIsAlice) {
introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId());
introduceeB = new Introducee(s.getIntroduceeB(), sent);
sender = introduceeA.author;
other = introduceeB.author;
} else {
introduceeA = new Introducee(s.getIntroduceeA(), sent);
introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId());
sender = introduceeB.author;
other = introduceeA.author;
}
// Broadcast IntroductionResponseReceivedEvent
Author sender = senderIsAlice ? introduceeA.author : introduceeB.author;
broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(), m);
broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(),
other, m);
return new IntroducerSession(s.getSessionId(), START,
s.getRequestTimestamp(), introduceeA, introduceeB);
@@ -360,19 +370,24 @@ class IntroducerProtocolEngine
// Create the next state
IntroducerState state = START;
Introducee introduceeA, introduceeB;
Author sender, other;
if (senderIsAlice) {
if (s.getState() == AWAIT_RESPONSES) state = A_DECLINED;
introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId());
introduceeB = new Introducee(s.getIntroduceeB(), sent);
sender = introduceeA.author;
other = introduceeB.author;
} else {
if (s.getState() == AWAIT_RESPONSES) state = B_DECLINED;
introduceeA = new Introducee(s.getIntroduceeA(), sent);
introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId());
sender = introduceeB.author;
other = introduceeA.author;
}
// Broadcast IntroductionResponseReceivedEvent
Author sender = senderIsAlice ? introduceeA.author : introduceeB.author;
broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(), m);
broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(),
other, m);
return new IntroducerSession(s.getSessionId(), state,
s.getRequestTimestamp(), introduceeA, introduceeB);
@@ -405,17 +420,22 @@ class IntroducerProtocolEngine
Message sent = sendDeclineMessage(txn, i, timestamp, false);
Introducee introduceeA, introduceeB;
Author sender, other;
if (senderIsAlice) {
introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId());
introduceeB = new Introducee(s.getIntroduceeB(), sent);
sender = introduceeA.author;
other = introduceeB.author;
} else {
introduceeA = new Introducee(s.getIntroduceeA(), sent);
introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId());
sender = introduceeB.author;
other = introduceeA.author;
}
// Broadcast IntroductionResponseReceivedEvent
Author sender = senderIsAlice ? introduceeA.author : introduceeB.author;
broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(), m);
broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(),
other, m);
return new IntroducerSession(s.getSessionId(), START,
s.getRequestTimestamp(), introduceeA, introduceeB);

View File

@@ -28,6 +28,7 @@ import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction.IntroductionManager;
import org.briarproject.briar.api.introduction.IntroductionMessage;
import org.briarproject.briar.api.introduction.IntroductionRequest;
import org.briarproject.briar.api.introduction.IntroductionResponse;
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent;
import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent;
@@ -51,6 +52,7 @@ import static org.briarproject.bramble.test.TestUtils.getTransportProperties;
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_VERSION;
import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_RESPONSES;
import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_DECLINED;
import static org.briarproject.briar.introduction.IntroducerState.A_DECLINED;
import static org.briarproject.briar.introduction.IntroducerState.B_DECLINED;
@@ -146,24 +148,32 @@ public class IntroductionIntegrationTest
sync0To1(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
assertEquals(introducee2.getAuthor().getName(),
listener1.getRequest().getName());
assertGroupCount(messageTracker1, g1.getId(), 2, 1);
// sync second REQUEST message
sync0To2(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener2.requestReceived);
assertEquals(introducee1.getAuthor().getName(),
listener2.getRequest().getName());
assertGroupCount(messageTracker2, g2.getId(), 2, 1);
// sync first ACCEPT message
sync1To0(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.response1Received);
assertEquals(introducee2.getAuthor().getName(),
listener0.getResponse().getName());
assertGroupCount(messageTracker0, g1.getId(), 2, 1);
// sync second ACCEPT message
sync2To0(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.response2Received);
assertEquals(introducee1.getAuthor().getName(),
listener0.getResponse().getName());
assertGroupCount(messageTracker0, g2.getId(), 2, 1);
// sync forwarded ACCEPT messages to introducees
@@ -259,6 +269,10 @@ public class IntroductionIntegrationTest
assertEquals(alice ? A_DECLINED : B_DECLINED,
introducerSession.getState());
// assert that the name on the decline event is correct
assertEquals(introducee2.getAuthor().getName(),
listener0.getResponse().getName());
// sync second response
sync2To0(1, true);
eventWaiter.await(TIMEOUT, 1);
@@ -271,6 +285,11 @@ public class IntroductionIntegrationTest
// sync first forwarded response
sync0To2(1, true);
// assert that the name on the decline event is correct
eventWaiter.await(TIMEOUT, 1);
assertEquals(introducee1.getAuthor().getName(),
listener2.getResponse().getName());
// note how the introducer does not forward the second response,
// because after the first decline the protocol finished
@@ -339,6 +358,11 @@ public class IntroductionIntegrationTest
sync0To2(1, true);
sync0To1(1, true);
// assert that the name on the decline event is correct
eventWaiter.await(TIMEOUT, 1);
assertEquals(contact2From0.getAuthor().getName(),
listener1.getResponse().getName());
assertFalse(contactManager1
.contactExists(author2.getId(), author1.getId()));
assertFalse(contactManager2
@@ -408,8 +432,6 @@ public class IntroductionIntegrationTest
assertFalse(contactManager2
.contactExists(author1.getId(), author2.getId()));
// since introducee2 was already in FINISHED state when
// introducee1's response arrived, she ignores and deletes it
assertDefaultUiMessages();
assertFalse(listener0.aborted);
assertFalse(listener1.aborted);
@@ -417,7 +439,7 @@ public class IntroductionIntegrationTest
}
@Test
public void testResponseAndAckInOneSession() throws Exception {
public void testResponseAndAuthInOneSync() throws Exception {
addListeners(true, true);
// make introduction
@@ -449,10 +471,125 @@ public class IntroductionIntegrationTest
.respondToIntroduction(contactId0From2, listener2.sessionId, time,
true);
// sync second response and ACK and make sure there is no abort
// sync second response and AUTH
sync2To0(2, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.response2Received);
// Forward AUTH
sync0To1(1, true);
// Second AUTH and ACTIATE and forward them
sync1To0(2, true);
sync0To2(2, true);
assertTrue(contactManager1
.contactExists(author2.getId(), author1.getId()));
assertTrue(contactManager2
.contactExists(author1.getId(), author2.getId()));
assertDefaultUiMessages();
assertFalse(listener0.aborted);
assertFalse(listener1.aborted);
assertFalse(listener2.aborted);
}
/**
* When an introducee declines an introduction,
* the other introducee needs to respond before returning to START state,
* otherwise a subsequent attempt at introducing the same contacts will fail
*/
@Test
public void testAutomaticSecondDecline() throws Exception {
// introducee1 declines automatically and introducee2 doesn't answer
addListeners(false, true);
listener2.answerRequests = false;
// make introduction
long time = clock.currentTimeMillis();
Contact introducee1 = contact1From0;
Contact introducee2 = contact2From0;
introductionManager0
.makeIntroduction(introducee1, introducee2, null, time);
// sync request messages
sync0To1(1, true);
sync0To2(1, true);
// assert that introducee1 is in correct state
IntroduceeSession introduceeSession = getIntroduceeSession(c1);
assertEquals(LOCAL_DECLINED, introduceeSession.getState());
// sync first response
sync1To0(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.response1Received);
// assert that introducer is in correct state
boolean alice = c0.getIntroductionCrypto()
.isAlice(introducee1.getAuthor().getId(),
introducee2.getAuthor().getId());
IntroducerSession introducerSession = getIntroducerSession();
assertEquals(alice ? A_DECLINED : B_DECLINED,
introducerSession.getState());
// assert that introducee2 is in correct state
introduceeSession = getIntroduceeSession(c2);
assertEquals(AWAIT_RESPONSES, introduceeSession.getState());
// forward first DECLINE
sync0To2(1, true);
// assert that the name on the decline event is correct
eventWaiter.await(TIMEOUT, 1);
assertEquals(introducee1.getAuthor().getName(),
listener2.getResponse().getName());
// assert that introducee2 is in correct state
introduceeSession = getIntroduceeSession(c2);
assertEquals(IntroduceeState.START, introduceeSession.getState());
// second response should be an immediate automatic DECLINE
sync2To0(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.response2Received);
// assert that introducer now moved to START state
introducerSession = getIntroducerSession();
assertEquals(START, introducerSession.getState());
// introducee1 is still waiting for second response
introduceeSession = getIntroduceeSession(c1);
assertEquals(LOCAL_DECLINED, introduceeSession.getState());
// forward automatic decline
sync0To1(1, true);
// introducee1 can finally move to the START
introduceeSession = getIntroduceeSession(c1);
assertEquals(IntroduceeState.START, introduceeSession.getState());
Group g1 = introductionManager0.getContactGroup(introducee1);
Group g2 = introductionManager0.getContactGroup(introducee2);
assertEquals(2,
introductionManager0.getIntroductionMessages(contactId1From0)
.size());
assertGroupCount(messageTracker0, g1.getId(), 2, 1);
assertEquals(2,
introductionManager0.getIntroductionMessages(contactId2From0)
.size());
assertGroupCount(messageTracker0, g2.getId(), 2, 1);
assertEquals(2,
introductionManager1.getIntroductionMessages(contactId0From1)
.size());
assertGroupCount(messageTracker1, g1.getId(), 2, 1);
// the automatic DECLINE is invisible in the UI
// so there's just the remote REQUEST and remote DECLINE
assertEquals(2,
introductionManager2.getIntroductionMessages(contactId0From2)
.size());
assertGroupCount(messageTracker2, g2.getId(), 2, 2);
assertFalse(listener0.aborted);
assertFalse(listener1.aborted);
assertFalse(listener2.aborted);
@@ -1012,11 +1149,25 @@ public class IntroductionIntegrationTest
@MethodsNotNullByDefault
@ParametersNotNullByDefault
private class IntroduceeListener implements EventListener {
private abstract class IntroductionListener implements EventListener {
protected volatile boolean aborted = false;
protected volatile Event latestEvent;
IntroductionResponse getResponse() {
assertTrue(
latestEvent instanceof IntroductionResponseReceivedEvent);
return ((IntroductionResponseReceivedEvent) latestEvent)
.getIntroductionResponse();
}
}
@MethodsNotNullByDefault
@ParametersNotNullByDefault
private class IntroduceeListener extends IntroductionListener {
private volatile boolean requestReceived = false;
private volatile boolean succeeded = false;
private volatile boolean aborted = false;
private volatile boolean answerRequests = true;
private volatile SessionId sessionId;
@@ -1031,6 +1182,7 @@ public class IntroductionIntegrationTest
@Override
public void eventOccurred(Event e) {
if (e instanceof IntroductionRequestReceivedEvent) {
latestEvent = e;
IntroductionRequestReceivedEvent introEvent =
((IntroductionRequestReceivedEvent) e);
requestReceived = true;
@@ -1053,29 +1205,42 @@ public class IntroductionIntegrationTest
} finally {
eventWaiter.resume();
}
} else if (e instanceof IntroductionResponseReceivedEvent) {
// only broadcast for DECLINE messages in introducee role
latestEvent = e;
eventWaiter.resume();
} else if (e instanceof IntroductionSucceededEvent) {
latestEvent = e;
succeeded = true;
Contact contact = ((IntroductionSucceededEvent) e).getContact();
eventWaiter
.assertFalse(contact.getId().equals(contactId0From1));
eventWaiter.resume();
} else if (e instanceof IntroductionAbortedEvent) {
latestEvent = e;
aborted = true;
eventWaiter.resume();
}
}
private IntroductionRequest getRequest() {
assertTrue(
latestEvent instanceof IntroductionRequestReceivedEvent);
return ((IntroductionRequestReceivedEvent) latestEvent)
.getIntroductionRequest();
}
}
@NotNullByDefault
private class IntroducerListener implements EventListener {
private class IntroducerListener extends IntroductionListener {
private volatile boolean response1Received = false;
private volatile boolean response2Received = false;
private volatile boolean aborted = false;
@Override
public void eventOccurred(Event e) {
if (e instanceof IntroductionResponseReceivedEvent) {
latestEvent = e;
ContactId c =
((IntroductionResponseReceivedEvent) e)
.getContactId();
@@ -1086,6 +1251,7 @@ public class IntroductionIntegrationTest
}
eventWaiter.resume();
} else if (e instanceof IntroductionAbortedEvent) {
latestEvent = e;
aborted = true;
eventWaiter.resume();
}