Implement auto-declining for self-destructed introductions

This commit is contained in:
Sebastian Kürten
2021-03-03 09:17:35 +01:00
committed by Torsten Grote
parent 49850e4198
commit 0bf10a827f
23 changed files with 1170 additions and 42 deletions

View File

@@ -7,6 +7,7 @@
<w>encrypter</w>
<w>identicon</w>
<w>introducee</w>
<w>introducees</w>
<w>introducer</w>
<w>onboarding</w>
</words>

View File

@@ -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,

View File

@@ -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>

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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";

View File

@@ -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.
*/

View File

@@ -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 {
/**

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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,

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -49,6 +49,8 @@ interface IntroductionIntegrationTestComponent
void inject(IntroductionCryptoIntegrationTest init);
void inject(AutoDeleteIntegrationTest init);
MessageEncoder getMessageEncoder();
MessageParser getMessageParser();

View File

@@ -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));
}});
}

View File

@@ -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());