mirror of
https://github.com/briar/briar.git
synced 2025-12-23 23:37:43 -05:00
Implement auto-declining for self-destructed introductions
This commit is contained in:
committed by
Torsten Grote
parent
49850e4198
commit
0bf10a827f
1
.idea/dictionaries/briar.xml
generated
1
.idea/dictionaries/briar.xml
generated
@@ -7,6 +7,7 @@
|
||||
<w>encrypter</w>
|
||||
<w>identicon</w>
|
||||
<w>introducee</w>
|
||||
<w>introducees</w>
|
||||
<w>introducer</w>
|
||||
<w>onboarding</w>
|
||||
</words>
|
||||
|
||||
@@ -289,6 +289,10 @@ class ConversationVisitor implements
|
||||
text = ctx.getString(
|
||||
R.string.introduction_response_accepted_sent,
|
||||
introducedAuthor) + suffix;
|
||||
} else if (r.isAutoDecline()) {
|
||||
text = ctx.getString(
|
||||
R.string.introduction_response_declined_auto,
|
||||
introducedAuthor);
|
||||
} else {
|
||||
text = ctx.getString(
|
||||
R.string.introduction_response_declined_sent,
|
||||
|
||||
@@ -317,6 +317,7 @@
|
||||
<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_declined_auto">The introduction to %1$s was automatically declined.</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>
|
||||
<string name="introduction_response_declined_received_by_introducee">%1$s says that %2$s declined the introduction.</string>
|
||||
|
||||
@@ -26,9 +26,9 @@ public class IntroductionResponse extends ConversationResponse {
|
||||
boolean local, boolean read, boolean sent, boolean seen,
|
||||
SessionId sessionId, boolean accepted, Author author,
|
||||
AuthorInfo introducedAuthorInfo, Role role, boolean canSucceed,
|
||||
long autoDeleteTimer) {
|
||||
long autoDeleteTimer, boolean isAutoDecline) {
|
||||
super(messageId, groupId, time, local, read, sent, seen, sessionId,
|
||||
accepted, autoDeleteTimer, false);
|
||||
accepted, autoDeleteTimer, isAutoDecline);
|
||||
this.introducedAuthor = author;
|
||||
this.introducedAuthorInfo = introducedAuthorInfo;
|
||||
this.ourRole = role;
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
@@ -39,6 +40,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionManager.MAJOR_VERSION;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
|
||||
import static org.briarproject.briar.introduction.MessageType.ABORT;
|
||||
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
|
||||
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
|
||||
@@ -105,6 +107,10 @@ abstract class AbstractProtocolEngine<S extends Session<?>>
|
||||
m = messageEncoder.encodeRequestMessage(s.getContactGroupId(),
|
||||
timestamp, s.getLastLocalMessageId(), author, text, timer);
|
||||
sendMessage(txn, REQUEST, s.getSessionId(), m, true, timer);
|
||||
// Set the auto-delete timer duration on the local message
|
||||
if (timer != NO_AUTO_DELETE_TIMER) {
|
||||
db.setCleanupTimerDuration(txn, m.getId(), timer);
|
||||
}
|
||||
} else {
|
||||
m = messageEncoder.encodeRequestMessage(s.getContactGroupId(),
|
||||
timestamp, s.getLastLocalMessageId(), author, text);
|
||||
@@ -128,6 +134,10 @@ abstract class AbstractProtocolEngine<S extends Session<?>>
|
||||
ephemeralPublicKey, acceptTimestamp, transportProperties,
|
||||
timer);
|
||||
sendMessage(txn, ACCEPT, s.getSessionId(), m, visible, timer);
|
||||
// Set the auto-delete timer duration on the message
|
||||
if (timer != NO_AUTO_DELETE_TIMER) {
|
||||
db.setCleanupTimerDuration(txn, m.getId(), timer);
|
||||
}
|
||||
} else {
|
||||
m = messageEncoder.encodeAcceptMessage(s.getContactGroupId(),
|
||||
timestamp, s.getLastLocalMessageId(), s.getSessionId(),
|
||||
@@ -139,7 +149,8 @@ abstract class AbstractProtocolEngine<S extends Session<?>>
|
||||
}
|
||||
|
||||
Message sendDeclineMessage(Transaction txn, PeerSession s, long timestamp,
|
||||
boolean visible) throws DbException {
|
||||
boolean visible, boolean isAutoDecline) throws DbException {
|
||||
if (!visible && isAutoDecline) throw new IllegalArgumentException();
|
||||
Message m;
|
||||
ContactId c = getContactId(txn, s.getContactGroupId());
|
||||
if (contactSupportsAutoDeletion(txn, c)) {
|
||||
@@ -148,7 +159,25 @@ abstract class AbstractProtocolEngine<S extends Session<?>>
|
||||
m = messageEncoder.encodeDeclineMessage(s.getContactGroupId(),
|
||||
timestamp, s.getLastLocalMessageId(), s.getSessionId(),
|
||||
timer);
|
||||
sendMessage(txn, DECLINE, s.getSessionId(), m, visible, timer);
|
||||
sendMessage(txn, DECLINE, s.getSessionId(), m, visible, timer,
|
||||
isAutoDecline);
|
||||
// Set the auto-delete timer duration on the local message
|
||||
if (timer != NO_AUTO_DELETE_TIMER) {
|
||||
db.setCleanupTimerDuration(txn, m.getId(), timer);
|
||||
}
|
||||
if (isAutoDecline) {
|
||||
// Broadcast an event, so the auto-decline becomes visible
|
||||
IntroduceeSession session = (IntroduceeSession) s;
|
||||
Author author = session.getRemote().author;
|
||||
AuthorInfo authorInfo =
|
||||
authorManager.getAuthorInfo(txn, author.getId());
|
||||
IntroductionResponse response = new IntroductionResponse(
|
||||
m.getId(), s.getContactGroupId(), m.getTimestamp(),
|
||||
true, true, false, false, s.getSessionId(), false,
|
||||
author, authorInfo, INTRODUCEE, false, timer, true);
|
||||
Event e = new IntroductionResponseReceivedEvent(response, c);
|
||||
txn.attach(e);
|
||||
}
|
||||
} else {
|
||||
m = messageEncoder.encodeDeclineMessage(s.getContactGroupId(),
|
||||
timestamp, s.getLastLocalMessageId(), s.getSessionId());
|
||||
@@ -192,9 +221,16 @@ abstract class AbstractProtocolEngine<S extends Session<?>>
|
||||
private void sendMessage(Transaction txn, MessageType type,
|
||||
SessionId sessionId, Message m, boolean visibleInConversation,
|
||||
long autoDeleteTimer) throws DbException {
|
||||
sendMessage(txn, type, sessionId, m, visibleInConversation,
|
||||
autoDeleteTimer, false);
|
||||
}
|
||||
|
||||
private void sendMessage(Transaction txn, MessageType type,
|
||||
SessionId sessionId, Message m, boolean visibleInConversation,
|
||||
long autoDeleteTimer, boolean isAutoDecline) throws DbException {
|
||||
BdfDictionary meta = messageEncoder.encodeMetadata(type, sessionId,
|
||||
m.getTimestamp(), true, true, visibleInConversation,
|
||||
autoDeleteTimer);
|
||||
autoDeleteTimer, isAutoDecline);
|
||||
try {
|
||||
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||
} catch (FormatException e) {
|
||||
@@ -215,7 +251,7 @@ abstract class AbstractProtocolEngine<S extends Session<?>>
|
||||
m.getTimestamp(), false, false, false, false,
|
||||
s.getSessionId(), m instanceof AcceptMessage,
|
||||
otherAuthor, otherAuthorInfo, s.getRole(), canSucceed,
|
||||
m.getAutoDeleteTimer());
|
||||
m.getAutoDeleteTimer(), false);
|
||||
IntroductionResponseReceivedEvent e =
|
||||
new IntroductionResponseReceivedEvent(response, c.getId());
|
||||
txn.attach(e);
|
||||
|
||||
@@ -123,12 +123,13 @@ class IntroduceeProtocolEngine
|
||||
|
||||
@Override
|
||||
public IntroduceeSession onDeclineAction(Transaction txn,
|
||||
IntroduceeSession session) throws DbException {
|
||||
IntroduceeSession session, boolean isAutoDecline)
|
||||
throws DbException {
|
||||
switch (session.getState()) {
|
||||
case AWAIT_RESPONSES:
|
||||
case REMOTE_DECLINED:
|
||||
case REMOTE_ACCEPTED:
|
||||
return onLocalDecline(txn, session);
|
||||
return onLocalDecline(txn, session, isAutoDecline);
|
||||
case START:
|
||||
case LOCAL_DECLINED:
|
||||
case LOCAL_ACCEPTED:
|
||||
@@ -319,13 +320,14 @@ class IntroduceeProtocolEngine
|
||||
}
|
||||
|
||||
private IntroduceeSession onLocalDecline(Transaction txn,
|
||||
IntroduceeSession s) throws DbException {
|
||||
IntroduceeSession s, boolean isAutoDecline) throws DbException {
|
||||
// Mark the request message unavailable to answer
|
||||
markRequestsUnavailableToAnswer(txn, s);
|
||||
|
||||
// Send a DECLINE message
|
||||
long localTimestamp = getTimestampForVisibleMessage(txn, s);
|
||||
Message sent = sendDeclineMessage(txn, s, localTimestamp, true);
|
||||
Message sent =
|
||||
sendDeclineMessage(txn, s, localTimestamp, true, isAutoDecline);
|
||||
|
||||
// Track the message
|
||||
messageTracker.trackOutgoingMessage(txn, sent);
|
||||
|
||||
@@ -98,7 +98,7 @@ class IntroducerProtocolEngine
|
||||
|
||||
@Override
|
||||
public IntroducerSession onDeclineAction(Transaction txn,
|
||||
IntroducerSession s) {
|
||||
IntroducerSession s, boolean isAutoDecline) {
|
||||
throw new UnsupportedOperationException(); // Invalid in this role
|
||||
}
|
||||
|
||||
@@ -387,7 +387,7 @@ class IntroducerProtocolEngine
|
||||
Introducee i = getOtherIntroducee(s, m.getGroupId());
|
||||
// The forwarded message will be visible to the introducee
|
||||
long localTimestamp = getTimestampForVisibleMessage(txn, s, i);
|
||||
Message sent = sendDeclineMessage(txn, i, localTimestamp, false);
|
||||
Message sent = sendDeclineMessage(txn, i, localTimestamp, false, false);
|
||||
|
||||
// Create the next state
|
||||
IntroducerState state = START;
|
||||
@@ -442,7 +442,7 @@ class IntroducerProtocolEngine
|
||||
Introducee i = getOtherIntroducee(s, m.getGroupId());
|
||||
// The forwarded message will be visible to the introducee
|
||||
long localTimestamp = getTimestampForVisibleMessage(txn, s, i);
|
||||
Message sent = sendDeclineMessage(txn, i, localTimestamp, false);
|
||||
Message sent = sendDeclineMessage(txn, i, localTimestamp, false, false);
|
||||
|
||||
Introducee introduceeA, introduceeB;
|
||||
Author sender, other;
|
||||
|
||||
@@ -10,6 +10,7 @@ interface IntroductionConstants {
|
||||
String MSG_KEY_VISIBLE_IN_UI = "visibleInUi";
|
||||
String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer";
|
||||
String MSG_KEY_AUTO_DELETE_TIMER = "autoDeleteTimer";
|
||||
String MSG_KEY_IS_AUTO_DECLINE = "isAutoDecline";
|
||||
|
||||
// Session Keys
|
||||
String SESSION_KEY_SESSION_ID = "sessionId";
|
||||
|
||||
@@ -20,7 +20,7 @@ interface IntroductionCrypto {
|
||||
|
||||
/**
|
||||
* Returns true if the local author is alice
|
||||
*
|
||||
* <p>
|
||||
* Alice is the Author whose unique ID has the lower ID,
|
||||
* comparing the IDs as byte strings.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.cleanup.CleanupHook;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
@@ -28,6 +29,7 @@ import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.MessageStatus;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
||||
import org.briarproject.briar.api.autodelete.event.ConversationMessagesDeletedEvent;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
||||
@@ -54,8 +56,10 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
|
||||
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
|
||||
import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_RESPONSES;
|
||||
import static org.briarproject.briar.introduction.IntroduceeState.REMOTE_DECLINED;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.A_DECLINED;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.B_DECLINED;
|
||||
@@ -71,7 +75,7 @@ import static org.briarproject.briar.introduction.MessageType.REQUEST;
|
||||
@NotNullByDefault
|
||||
class IntroductionManagerImpl extends ConversationClientImpl
|
||||
implements IntroductionManager, OpenDatabaseHook, ContactHook,
|
||||
ClientVersioningHook {
|
||||
ClientVersioningHook, CleanupHook {
|
||||
|
||||
private final ClientVersioningManager clientVersioningManager;
|
||||
private final ContactGroupFactory contactGroupFactory;
|
||||
@@ -170,6 +174,11 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
||||
BdfDictionary bdfMeta) throws DbException, FormatException {
|
||||
// Parse the metadata
|
||||
MessageMetadata meta = messageParser.parseMetadata(bdfMeta);
|
||||
// set the clean-up timer that will be started when message gets read
|
||||
long timer = meta.getAutoDeleteTimer();
|
||||
if (timer != NO_AUTO_DELETE_TIMER) {
|
||||
db.setCleanupTimerDuration(txn, m.getId(), timer);
|
||||
}
|
||||
// Look up the session, if there is one
|
||||
SessionId sessionId = meta.getSessionId();
|
||||
IntroduceeSession newIntroduceeSession = null;
|
||||
@@ -362,7 +371,19 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
||||
@Override
|
||||
public void respondToIntroduction(ContactId contactId, SessionId sessionId,
|
||||
boolean accept) throws DbException {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
respondToIntroduction(contactId, sessionId, accept, false);
|
||||
}
|
||||
|
||||
private void respondToIntroduction(ContactId contactId, SessionId sessionId,
|
||||
boolean accept, boolean isAutoDecline) throws DbException {
|
||||
db.transaction(false,
|
||||
txn -> respondToIntroduction(txn, contactId, sessionId, accept,
|
||||
isAutoDecline));
|
||||
}
|
||||
|
||||
private void respondToIntroduction(Transaction txn, ContactId contactId,
|
||||
SessionId sessionId, boolean accept, boolean isAutoDecline)
|
||||
throws DbException {
|
||||
try {
|
||||
// Look up the session
|
||||
StoredSession ss = getSession(txn, sessionId);
|
||||
@@ -381,15 +402,13 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
||||
if (accept) {
|
||||
session = introduceeEngine.onAcceptAction(txn, session);
|
||||
} else {
|
||||
session = introduceeEngine.onDeclineAction(txn, session);
|
||||
session = introduceeEngine
|
||||
.onDeclineAction(txn, session, isAutoDecline);
|
||||
}
|
||||
// Store the updated session
|
||||
storeSession(txn, ss.storageId, session);
|
||||
db.commitTransaction(txn);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,7 +506,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
||||
return new IntroductionResponse(m, contactGroupId, meta.getTimestamp(),
|
||||
meta.isLocal(), meta.isRead(), status.isSent(), status.isSeen(),
|
||||
sessionId, accept, author, authorInfo, role, canSucceed,
|
||||
meta.getAutoDeleteTimer());
|
||||
meta.getAutoDeleteTimer(), meta.isAutoDecline());
|
||||
}
|
||||
|
||||
private void removeSessionWithIntroducer(Transaction txn,
|
||||
@@ -547,6 +566,67 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteMessages(Transaction txn, GroupId g,
|
||||
Collection<MessageId> messageIds) throws DbException {
|
||||
ContactId c;
|
||||
Map<SessionId, DeletableSession> sessions = new HashMap<>();
|
||||
try {
|
||||
// get the ContactId from the given GroupId
|
||||
c = clientHelper.getContactId(txn, g);
|
||||
// get sessions for all messages to be deleted
|
||||
for (MessageId messageId : messageIds) {
|
||||
BdfDictionary d = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, messageId);
|
||||
MessageMetadata messageMetadata =
|
||||
messageParser.parseMetadata(d);
|
||||
if (!messageMetadata.isVisibleInConversation())
|
||||
throw new IllegalArgumentException();
|
||||
SessionId sessionId = messageMetadata.getSessionId();
|
||||
DeletableSession deletableSession =
|
||||
sessions.get(sessionId);
|
||||
if (deletableSession == null) {
|
||||
StoredSession ss = getSession(txn, sessionId);
|
||||
if (ss == null) throw new DbException();
|
||||
Role role = sessionParser.getRole(ss.bdfSession);
|
||||
Session session;
|
||||
if (role == INTRODUCER) {
|
||||
session = sessionParser
|
||||
.parseIntroducerSession(ss.bdfSession);
|
||||
} else if (role == INTRODUCEE) {
|
||||
session = sessionParser
|
||||
.parseIntroduceeSession(g, ss.bdfSession);
|
||||
} else throw new AssertionError();
|
||||
deletableSession = new DeletableSession(session.getState());
|
||||
sessions.put(sessionId, deletableSession);
|
||||
}
|
||||
deletableSession.messages.add(messageId);
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
|
||||
// delete given visible messages in sessions and auto-respond before
|
||||
for (Entry<SessionId, DeletableSession> entry : sessions.entrySet()) {
|
||||
DeletableSession session = entry.getValue();
|
||||
// decline invitee sessions waiting for a response before
|
||||
if (session.state instanceof IntroduceeState) {
|
||||
IntroduceeState introduceeState =
|
||||
(IntroduceeState) session.state;
|
||||
if (introduceeState == AWAIT_RESPONSES) {
|
||||
respondToIntroduction(txn, c, entry.getKey(), false, true);
|
||||
}
|
||||
}
|
||||
for (MessageId m : session.messages) {
|
||||
db.deleteMessage(txn, m);
|
||||
db.deleteMessageMetadata(txn, m);
|
||||
}
|
||||
}
|
||||
recalculateGroupCount(txn, g);
|
||||
|
||||
txn.attach(new ConversationMessagesDeletedEvent(c, messageIds));
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface MessageRetriever {
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.cleanup.CleanupManager;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
@@ -50,7 +51,8 @@ public class IntroductionModule {
|
||||
ValidationManager validationManager,
|
||||
ConversationManager conversationManager,
|
||||
ClientVersioningManager clientVersioningManager,
|
||||
IntroductionManagerImpl introductionManager) {
|
||||
IntroductionManagerImpl introductionManager,
|
||||
CleanupManager cleanupManager) {
|
||||
lifecycleManager.registerOpenDatabaseHook(introductionManager);
|
||||
contactManager.registerContactHook(introductionManager);
|
||||
validationManager.registerIncomingMessageHook(CLIENT_ID,
|
||||
@@ -58,6 +60,8 @@ public class IntroductionModule {
|
||||
conversationManager.registerConversationClient(introductionManager);
|
||||
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
||||
MINOR_VERSION, introductionManager);
|
||||
cleanupManager.registerCleanupHook(CLIENT_ID, MAJOR_VERSION,
|
||||
introductionManager);
|
||||
return introductionManager;
|
||||
}
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ class IntroductionValidator extends BdfMessageValidator {
|
||||
|
||||
SessionId sessionId = new SessionId(sessionIdBytes);
|
||||
BdfDictionary meta = messageEncoder.encodeMetadata(ACCEPT, sessionId,
|
||||
m.getTimestamp(), false, false, false, timer);
|
||||
m.getTimestamp(), timer);
|
||||
if (previousMessageId == null) {
|
||||
return new BdfMessageContext(meta);
|
||||
} else {
|
||||
@@ -163,7 +163,7 @@ class IntroductionValidator extends BdfMessageValidator {
|
||||
|
||||
SessionId sessionId = new SessionId(sessionIdBytes);
|
||||
BdfDictionary meta = messageEncoder.encodeMetadata(type, sessionId,
|
||||
m.getTimestamp(), false, false, false, timer);
|
||||
m.getTimestamp(), timer);
|
||||
if (previousMessageId == null) {
|
||||
return new BdfMessageContext(meta);
|
||||
} else {
|
||||
@@ -190,7 +190,7 @@ class IntroductionValidator extends BdfMessageValidator {
|
||||
|
||||
SessionId sessionId = new SessionId(sessionIdBytes);
|
||||
BdfDictionary meta = messageEncoder.encodeMetadata(AUTH, sessionId,
|
||||
m.getTimestamp(), false, false, false, NO_AUTO_DELETE_TIMER);
|
||||
m.getTimestamp(), NO_AUTO_DELETE_TIMER);
|
||||
MessageId dependency = new MessageId(previousMessageId);
|
||||
return new BdfMessageContext(meta, singletonList(dependency));
|
||||
}
|
||||
@@ -210,7 +210,7 @@ class IntroductionValidator extends BdfMessageValidator {
|
||||
|
||||
SessionId sessionId = new SessionId(sessionIdBytes);
|
||||
BdfDictionary meta = messageEncoder.encodeMetadata(ACTIVATE, sessionId,
|
||||
m.getTimestamp(), false, false, false, NO_AUTO_DELETE_TIMER);
|
||||
m.getTimestamp(), NO_AUTO_DELETE_TIMER);
|
||||
if (previousMessageId == null) {
|
||||
return new BdfMessageContext(meta);
|
||||
} else {
|
||||
@@ -231,7 +231,7 @@ class IntroductionValidator extends BdfMessageValidator {
|
||||
|
||||
SessionId sessionId = new SessionId(sessionIdBytes);
|
||||
BdfDictionary meta = messageEncoder.encodeMetadata(type, sessionId,
|
||||
m.getTimestamp(), false, false, false, NO_AUTO_DELETE_TIMER);
|
||||
m.getTimestamp(), NO_AUTO_DELETE_TIMER);
|
||||
if (previousMessageId == null) {
|
||||
return new BdfMessageContext(meta);
|
||||
} else {
|
||||
|
||||
@@ -21,9 +21,14 @@ interface MessageEncoder {
|
||||
BdfDictionary encodeRequestMetadata(long timestamp,
|
||||
long autoDeleteTimer);
|
||||
|
||||
BdfDictionary encodeMetadata(MessageType type,
|
||||
@Nullable SessionId sessionId, long timestamp,
|
||||
long autoDeleteTimer);
|
||||
|
||||
BdfDictionary encodeMetadata(MessageType type,
|
||||
@Nullable SessionId sessionId, long timestamp, boolean local,
|
||||
boolean read, boolean visible, long autoDeleteTimer);
|
||||
boolean read, boolean visible, long autoDeleteTimer,
|
||||
boolean isAutoDecline);
|
||||
|
||||
void addSessionId(BdfDictionary meta, SessionId sessionId);
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_
|
||||
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AUTO_DELETE_TIMER;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_IS_AUTO_DECLINE;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_SESSION_ID;
|
||||
@@ -53,15 +54,24 @@ class MessageEncoderImpl implements MessageEncoder {
|
||||
public BdfDictionary encodeRequestMetadata(long timestamp,
|
||||
long autoDeleteTimer) {
|
||||
BdfDictionary meta = encodeMetadata(REQUEST, null, timestamp,
|
||||
false, false, false, autoDeleteTimer);
|
||||
autoDeleteTimer);
|
||||
meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, false);
|
||||
return meta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfDictionary encodeMetadata(MessageType type,
|
||||
@Nullable SessionId sessionId, long timestamp,
|
||||
long autoDeleteTimer) {
|
||||
return encodeMetadata(type, sessionId, timestamp, false, false, false,
|
||||
autoDeleteTimer, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfDictionary encodeMetadata(MessageType type,
|
||||
@Nullable SessionId sessionId, long timestamp, boolean local,
|
||||
boolean read, boolean visible, long autoDeleteTimer) {
|
||||
boolean read, boolean visible, long autoDeleteTimer,
|
||||
boolean isAutoDecline) {
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(MSG_KEY_MESSAGE_TYPE, type.getValue());
|
||||
if (sessionId != null)
|
||||
@@ -75,6 +85,9 @@ class MessageEncoderImpl implements MessageEncoder {
|
||||
if (autoDeleteTimer != NO_AUTO_DELETE_TIMER) {
|
||||
meta.put(MSG_KEY_AUTO_DELETE_TIMER, autoDeleteTimer);
|
||||
}
|
||||
if (isAutoDecline) {
|
||||
meta.put(MSG_KEY_IS_AUTO_DECLINE, isAutoDecline);
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,11 +14,11 @@ class MessageMetadata {
|
||||
@Nullable
|
||||
private final SessionId sessionId;
|
||||
private final long timestamp, autoDeleteTimer;
|
||||
private final boolean local, read, visible, available;
|
||||
private final boolean local, read, visible, available, isAutoDecline;
|
||||
|
||||
MessageMetadata(MessageType type, @Nullable SessionId sessionId,
|
||||
long timestamp, boolean local, boolean read, boolean visible,
|
||||
boolean available, long autoDeleteTimer) {
|
||||
boolean available, long autoDeleteTimer, boolean isAutoDecline) {
|
||||
this.type = type;
|
||||
this.sessionId = sessionId;
|
||||
this.timestamp = timestamp;
|
||||
@@ -27,6 +27,7 @@ class MessageMetadata {
|
||||
this.visible = visible;
|
||||
this.available = available;
|
||||
this.autoDeleteTimer = autoDeleteTimer;
|
||||
this.isAutoDecline = isAutoDecline;
|
||||
}
|
||||
|
||||
MessageType getMessageType() {
|
||||
@@ -61,4 +62,8 @@ class MessageMetadata {
|
||||
public long getAutoDeleteTimer() {
|
||||
return autoDeleteTimer;
|
||||
}
|
||||
|
||||
public boolean isAutoDecline() {
|
||||
return isAutoDecline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_
|
||||
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AUTO_DELETE_TIMER;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_IS_AUTO_DECLINE;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
|
||||
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_SESSION_ID;
|
||||
@@ -46,7 +47,8 @@ class MessageParserImpl implements MessageParser {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfDictionary getRequestsAvailableToAnswerQuery(SessionId sessionId) {
|
||||
public BdfDictionary getRequestsAvailableToAnswerQuery(
|
||||
SessionId sessionId) {
|
||||
return BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_AVAILABLE_TO_ANSWER, true),
|
||||
new BdfEntry(MSG_KEY_MESSAGE_TYPE, REQUEST.getValue()),
|
||||
@@ -68,8 +70,9 @@ class MessageParserImpl implements MessageParser {
|
||||
boolean visible = d.getBoolean(MSG_KEY_VISIBLE_IN_UI);
|
||||
boolean available = d.getBoolean(MSG_KEY_AVAILABLE_TO_ANSWER, false);
|
||||
long timer = d.getLong(MSG_KEY_AUTO_DELETE_TIMER, NO_AUTO_DELETE_TIMER);
|
||||
boolean isAutoDecline = d.getBoolean(MSG_KEY_IS_AUTO_DECLINE, false);
|
||||
return new MessageMetadata(type, sessionId, timestamp, local, read,
|
||||
visible, available, timer);
|
||||
visible, available, timer, isAutoDecline);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,7 +15,14 @@ interface ProtocolEngine<S extends Session<?>> {
|
||||
|
||||
S onAcceptAction(Transaction txn, S session) throws DbException;
|
||||
|
||||
S onDeclineAction(Transaction txn, S session) throws DbException;
|
||||
/**
|
||||
* Declines an introduction.
|
||||
*
|
||||
* @param isAutoDecline true if automatically declined due to deletion
|
||||
* and false if initiated by the user.
|
||||
*/
|
||||
S onDeclineAction(Transaction txn, S session, boolean isAutoDecline)
|
||||
throws DbException;
|
||||
|
||||
S onRequestMessage(Transaction txn, S session, RequestMessage m)
|
||||
throws DbException, FormatException;
|
||||
|
||||
@@ -102,7 +102,7 @@ public abstract class AbstractAutoDeleteTest extends
|
||||
|
||||
@FunctionalInterface
|
||||
protected interface HeaderConsumer {
|
||||
void accept(ConversationMessageHeader header);
|
||||
void accept(ConversationMessageHeader header) throws DbException;
|
||||
}
|
||||
|
||||
protected void forEachHeader(BriarIntegrationTestComponent component,
|
||||
|
||||
@@ -0,0 +1,968 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.system.TimeTravelModule;
|
||||
import org.briarproject.bramble.test.TestDatabaseConfigModule;
|
||||
import org.briarproject.briar.api.conversation.ConversationManager.ConversationClient;
|
||||
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
||||
import org.briarproject.briar.api.introduction.IntroductionRequest;
|
||||
import org.briarproject.briar.api.introduction.IntroductionResponse;
|
||||
import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent;
|
||||
import org.briarproject.briar.autodelete.AbstractAutoDeleteTest;
|
||||
import org.briarproject.briar.test.BriarIntegrationTestComponent;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.briarproject.bramble.api.cleanup.CleanupManager.BATCH_DELAY_MS;
|
||||
import static org.briarproject.bramble.test.TestPluginConfigModule.SIMPLEX_TRANSPORT_ID;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportProperties;
|
||||
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.MIN_AUTO_DELETE_TIMER_MS;
|
||||
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionManager.MAJOR_VERSION;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSES;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSE_A;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSE_B;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.A_DECLINED;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.B_DECLINED;
|
||||
import static org.briarproject.briar.introduction.IntroducerState.START;
|
||||
import static org.briarproject.briar.test.TestEventListener.assertEvent;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class AutoDeleteIntegrationTest extends AbstractAutoDeleteTest {
|
||||
|
||||
@Override
|
||||
protected void createComponents() {
|
||||
IntroductionIntegrationTestComponent component =
|
||||
DaggerIntroductionIntegrationTestComponent.builder().build();
|
||||
BriarIntegrationTestComponent.Helper.injectEagerSingletons(component);
|
||||
component.inject(this);
|
||||
|
||||
IntroductionIntegrationTestComponent c0 =
|
||||
DaggerIntroductionIntegrationTestComponent.builder()
|
||||
.testDatabaseConfigModule(
|
||||
new TestDatabaseConfigModule(t0Dir))
|
||||
.timeTravelModule(new TimeTravelModule(true))
|
||||
.build();
|
||||
BriarIntegrationTestComponent.Helper.injectEagerSingletons(c0);
|
||||
|
||||
IntroductionIntegrationTestComponent c1 =
|
||||
DaggerIntroductionIntegrationTestComponent.builder()
|
||||
.testDatabaseConfigModule(
|
||||
new TestDatabaseConfigModule(t1Dir))
|
||||
.timeTravelModule(new TimeTravelModule(true))
|
||||
.build();
|
||||
BriarIntegrationTestComponent.Helper.injectEagerSingletons(c1);
|
||||
|
||||
IntroductionIntegrationTestComponent c2 =
|
||||
DaggerIntroductionIntegrationTestComponent.builder()
|
||||
.testDatabaseConfigModule(
|
||||
new TestDatabaseConfigModule(t2Dir))
|
||||
.timeTravelModule(new TimeTravelModule(true))
|
||||
.build();
|
||||
BriarIntegrationTestComponent.Helper.injectEagerSingletons(c2);
|
||||
|
||||
this.c0 = c0;
|
||||
this.c1 = c1;
|
||||
this.c2 = c2;
|
||||
|
||||
// Use different times to avoid creating identical messages that are
|
||||
// treated as redundant copies of the same message (#1907)
|
||||
try {
|
||||
c0.getTimeTravel().setCurrentTimeMillis(startTime);
|
||||
c1.getTimeTravel().setCurrentTimeMillis(startTime + 1);
|
||||
c2.getTimeTravel().setCurrentTimeMillis(startTime + 2);
|
||||
} catch (InterruptedException e) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
addTransportProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConversationClient getConversationClient(
|
||||
BriarIntegrationTestComponent component) {
|
||||
return component.getIntroductionManager();
|
||||
}
|
||||
|
||||
/*
|
||||
* Basic tests.
|
||||
* ASSERT timers are set on introduction messages
|
||||
* ASSERT introduction messages self-destruct on all three sides
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testIntroductionMessagesHaveTimer() throws Exception {
|
||||
makeIntroduction(true, true);
|
||||
assertIntroductionsArrived();
|
||||
|
||||
assertMessagesAmong0And1HaveTimerSet(1, 1);
|
||||
assertMessagesAmong0And2HaveTimerSet(1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntroductionAutoDeleteIntroducer() throws Exception {
|
||||
long timerLatency = MIN_AUTO_DELETE_TIMER_MS + BATCH_DELAY_MS;
|
||||
|
||||
makeIntroduction(true, true);
|
||||
assertIntroductionsArrived();
|
||||
|
||||
assertMessagesAmong0And1HaveTimerSet(1, 1);
|
||||
assertMessagesAmong0And2HaveTimerSet(1, 1);
|
||||
|
||||
// Sync the ack to 0 from 1 - this starts 0's timer
|
||||
ack1To0(1);
|
||||
waitForEvents(c0);
|
||||
|
||||
// Before 0's timer elapses, the introducer should still see the
|
||||
// introduction sent to 1
|
||||
timeTravel(c0, timerLatency - 1);
|
||||
assertGroupCountAt0With1(1, 0);
|
||||
assertGroupCountAt0With2(1, 0);
|
||||
assertGroupCountAt1With0(1, 1);
|
||||
assertGroupCountAt2With0(1, 1);
|
||||
|
||||
// After 0's timer elapses, the introducer should have deleted the
|
||||
// introduction sent to 1
|
||||
timeTravel(c0, 1);
|
||||
assertGroupCountAt0With1(0, 0);
|
||||
assertGroupCountAt0With2(1, 0);
|
||||
assertGroupCountAt1With0(1, 1);
|
||||
assertGroupCountAt2With0(1, 1);
|
||||
|
||||
// Sync the ack to 0 from 2 - this starts 0's timer
|
||||
ack2To0(1);
|
||||
waitForEvents(c0);
|
||||
|
||||
// Before 0's timer elapses, the introducer should still see the
|
||||
// introduction sent to 2
|
||||
timeTravel(c0, timerLatency - 1);
|
||||
assertGroupCountAt0With1(0, 0);
|
||||
assertGroupCountAt0With2(1, 0);
|
||||
assertGroupCountAt1With0(1, 1);
|
||||
assertGroupCountAt2With0(1, 1);
|
||||
|
||||
// After 0's timer elapses, the introducer should have deleted the
|
||||
// introduction sent to 2
|
||||
timeTravel(c0, 1);
|
||||
assertGroupCountAt0With1(0, 0);
|
||||
assertGroupCountAt0With2(0, 0);
|
||||
assertGroupCountAt1With0(1, 1);
|
||||
assertGroupCountAt2With0(1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntroductionAutoDeleteIntroducee() throws Exception {
|
||||
long timerLatency = MIN_AUTO_DELETE_TIMER_MS + BATCH_DELAY_MS;
|
||||
|
||||
makeIntroduction(true, false);
|
||||
|
||||
markMessagesRead(c1, contact0From1);
|
||||
assertGroupCountAt1With0(1, 0);
|
||||
assertGroupCountAt2With0(1, 1);
|
||||
|
||||
// Travel in time at c1 unit 1ms before the deadline expires
|
||||
timeTravel(c1, timerLatency - 1);
|
||||
assertGroupCountAt0With1(1, 0);
|
||||
assertGroupCountAt0With2(1, 0);
|
||||
assertGroupCountAt2With0(1, 1);
|
||||
// There is currently 1 message at 1 with 0, the introduction request
|
||||
assertGroupCountAt1With0(1, 0);
|
||||
forEachHeader(c1, contactId0From1, 1, h ->
|
||||
assertTrue(h instanceof IntroductionRequest)
|
||||
);
|
||||
|
||||
// After travelling in time one more 1ms, the introduction should be
|
||||
// auto-declined. We should get an event signalling that the response
|
||||
// has been sent.
|
||||
IntroductionResponseReceivedEvent e = assertEvent(c1,
|
||||
IntroductionResponseReceivedEvent.class, () ->
|
||||
timeTravel(c1, 1)
|
||||
);
|
||||
// the event should have correct values
|
||||
assertEquals(contactId0From1, e.getContactId());
|
||||
IntroductionResponse response = e.getMessageHeader();
|
||||
assertEquals(author2.getName(),
|
||||
response.getIntroducedAuthor().getName());
|
||||
assertTrue(response.isAutoDecline());
|
||||
|
||||
// these should not have changed
|
||||
assertGroupCountAt0With1(1, 0);
|
||||
assertGroupCountAt0With2(1, 0);
|
||||
assertGroupCountAt2With0(1, 1);
|
||||
// there is still 1 message at 1 with 0, but it should now be the new
|
||||
// auto-decline message instead of the introduction
|
||||
assertGroupCountAt1With0(1, 0);
|
||||
forEachHeader(c1, contactId0From1, 1, h -> {
|
||||
assertTrue(h instanceof IntroductionResponse);
|
||||
IntroductionResponse r = (IntroductionResponse) h;
|
||||
assertEquals(author2.getName(), r.getIntroducedAuthor().getName());
|
||||
assertTrue(r.isAutoDecline());
|
||||
});
|
||||
|
||||
// sync auto-decline to 0
|
||||
sync1To0(1, true);
|
||||
waitForEvents(c0);
|
||||
// auto-decline arrived at 0
|
||||
assertGroupCountAt0With1(2, 1);
|
||||
|
||||
// forward auto-decline to 2
|
||||
sync0To2(1, true);
|
||||
waitForEvents(c2);
|
||||
// auto-decline arrived at 2
|
||||
assertGroupCountAt2With0(2, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Let one introducee accept, the other decline manually
|
||||
* ASSERT accept and decline messages arrive and have timer on all sides
|
||||
* ASSERT accept and decline get forwarded to the other introducee and that
|
||||
* they all have timers set on all sides
|
||||
* ASSERT that all messages self-destruct
|
||||
*/
|
||||
@Test
|
||||
public void testIntroductionSessionManualDecline() throws Exception {
|
||||
makeIntroduction(true, true);
|
||||
|
||||
assertIntroducerStatus(AWAIT_RESPONSES);
|
||||
|
||||
// mark messages as read on 1 and 2, this starts 1's and 2's timer for 0
|
||||
markMessagesRead(c1, contact0From1);
|
||||
markMessagesRead(c2, contact0From2);
|
||||
assertGroupCountAt1With0(1, 0);
|
||||
assertGroupCountAt2With0(1, 0);
|
||||
|
||||
respondToMostRecentIntroduction(c1, contactId0From1, true);
|
||||
respondToMostRecentIntroduction(c2, contactId0From2, false);
|
||||
sync1To0(1, true);
|
||||
sync2To0(1, true);
|
||||
waitForEvents(c0);
|
||||
|
||||
// added the own responses
|
||||
assertGroupCountAt1With0(2, 0);
|
||||
assertGroupCountAt2With0(2, 0);
|
||||
|
||||
// 0 has the sent introduction and the unread responses
|
||||
assertGroupCountAt0With1(2, 1);
|
||||
assertGroupCountAt0With2(2, 1);
|
||||
|
||||
assertIntroducerStatus(START);
|
||||
|
||||
markMessagesRead(c0, contact1From0);
|
||||
markMessagesRead(c0, contact2From0);
|
||||
assertGroupCountAt0With1(2, 0);
|
||||
assertGroupCountAt0With2(2, 0);
|
||||
|
||||
// forward responses from 0 to introducees
|
||||
sync0To1(1, true);
|
||||
sync0To2(1, true);
|
||||
waitForEvents(c1);
|
||||
waitForEvents(c2);
|
||||
|
||||
// first contact receives the forwarded decline
|
||||
assertGroupCountAt1With0(3, 1);
|
||||
// second contact does not display an extra message because 2 declined
|
||||
// themselves
|
||||
assertGroupCountAt2With0(2, 0);
|
||||
|
||||
markMessagesRead(c1, contact0From1);
|
||||
assertGroupCountAt1With0(3, 0);
|
||||
|
||||
assertMessagesAmong0And1HaveTimerSet(2, 3);
|
||||
assertMessagesAmong0And2HaveTimerSet(2, 2);
|
||||
|
||||
// Travel in time on all devices
|
||||
timeTravel(c0);
|
||||
timeTravel(c1);
|
||||
timeTravel(c2);
|
||||
|
||||
assertGroupCountAt0With1(0, 0);
|
||||
assertGroupCountAt0With2(0, 0);
|
||||
assertGroupCountAt1With0(0, 0);
|
||||
assertGroupCountAt2With0(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Let introductions self-destruct at the introducer and one of the
|
||||
* introducees
|
||||
* ASSERT that auto-declines get sent and arrive
|
||||
* ASSERT that auto-declines have the timer set
|
||||
* ASSERT that declines get forwarded to other introducee
|
||||
* ASSERT that forwarded declines have the timer set
|
||||
* ASSERT that forwarded declines self-destruct
|
||||
* ASSERT that a that a new introduction can succeed afterwards
|
||||
*/
|
||||
@Test
|
||||
public void testIntroductionSessionAutoDecline() throws Exception {
|
||||
makeIntroduction(true, false);
|
||||
|
||||
assertIntroducerStatus(AWAIT_RESPONSES);
|
||||
|
||||
// ack from 1 and 2 to 0. This starts 0's timer for 1
|
||||
ack1To0(1);
|
||||
ack2To0(1);
|
||||
waitForEvents(c0);
|
||||
|
||||
// mark messages as read on 1 and 2, this starts 1's timer for 0
|
||||
markMessagesRead(c1, contact0From1);
|
||||
markMessagesRead(c2, contact0From2);
|
||||
assertGroupCountAt1With0(1, 0);
|
||||
assertGroupCountAt2With0(1, 0);
|
||||
|
||||
// Travel in time on all devices
|
||||
timeTravel(c0);
|
||||
timeTravel(c1);
|
||||
timeTravel(c2);
|
||||
|
||||
// assert that introductions have been deleted between 0 and 1 but not
|
||||
// between 0 and 2
|
||||
assertGroupCountAt0With1(0, 0);
|
||||
assertGroupCountAt0With2(1, 0);
|
||||
// there is now 1 message, the auto-decline message
|
||||
assertGroupCountAt1With0(1, 0);
|
||||
assertGroupCountAt2With0(1, 0);
|
||||
|
||||
// sync the auto-decline message from 1 to 0
|
||||
sync1To0(1, true);
|
||||
waitForEvents(c0);
|
||||
|
||||
// the auto-decline from 1 has arrived at 0
|
||||
assertGroupCountAt0With1(1, 1);
|
||||
|
||||
// the session status has moved to A_DECLINED or B_DECLINED
|
||||
assertIntroducerStatusFirstDeclined();
|
||||
|
||||
// sync the auto-decline message from 0 to 2
|
||||
sync0To2(1, true);
|
||||
waitForEvents(c2);
|
||||
// the auto-decline from 1 has arrived at 2
|
||||
assertGroupCountAt2With0(2, 1);
|
||||
|
||||
// 0 and 1 still have the auto-decline message from 0
|
||||
assertGroupCountAt0With1(1, 1);
|
||||
assertGroupCountAt1With0(1, 0);
|
||||
|
||||
// make sure the auto-decline has the timer set at 0 and 1
|
||||
assertMessagesAmong0And1HaveTimerSet(1, 1);
|
||||
|
||||
// ack message from 0 to 1 and make sure it self-destructs on 1
|
||||
ack0To1(1);
|
||||
timeTravel(c1);
|
||||
assertGroupCountAt1With0(0, 0);
|
||||
|
||||
// mark message read on 0 and make sure it self-destructs on 0
|
||||
markMessagesRead(c0, contact1From0);
|
||||
timeTravel(c0);
|
||||
assertGroupCountAt0With1(0, 0);
|
||||
|
||||
// assert that a that a new introduction can succeed afterwards:
|
||||
// first decline from c2, assert we're in START state and then
|
||||
// make the new introdution
|
||||
respondToMostRecentIntroduction(c2, contactId0From2, false);
|
||||
sync2To0(1, true);
|
||||
waitForEvents(c0);
|
||||
sync0To1(1, true);
|
||||
waitForEvents(c1);
|
||||
|
||||
assertIntroducerStatus(START);
|
||||
assertNewIntroductionSucceeds();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntroductionAcceptHasTimer() throws Exception {
|
||||
testIntroductionResponseHasTimer(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntroductionDeclineHasTimer() throws Exception {
|
||||
testIntroductionResponseHasTimer(false);
|
||||
}
|
||||
|
||||
private void testIntroductionResponseHasTimer(boolean accept)
|
||||
throws Exception {
|
||||
makeIntroduction(true, false);
|
||||
assertIntroductionsArrived();
|
||||
|
||||
// check that all messages have the timer set
|
||||
assertMessagesAmong0And1HaveTimerSet(1, 1);
|
||||
assertMessagesAmong0And2HaveTimerNotSet(1, 1);
|
||||
|
||||
respondToMostRecentIntroduction(c1, contactId0From1, accept);
|
||||
sync1To0(1, true);
|
||||
waitForEvents(c0);
|
||||
|
||||
// check that response has arrived
|
||||
assertGroupCountAt0With1(2, 1);
|
||||
assertGroupCountAt1With0(2, 1);
|
||||
assertMessagesAmong0And1HaveTimerSet(2, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntroductionAcceptSelfDestructs() throws Exception {
|
||||
testIntroductionResponseSelfDestructs(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntroductionDeclineSelfDestructs() throws Exception {
|
||||
testIntroductionResponseSelfDestructs(false);
|
||||
}
|
||||
|
||||
private void testIntroductionResponseSelfDestructs(boolean accept)
|
||||
throws Exception {
|
||||
makeIntroduction(true, false);
|
||||
assertIntroductionsArrived();
|
||||
|
||||
assertMessagesAmong0And1HaveTimerSet(1, 1);
|
||||
assertMessagesAmong0And2HaveTimerNotSet(1, 1);
|
||||
|
||||
respondToMostRecentIntroduction(c1, contactId0From1, accept);
|
||||
sync1To0(1, true);
|
||||
waitForEvents(c0);
|
||||
|
||||
// Sync the ack to 1 - this starts 1's timer
|
||||
ack0To1(1);
|
||||
waitForEvents(c1);
|
||||
|
||||
// check that response has arrived
|
||||
assertGroupCountAt0With1(2, 1);
|
||||
assertGroupCountAt1With0(2, 1);
|
||||
assertMessagesAmong0And1HaveTimerSet(2, 2);
|
||||
|
||||
// mark messages as read on 1, this starts 1's timer for 0
|
||||
markMessagesRead(c1, contact0From1);
|
||||
assertGroupCountAt1With0(2, 0);
|
||||
|
||||
// mark messages as read on 0, this starts 0's timer for 1
|
||||
markMessagesRead(c0, contact1From0);
|
||||
assertGroupCountAt0With1(2, 0);
|
||||
|
||||
// Travel in time on all devices
|
||||
timeTravel(c0);
|
||||
timeTravel(c1);
|
||||
timeTravel(c2);
|
||||
|
||||
// assert that introductions and responses have been deleted between
|
||||
// 0 and 1
|
||||
assertGroupCountAt0With1(0, 0);
|
||||
assertGroupCountAt1With0(0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tests that checks whether an introduction can still succeed properly
|
||||
* after the introduction self-destructed at the introducer.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Let introductions self-destruct at the introducer only
|
||||
* Let both introducees accept the introduction
|
||||
* ASSERT that accept messages still get forwarded to the other introducer
|
||||
* ASSERT that the introduction succeeds
|
||||
* ASSERT all messages involved self-destruct eventually
|
||||
*/
|
||||
@Test
|
||||
public void testSucceedAfterIntroducerSelfDestructed() throws Exception {
|
||||
testSucceedAfterIntroducerSelfDestructed(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of the above test that also auto-deletes the responses at each
|
||||
* introducee received from the respective other introducee.
|
||||
*/
|
||||
@Test
|
||||
public void testSucceedAfterIntroducerAndResponsesSelfDestructed()
|
||||
throws Exception {
|
||||
testSucceedAfterIntroducerSelfDestructed(true);
|
||||
}
|
||||
|
||||
private void testSucceedAfterIntroducerSelfDestructed(
|
||||
boolean autoDeleteResponsesBeforeSyncingAuthAndActivate)
|
||||
throws Exception {
|
||||
makeIntroduction(true, true);
|
||||
|
||||
// ack from 1 and 2 to 0. This starts 0's timer for 1 and 2
|
||||
ack1To0(1);
|
||||
ack2To0(1);
|
||||
waitForEvents(c0);
|
||||
|
||||
// Travel in time at 0
|
||||
timeTravel(c0);
|
||||
|
||||
assertGroupCountAt0With1(0, 0);
|
||||
assertGroupCountAt0With2(0, 0);
|
||||
|
||||
// -> introductions self-destructed at the introducer only
|
||||
|
||||
// introducees have got the introduction
|
||||
assertGroupCountAt1With0(1, 1);
|
||||
assertGroupCountAt2With0(1, 1);
|
||||
|
||||
respondToMostRecentIntroduction(c1, contactId0From1, true);
|
||||
respondToMostRecentIntroduction(c2, contactId0From2, true);
|
||||
sync1To0(1, true);
|
||||
sync2To0(1, true);
|
||||
waitForEvents(c0);
|
||||
|
||||
// introducees have got the own response
|
||||
assertGroupCountAt1With0(2, 1);
|
||||
assertGroupCountAt2With0(2, 1);
|
||||
|
||||
// sync forwarded ACCEPT messages to introducees
|
||||
sync0To1(1, true);
|
||||
sync0To2(1, true);
|
||||
waitForEvents(c1);
|
||||
waitForEvents(c2);
|
||||
|
||||
if (autoDeleteResponsesBeforeSyncingAuthAndActivate) {
|
||||
assertGroupCountAt1With0(2, 1);
|
||||
assertGroupCountAt2With0(2, 1);
|
||||
|
||||
markMessagesRead(c1, contact0From1);
|
||||
markMessagesRead(c2, contact0From2);
|
||||
|
||||
assertGroupCountAt1With0(2, 0);
|
||||
assertGroupCountAt2With0(2, 0);
|
||||
|
||||
// Travel in time at 1 and 2
|
||||
timeTravel(c1);
|
||||
timeTravel(c2);
|
||||
|
||||
assertGroupCountAt1With0(0, 0);
|
||||
assertGroupCountAt2With0(0, 0);
|
||||
}
|
||||
|
||||
syncAuthAndActivateMessages();
|
||||
|
||||
assertIntroductionSucceeded();
|
||||
}
|
||||
|
||||
/*
|
||||
* Group of three tests that check whether an introduction can still fail
|
||||
* properly after the introduction self-destructed at the introducer.
|
||||
* <p>
|
||||
* Let introductions self-destruct at the introducer only
|
||||
* Variant 1: Let both introducees decline the introduction
|
||||
* Variant 2: Let first introducee accept, second decline the introduction
|
||||
* Variant 3: Let first introducee decline, second accept the introduction
|
||||
* ASSERT that accept/decline messages still get forwarded to the other introducer
|
||||
* ASSERT that the introduction does not succeed
|
||||
* ASSERT that abort messages do get sent
|
||||
* ASSERT all messages involved self-destruct eventually
|
||||
* ASSERT that a new introduction can succeed afterwards
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testFailAfterIntroducerSelfDestructedBothDecline()
|
||||
throws Exception {
|
||||
testFailAfterIntroducerSelfDestructed(false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailAfterIntroducerSelfDestructedFirstAccept()
|
||||
throws Exception {
|
||||
testFailAfterIntroducerSelfDestructed(true, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailAfterIntroducerSelfDestructedSecondAccept()
|
||||
throws Exception {
|
||||
testFailAfterIntroducerSelfDestructed(false, true);
|
||||
}
|
||||
|
||||
private void testFailAfterIntroducerSelfDestructed(boolean firstAccepts,
|
||||
boolean secondAccepts) throws Exception {
|
||||
assertFalse(firstAccepts && secondAccepts);
|
||||
|
||||
makeIntroduction(true, true);
|
||||
|
||||
// ack from 1 and 2 to 0. This starts 0's timer for 1 and 2
|
||||
ack1To0(1);
|
||||
ack2To0(1);
|
||||
waitForEvents(c0);
|
||||
|
||||
// Travel in time at 0
|
||||
timeTravel(c0);
|
||||
|
||||
assertGroupCountAt0With1(0, 0);
|
||||
assertGroupCountAt0With2(0, 0);
|
||||
|
||||
// -> introductions self-destructed at the introducer only
|
||||
|
||||
assertIntroducerStatus(AWAIT_RESPONSES);
|
||||
assertIntroduceeStatus(c1, IntroduceeState.AWAIT_RESPONSES);
|
||||
assertIntroduceeStatus(c2, IntroduceeState.AWAIT_RESPONSES);
|
||||
|
||||
// introducees have got the introduction
|
||||
assertGroupCountAt1With0(1, 1);
|
||||
assertGroupCountAt2With0(1, 1);
|
||||
|
||||
// first contact reads the introduction and responds
|
||||
markMessagesRead(c1, contact0From1);
|
||||
respondToMostRecentIntroduction(c1, contactId0From1, firstAccepts);
|
||||
sync1To0(1, true);
|
||||
waitForEvents(c0);
|
||||
|
||||
if (firstAccepts) {
|
||||
assertIntroducerStatusFirstAccepted();
|
||||
} else {
|
||||
assertIntroducerStatusFirstDeclined();
|
||||
}
|
||||
|
||||
// second contact reads the introduction and responds
|
||||
markMessagesRead(c2, contact0From2);
|
||||
respondToMostRecentIntroduction(c2, contactId0From2, secondAccepts);
|
||||
sync2To0(1, true);
|
||||
waitForEvents(c0);
|
||||
|
||||
assertIntroducerStatus(START);
|
||||
|
||||
// introducees have got the own response
|
||||
assertGroupCountAt1With0(2, 0);
|
||||
assertGroupCountAt2With0(2, 0);
|
||||
|
||||
// sync forwarded ACCEPT/DECLINE messages to introducees
|
||||
sync0To1(1, true);
|
||||
waitForEvents(c1);
|
||||
sync0To2(1, true);
|
||||
waitForEvents(c2);
|
||||
|
||||
assertIntroductionFailed();
|
||||
|
||||
if (firstAccepts) {
|
||||
// one additional message, the other introducee's response
|
||||
assertGroupCountAt1With0(3, 1);
|
||||
} else {
|
||||
assertGroupCountAt1With0(2, 0);
|
||||
}
|
||||
if (secondAccepts) {
|
||||
// one additional message, the other introducee's response
|
||||
assertGroupCountAt2With0(3, 1);
|
||||
} else {
|
||||
assertGroupCountAt2With0(2, 0);
|
||||
}
|
||||
|
||||
timeTravel(c1);
|
||||
timeTravel(c2);
|
||||
|
||||
if (firstAccepts) {
|
||||
assertGroupCountAt1With0(1, 1);
|
||||
} else {
|
||||
assertGroupCountAt1With0(0, 0);
|
||||
}
|
||||
if (secondAccepts) {
|
||||
assertGroupCountAt2With0(1, 1);
|
||||
} else {
|
||||
assertGroupCountAt2With0(0, 0);
|
||||
}
|
||||
|
||||
// -> if one of the introducees accepted, they still have got an unread
|
||||
// decline from the other introducee
|
||||
|
||||
if (firstAccepts) {
|
||||
markMessagesRead(c1, contact0From1);
|
||||
timeTravel(c1);
|
||||
assertGroupCountAt1With0(0, 0);
|
||||
}
|
||||
if (secondAccepts) {
|
||||
markMessagesRead(c2, contact0From2);
|
||||
timeTravel(c2);
|
||||
assertGroupCountAt2With0(0, 0);
|
||||
}
|
||||
|
||||
// make sure the introducees session status returned to START
|
||||
assertIntroduceeStatus(c1, IntroduceeState.START);
|
||||
assertIntroduceeStatus(c2, IntroduceeState.START);
|
||||
|
||||
assertNewIntroductionSucceeds();
|
||||
}
|
||||
|
||||
private void makeIntroduction(boolean enableTimer1, boolean enableTimer2)
|
||||
throws Exception {
|
||||
if (enableTimer1) {
|
||||
setAutoDeleteTimer(c0, contact1From0.getId(),
|
||||
MIN_AUTO_DELETE_TIMER_MS);
|
||||
}
|
||||
if (enableTimer2) {
|
||||
setAutoDeleteTimer(c0, contact2From0.getId(),
|
||||
MIN_AUTO_DELETE_TIMER_MS);
|
||||
}
|
||||
|
||||
// make introduction
|
||||
c0.getIntroductionManager()
|
||||
.makeIntroduction(contact1From0, contact2From0, "Hi!");
|
||||
|
||||
sync0To1(1, true);
|
||||
sync0To2(1, true);
|
||||
waitForEvents(c1);
|
||||
waitForEvents(c2);
|
||||
}
|
||||
|
||||
private void respondToMostRecentIntroduction(
|
||||
BriarIntegrationTestComponent c, ContactId contactId,
|
||||
boolean accept) throws Exception {
|
||||
List<ConversationMessageHeader> headers =
|
||||
getMessageHeaders(c, contactId);
|
||||
Collections.reverse(headers);
|
||||
for (ConversationMessageHeader h : headers) {
|
||||
if (h instanceof IntroductionRequest) {
|
||||
IntroductionRequest ir = (IntroductionRequest) h;
|
||||
c.getIntroductionManager().respondToIntroduction(contactId,
|
||||
ir.getSessionId(), accept);
|
||||
return;
|
||||
}
|
||||
}
|
||||
fail("no introduction found");
|
||||
}
|
||||
|
||||
private void markMessagesRead(BriarIntegrationTestComponent c,
|
||||
Contact contact) throws Exception {
|
||||
for (ConversationMessageHeader h : getMessageHeaders(c,
|
||||
contact.getId())) {
|
||||
markMessageRead(c, contact, h.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private void syncAuthAndActivateMessages() throws Exception {
|
||||
// sync first AUTH and its forward
|
||||
sync1To0(1, true);
|
||||
sync0To2(1, true);
|
||||
|
||||
// sync second AUTH and its forward as well as the following ACTIVATE
|
||||
sync2To0(2, true);
|
||||
sync0To1(2, true);
|
||||
|
||||
// sync second ACTIVATE and its forward
|
||||
sync1To0(1, true);
|
||||
sync0To2(1, true);
|
||||
}
|
||||
|
||||
private void timeTravel(BriarIntegrationTestComponent c) throws Exception {
|
||||
long timerLatency = MIN_AUTO_DELETE_TIMER_MS + BATCH_DELAY_MS;
|
||||
timeTravel(c, timerLatency);
|
||||
}
|
||||
|
||||
private void timeTravel(BriarIntegrationTestComponent c, long timerLatency)
|
||||
throws Exception {
|
||||
c.getTimeTravel().addCurrentTimeMillis(timerLatency);
|
||||
waitForEvents(c);
|
||||
}
|
||||
|
||||
private void assertIntroductionsArrived() throws DbException {
|
||||
// check that introductions have arrived at the introducees
|
||||
assertGroupCount(c0, contactId1From0, 1, 0);
|
||||
assertGroupCount(c0, contactId2From0, 1, 0);
|
||||
assertGroupCount(c1, contactId0From1, 1, 1);
|
||||
assertGroupCount(c2, contactId0From2, 1, 1);
|
||||
}
|
||||
|
||||
private void assertGroupCountAt0With1(int messageCount, int unreadCount)
|
||||
throws Exception {
|
||||
assertGroupCount(c0, contactId1From0, messageCount, unreadCount);
|
||||
assertEquals(messageCount,
|
||||
getMessageHeaders(c0, contactId1From0).size());
|
||||
}
|
||||
|
||||
private void assertGroupCountAt0With2(int messageCount, int unreadCount)
|
||||
throws Exception {
|
||||
assertGroupCount(c0, contactId2From0, messageCount, unreadCount);
|
||||
assertEquals(messageCount,
|
||||
getMessageHeaders(c0, contactId2From0).size());
|
||||
}
|
||||
|
||||
private void assertGroupCountAt1With0(int messageCount, int unreadCount)
|
||||
throws Exception {
|
||||
assertGroupCount(c1, contactId0From1, messageCount, unreadCount);
|
||||
assertEquals(messageCount,
|
||||
getMessageHeaders(c1, contactId0From1).size());
|
||||
}
|
||||
|
||||
private void assertGroupCountAt2With0(int messageCount, int unreadCount)
|
||||
throws Exception {
|
||||
assertGroupCount(c2, contactId0From2, messageCount, unreadCount);
|
||||
assertEquals(messageCount,
|
||||
getMessageHeaders(c2, contactId0From2).size());
|
||||
}
|
||||
|
||||
private void assertMessagesAmong0And1HaveTimerSet(int numC0, int numC1)
|
||||
throws Exception {
|
||||
forEachHeader(c0, contactId1From0, numC0, h ->
|
||||
assertEquals(MIN_AUTO_DELETE_TIMER_MS, h.getAutoDeleteTimer()));
|
||||
forEachHeader(c1, contactId0From1, numC1, h ->
|
||||
assertEquals(MIN_AUTO_DELETE_TIMER_MS, h.getAutoDeleteTimer()));
|
||||
}
|
||||
|
||||
private void assertMessagesAmong0And2HaveTimerSet(int numC0, int numC2)
|
||||
throws Exception {
|
||||
forEachHeader(c0, contactId2From0, numC0, h ->
|
||||
assertEquals(MIN_AUTO_DELETE_TIMER_MS, h.getAutoDeleteTimer()));
|
||||
forEachHeader(c2, contactId0From2, numC2, h ->
|
||||
assertEquals(MIN_AUTO_DELETE_TIMER_MS, h.getAutoDeleteTimer()));
|
||||
}
|
||||
|
||||
private void assertMessagesAmong0And2HaveTimerNotSet(int numC0, int numC2)
|
||||
throws Exception {
|
||||
forEachHeader(c0, contactId2From0, numC0, h ->
|
||||
assertEquals(NO_AUTO_DELETE_TIMER, h.getAutoDeleteTimer()));
|
||||
forEachHeader(c2, contactId0From2, numC2, h ->
|
||||
assertEquals(NO_AUTO_DELETE_TIMER, h.getAutoDeleteTimer()));
|
||||
}
|
||||
|
||||
private void assertIntroducerStatus(IntroducerState state)
|
||||
throws DbException, FormatException {
|
||||
IntroducerSession introducerSession = getIntroducerSession();
|
||||
assertEquals(state, introducerSession.getState());
|
||||
}
|
||||
|
||||
private void assertIntroducerStatusFirstDeclined()
|
||||
throws DbException, FormatException {
|
||||
IntroductionCrypto introductionCrypto =
|
||||
((IntroductionIntegrationTestComponent) c0)
|
||||
.getIntroductionCrypto();
|
||||
boolean alice =
|
||||
introductionCrypto.isAlice(contact1From0.getAuthor().getId(),
|
||||
contact2From0.getAuthor().getId());
|
||||
IntroducerSession introducerSession = getIntroducerSession();
|
||||
assertEquals(alice ? A_DECLINED : B_DECLINED,
|
||||
introducerSession.getState());
|
||||
}
|
||||
|
||||
private void assertIntroducerStatusFirstAccepted()
|
||||
throws DbException, FormatException {
|
||||
IntroductionCrypto introductionCrypto =
|
||||
((IntroductionIntegrationTestComponent) c0)
|
||||
.getIntroductionCrypto();
|
||||
boolean alice =
|
||||
introductionCrypto.isAlice(contact1From0.getAuthor().getId(),
|
||||
contact2From0.getAuthor().getId());
|
||||
IntroducerSession introducerSession = getIntroducerSession();
|
||||
assertEquals(alice ? AWAIT_RESPONSE_B : AWAIT_RESPONSE_A,
|
||||
introducerSession.getState());
|
||||
}
|
||||
|
||||
private void assertIntroduceeStatus(BriarIntegrationTestComponent c,
|
||||
IntroduceeState state)
|
||||
throws DbException, FormatException {
|
||||
IntroduceeSession introduceeSession = getIntroduceeSession(c);
|
||||
assertEquals(state, introduceeSession.getState());
|
||||
}
|
||||
|
||||
private void assertIntroductionSucceeded() throws DbException {
|
||||
// make sure that introduced contacts have each other in their contact
|
||||
// manager
|
||||
assertTrue(contactManager1
|
||||
.contactExists(author2.getId(), author1.getId()));
|
||||
assertTrue(contactManager2
|
||||
.contactExists(author1.getId(), author2.getId()));
|
||||
|
||||
// make sure that introduced contacts are not verified
|
||||
for (Contact c : contactManager1.getContacts()) {
|
||||
if (c.getAuthor().equals(author2)) {
|
||||
assertFalse(c.isVerified());
|
||||
}
|
||||
}
|
||||
for (Contact c : contactManager2.getContacts()) {
|
||||
if (c.getAuthor().equals(author1)) {
|
||||
assertFalse(c.isVerified());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertIntroductionFailed() throws DbException {
|
||||
// make sure that introduced contacts do not have each other in their
|
||||
// contact manager
|
||||
assertFalse(contactManager1
|
||||
.contactExists(author2.getId(), author1.getId()));
|
||||
assertFalse(contactManager2
|
||||
.contactExists(author1.getId(), author2.getId()));
|
||||
}
|
||||
|
||||
private void assertNewIntroductionSucceeds() throws Exception {
|
||||
makeIntroduction(false, false);
|
||||
|
||||
respondToMostRecentIntroduction(c1, contactId0From1, true);
|
||||
respondToMostRecentIntroduction(c2, contactId0From2, true);
|
||||
sync1To0(1, true);
|
||||
sync2To0(1, true);
|
||||
waitForEvents(c0);
|
||||
|
||||
// forward responses from 0 to introducees
|
||||
sync0To1(1, true);
|
||||
sync0To2(1, true);
|
||||
waitForEvents(c1);
|
||||
waitForEvents(c2);
|
||||
|
||||
syncAuthAndActivateMessages();
|
||||
|
||||
assertIntroductionSucceeded();
|
||||
}
|
||||
|
||||
private void addTransportProperties() throws Exception {
|
||||
TransportPropertyManager tpm0 = c0.getTransportPropertyManager();
|
||||
TransportPropertyManager tpm1 = c1.getTransportPropertyManager();
|
||||
TransportPropertyManager tpm2 = c2.getTransportPropertyManager();
|
||||
|
||||
tpm0.mergeLocalProperties(SIMPLEX_TRANSPORT_ID,
|
||||
getTransportProperties(2));
|
||||
sync0To1(1, true);
|
||||
sync0To2(1, true);
|
||||
|
||||
tpm1.mergeLocalProperties(SIMPLEX_TRANSPORT_ID,
|
||||
getTransportProperties(2));
|
||||
sync1To0(1, true);
|
||||
|
||||
tpm2.mergeLocalProperties(SIMPLEX_TRANSPORT_ID,
|
||||
getTransportProperties(2));
|
||||
sync2To0(1, true);
|
||||
}
|
||||
|
||||
private IntroducerSession getIntroducerSession()
|
||||
throws DbException, FormatException {
|
||||
Map<MessageId, BdfDictionary> dicts = c0.getClientHelper()
|
||||
.getMessageMetadataAsDictionary(getLocalGroup().getId());
|
||||
assertEquals(1, dicts.size());
|
||||
BdfDictionary d = dicts.values().iterator().next();
|
||||
SessionParser sessionParser =
|
||||
((IntroductionIntegrationTestComponent) c0).getSessionParser();
|
||||
return sessionParser.parseIntroducerSession(d);
|
||||
}
|
||||
|
||||
private IntroduceeSession getIntroduceeSession(
|
||||
BriarIntegrationTestComponent c)
|
||||
throws DbException, FormatException {
|
||||
Map<MessageId, BdfDictionary> dicts = c.getClientHelper()
|
||||
.getMessageMetadataAsDictionary(getLocalGroup().getId());
|
||||
assertEquals(1, dicts.size());
|
||||
BdfDictionary d = dicts.values().iterator().next();
|
||||
Group introducerGroup =
|
||||
c2.getIntroductionManager().getContactGroup(contact0From2);
|
||||
SessionParser sessionParser =
|
||||
((IntroductionIntegrationTestComponent) c).getSessionParser();
|
||||
return sessionParser
|
||||
.parseIntroduceeSession(introducerGroup.getId(), d);
|
||||
}
|
||||
|
||||
private Group getLocalGroup() {
|
||||
return contactGroupFactory
|
||||
.createLocalGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -381,9 +381,6 @@ public class IntroductionIntegrationTest
|
||||
listener2.getResponse().getIntroducedAuthor().getName());
|
||||
assertFalse(listener2.getResponse().canSucceed());
|
||||
|
||||
// note how the introducer does not forward the second response,
|
||||
// because after the first decline the protocol finished
|
||||
|
||||
assertFalse(listener1.succeeded);
|
||||
assertFalse(listener2.succeeded);
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@ interface IntroductionIntegrationTestComponent
|
||||
|
||||
void inject(IntroductionCryptoIntegrationTest init);
|
||||
|
||||
void inject(AutoDeleteIntegrationTest init);
|
||||
|
||||
MessageEncoder getMessageEncoder();
|
||||
|
||||
MessageParser getMessageParser();
|
||||
|
||||
@@ -640,8 +640,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase {
|
||||
private void expectEncodeMetadata(MessageType type, long autoDeleteTimer) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageEncoder).encodeMetadata(type, sessionId,
|
||||
message.getTimestamp(), false, false, false,
|
||||
autoDeleteTimer);
|
||||
message.getTimestamp(), autoDeleteTimer);
|
||||
will(returnValue(meta));
|
||||
}});
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase {
|
||||
@Test
|
||||
public void testMessageMetadata() throws FormatException {
|
||||
BdfDictionary d = messageEncoder.encodeMetadata(ABORT, sessionId,
|
||||
timestamp, false, true, false, MAX_AUTO_DELETE_TIMER_MS);
|
||||
timestamp, false, true, false, MAX_AUTO_DELETE_TIMER_MS, false);
|
||||
MessageMetadata meta = messageParser.parseMetadata(d);
|
||||
|
||||
assertEquals(ABORT, meta.getMessageType());
|
||||
|
||||
Reference in New Issue
Block a user