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>encrypter</w>
<w>identicon</w> <w>identicon</w>
<w>introducee</w> <w>introducee</w>
<w>introducees</w>
<w>introducer</w> <w>introducer</w>
<w>onboarding</w> <w>onboarding</w>
</words> </words>

View File

@@ -289,6 +289,10 @@ class ConversationVisitor implements
text = ctx.getString( text = ctx.getString(
R.string.introduction_response_accepted_sent, R.string.introduction_response_accepted_sent,
introducedAuthor) + suffix; introducedAuthor) + suffix;
} else if (r.isAutoDecline()) {
text = ctx.getString(
R.string.introduction_response_declined_auto,
introducedAuthor);
} else { } else {
text = ctx.getString( text = ctx.getString(
R.string.introduction_response_declined_sent, 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">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_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_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_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">%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> <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, boolean local, boolean read, boolean sent, boolean seen,
SessionId sessionId, boolean accepted, Author author, SessionId sessionId, boolean accepted, Author author,
AuthorInfo introducedAuthorInfo, Role role, boolean canSucceed, AuthorInfo introducedAuthorInfo, Role role, boolean canSucceed,
long autoDeleteTimer) { long autoDeleteTimer, boolean isAutoDecline) {
super(messageId, groupId, time, local, read, sent, seen, sessionId, super(messageId, groupId, time, local, read, sent, seen, sessionId,
accepted, autoDeleteTimer, false); accepted, autoDeleteTimer, isAutoDecline);
this.introducedAuthor = author; this.introducedAuthor = author;
this.introducedAuthorInfo = introducedAuthorInfo; this.introducedAuthorInfo = introducedAuthorInfo;
this.ourRole = role; 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.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; 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.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.IdentityManager; 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.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID; 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.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.ABORT;
import static org.briarproject.briar.introduction.MessageType.ACCEPT; import static org.briarproject.briar.introduction.MessageType.ACCEPT;
import static org.briarproject.briar.introduction.MessageType.ACTIVATE; import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
@@ -105,6 +107,10 @@ abstract class AbstractProtocolEngine<S extends Session<?>>
m = messageEncoder.encodeRequestMessage(s.getContactGroupId(), m = messageEncoder.encodeRequestMessage(s.getContactGroupId(),
timestamp, s.getLastLocalMessageId(), author, text, timer); timestamp, s.getLastLocalMessageId(), author, text, timer);
sendMessage(txn, REQUEST, s.getSessionId(), m, true, 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 { } else {
m = messageEncoder.encodeRequestMessage(s.getContactGroupId(), m = messageEncoder.encodeRequestMessage(s.getContactGroupId(),
timestamp, s.getLastLocalMessageId(), author, text); timestamp, s.getLastLocalMessageId(), author, text);
@@ -128,6 +134,10 @@ abstract class AbstractProtocolEngine<S extends Session<?>>
ephemeralPublicKey, acceptTimestamp, transportProperties, ephemeralPublicKey, acceptTimestamp, transportProperties,
timer); timer);
sendMessage(txn, ACCEPT, s.getSessionId(), m, visible, 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 { } else {
m = messageEncoder.encodeAcceptMessage(s.getContactGroupId(), m = messageEncoder.encodeAcceptMessage(s.getContactGroupId(),
timestamp, s.getLastLocalMessageId(), s.getSessionId(), timestamp, s.getLastLocalMessageId(), s.getSessionId(),
@@ -139,7 +149,8 @@ abstract class AbstractProtocolEngine<S extends Session<?>>
} }
Message sendDeclineMessage(Transaction txn, PeerSession s, long timestamp, 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; Message m;
ContactId c = getContactId(txn, s.getContactGroupId()); ContactId c = getContactId(txn, s.getContactGroupId());
if (contactSupportsAutoDeletion(txn, c)) { if (contactSupportsAutoDeletion(txn, c)) {
@@ -148,7 +159,25 @@ abstract class AbstractProtocolEngine<S extends Session<?>>
m = messageEncoder.encodeDeclineMessage(s.getContactGroupId(), m = messageEncoder.encodeDeclineMessage(s.getContactGroupId(),
timestamp, s.getLastLocalMessageId(), s.getSessionId(), timestamp, s.getLastLocalMessageId(), s.getSessionId(),
timer); 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 { } else {
m = messageEncoder.encodeDeclineMessage(s.getContactGroupId(), m = messageEncoder.encodeDeclineMessage(s.getContactGroupId(),
timestamp, s.getLastLocalMessageId(), s.getSessionId()); timestamp, s.getLastLocalMessageId(), s.getSessionId());
@@ -192,9 +221,16 @@ abstract class AbstractProtocolEngine<S extends Session<?>>
private void sendMessage(Transaction txn, MessageType type, private void sendMessage(Transaction txn, MessageType type,
SessionId sessionId, Message m, boolean visibleInConversation, SessionId sessionId, Message m, boolean visibleInConversation,
long autoDeleteTimer) throws DbException { 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, BdfDictionary meta = messageEncoder.encodeMetadata(type, sessionId,
m.getTimestamp(), true, true, visibleInConversation, m.getTimestamp(), true, true, visibleInConversation,
autoDeleteTimer); autoDeleteTimer, isAutoDecline);
try { try {
clientHelper.addLocalMessage(txn, m, meta, true, false); clientHelper.addLocalMessage(txn, m, meta, true, false);
} catch (FormatException e) { } catch (FormatException e) {
@@ -215,7 +251,7 @@ abstract class AbstractProtocolEngine<S extends Session<?>>
m.getTimestamp(), false, false, false, false, m.getTimestamp(), false, false, false, false,
s.getSessionId(), m instanceof AcceptMessage, s.getSessionId(), m instanceof AcceptMessage,
otherAuthor, otherAuthorInfo, s.getRole(), canSucceed, otherAuthor, otherAuthorInfo, s.getRole(), canSucceed,
m.getAutoDeleteTimer()); m.getAutoDeleteTimer(), false);
IntroductionResponseReceivedEvent e = IntroductionResponseReceivedEvent e =
new IntroductionResponseReceivedEvent(response, c.getId()); new IntroductionResponseReceivedEvent(response, c.getId());
txn.attach(e); txn.attach(e);

View File

@@ -123,12 +123,13 @@ class IntroduceeProtocolEngine
@Override @Override
public IntroduceeSession onDeclineAction(Transaction txn, public IntroduceeSession onDeclineAction(Transaction txn,
IntroduceeSession session) throws DbException { IntroduceeSession session, boolean isAutoDecline)
throws DbException {
switch (session.getState()) { switch (session.getState()) {
case AWAIT_RESPONSES: case AWAIT_RESPONSES:
case REMOTE_DECLINED: case REMOTE_DECLINED:
case REMOTE_ACCEPTED: case REMOTE_ACCEPTED:
return onLocalDecline(txn, session); return onLocalDecline(txn, session, isAutoDecline);
case START: case START:
case LOCAL_DECLINED: case LOCAL_DECLINED:
case LOCAL_ACCEPTED: case LOCAL_ACCEPTED:
@@ -319,13 +320,14 @@ class IntroduceeProtocolEngine
} }
private IntroduceeSession onLocalDecline(Transaction txn, private IntroduceeSession onLocalDecline(Transaction txn,
IntroduceeSession s) throws DbException { IntroduceeSession s, boolean isAutoDecline) throws DbException {
// Mark the request message unavailable to answer // Mark the request message unavailable to answer
markRequestsUnavailableToAnswer(txn, s); markRequestsUnavailableToAnswer(txn, s);
// Send a DECLINE message // Send a DECLINE message
long localTimestamp = getTimestampForVisibleMessage(txn, s); long localTimestamp = getTimestampForVisibleMessage(txn, s);
Message sent = sendDeclineMessage(txn, s, localTimestamp, true); Message sent =
sendDeclineMessage(txn, s, localTimestamp, true, isAutoDecline);
// Track the message // Track the message
messageTracker.trackOutgoingMessage(txn, sent); messageTracker.trackOutgoingMessage(txn, sent);

View File

@@ -98,7 +98,7 @@ class IntroducerProtocolEngine
@Override @Override
public IntroducerSession onDeclineAction(Transaction txn, public IntroducerSession onDeclineAction(Transaction txn,
IntroducerSession s) { IntroducerSession s, boolean isAutoDecline) {
throw new UnsupportedOperationException(); // Invalid in this role throw new UnsupportedOperationException(); // Invalid in this role
} }
@@ -387,7 +387,7 @@ class IntroducerProtocolEngine
Introducee i = getOtherIntroducee(s, m.getGroupId()); Introducee i = getOtherIntroducee(s, m.getGroupId());
// The forwarded message will be visible to the introducee // The forwarded message will be visible to the introducee
long localTimestamp = getTimestampForVisibleMessage(txn, s, i); 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 // Create the next state
IntroducerState state = START; IntroducerState state = START;
@@ -442,7 +442,7 @@ class IntroducerProtocolEngine
Introducee i = getOtherIntroducee(s, m.getGroupId()); Introducee i = getOtherIntroducee(s, m.getGroupId());
// The forwarded message will be visible to the introducee // The forwarded message will be visible to the introducee
long localTimestamp = getTimestampForVisibleMessage(txn, s, i); long localTimestamp = getTimestampForVisibleMessage(txn, s, i);
Message sent = sendDeclineMessage(txn, i, localTimestamp, false); Message sent = sendDeclineMessage(txn, i, localTimestamp, false, false);
Introducee introduceeA, introduceeB; Introducee introduceeA, introduceeB;
Author sender, other; Author sender, other;

View File

@@ -10,6 +10,7 @@ interface IntroductionConstants {
String MSG_KEY_VISIBLE_IN_UI = "visibleInUi"; String MSG_KEY_VISIBLE_IN_UI = "visibleInUi";
String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer"; String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer";
String MSG_KEY_AUTO_DELETE_TIMER = "autoDeleteTimer"; String MSG_KEY_AUTO_DELETE_TIMER = "autoDeleteTimer";
String MSG_KEY_IS_AUTO_DECLINE = "isAutoDecline";
// Session Keys // Session Keys
String SESSION_KEY_SESSION_ID = "sessionId"; String SESSION_KEY_SESSION_ID = "sessionId";

View File

@@ -20,7 +20,7 @@ interface IntroductionCrypto {
/** /**
* Returns true if the local author is alice * Returns true if the local author is alice
* * <p>
* Alice is the Author whose unique ID has the lower ID, * Alice is the Author whose unique ID has the lower ID,
* comparing the IDs as byte strings. * comparing the IDs as byte strings.
*/ */

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.introduction; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; 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.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory; import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact; 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.sync.MessageStatus;
import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; 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.MessageTracker;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationMessageHeader;
@@ -54,8 +56,10 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; 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.INTRODUCEE;
import static org.briarproject.briar.api.introduction.Role.INTRODUCER; 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.IntroduceeState.REMOTE_DECLINED;
import static org.briarproject.briar.introduction.IntroducerState.A_DECLINED; 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.B_DECLINED;
@@ -71,7 +75,7 @@ import static org.briarproject.briar.introduction.MessageType.REQUEST;
@NotNullByDefault @NotNullByDefault
class IntroductionManagerImpl extends ConversationClientImpl class IntroductionManagerImpl extends ConversationClientImpl
implements IntroductionManager, OpenDatabaseHook, ContactHook, implements IntroductionManager, OpenDatabaseHook, ContactHook,
ClientVersioningHook { ClientVersioningHook, CleanupHook {
private final ClientVersioningManager clientVersioningManager; private final ClientVersioningManager clientVersioningManager;
private final ContactGroupFactory contactGroupFactory; private final ContactGroupFactory contactGroupFactory;
@@ -170,6 +174,11 @@ class IntroductionManagerImpl extends ConversationClientImpl
BdfDictionary bdfMeta) throws DbException, FormatException { BdfDictionary bdfMeta) throws DbException, FormatException {
// Parse the metadata // Parse the metadata
MessageMetadata meta = messageParser.parseMetadata(bdfMeta); 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 // Look up the session, if there is one
SessionId sessionId = meta.getSessionId(); SessionId sessionId = meta.getSessionId();
IntroduceeSession newIntroduceeSession = null; IntroduceeSession newIntroduceeSession = null;
@@ -362,7 +371,19 @@ class IntroductionManagerImpl extends ConversationClientImpl
@Override @Override
public void respondToIntroduction(ContactId contactId, SessionId sessionId, public void respondToIntroduction(ContactId contactId, SessionId sessionId,
boolean accept) throws DbException { 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 { try {
// Look up the session // Look up the session
StoredSession ss = getSession(txn, sessionId); StoredSession ss = getSession(txn, sessionId);
@@ -381,15 +402,13 @@ class IntroductionManagerImpl extends ConversationClientImpl
if (accept) { if (accept) {
session = introduceeEngine.onAcceptAction(txn, session); session = introduceeEngine.onAcceptAction(txn, session);
} else { } else {
session = introduceeEngine.onDeclineAction(txn, session); session = introduceeEngine
.onDeclineAction(txn, session, isAutoDecline);
} }
// Store the updated session // Store the updated session
storeSession(txn, ss.storageId, session); storeSession(txn, ss.storageId, session);
db.commitTransaction(txn);
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} finally {
db.endTransaction(txn);
} }
} }
@@ -487,7 +506,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
return new IntroductionResponse(m, contactGroupId, meta.getTimestamp(), return new IntroductionResponse(m, contactGroupId, meta.getTimestamp(),
meta.isLocal(), meta.isRead(), status.isSent(), status.isSeen(), meta.isLocal(), meta.isRead(), status.isSent(), status.isSeen(),
sessionId, accept, author, authorInfo, role, canSucceed, sessionId, accept, author, authorInfo, role, canSucceed,
meta.getAutoDeleteTimer()); meta.getAutoDeleteTimer(), meta.isAutoDecline());
} }
private void removeSessionWithIntroducer(Transaction txn, 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 @FunctionalInterface
private interface MessageRetriever { private interface MessageRetriever {
/** /**

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.introduction; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.cleanup.CleanupManager;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder; import org.briarproject.bramble.api.data.MetadataEncoder;
@@ -50,7 +51,8 @@ public class IntroductionModule {
ValidationManager validationManager, ValidationManager validationManager,
ConversationManager conversationManager, ConversationManager conversationManager,
ClientVersioningManager clientVersioningManager, ClientVersioningManager clientVersioningManager,
IntroductionManagerImpl introductionManager) { IntroductionManagerImpl introductionManager,
CleanupManager cleanupManager) {
lifecycleManager.registerOpenDatabaseHook(introductionManager); lifecycleManager.registerOpenDatabaseHook(introductionManager);
contactManager.registerContactHook(introductionManager); contactManager.registerContactHook(introductionManager);
validationManager.registerIncomingMessageHook(CLIENT_ID, validationManager.registerIncomingMessageHook(CLIENT_ID,
@@ -58,6 +60,8 @@ public class IntroductionModule {
conversationManager.registerConversationClient(introductionManager); conversationManager.registerConversationClient(introductionManager);
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION, clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
MINOR_VERSION, introductionManager); MINOR_VERSION, introductionManager);
cleanupManager.registerCleanupHook(CLIENT_ID, MAJOR_VERSION,
introductionManager);
return introductionManager; return introductionManager;
} }

View File

@@ -133,7 +133,7 @@ class IntroductionValidator extends BdfMessageValidator {
SessionId sessionId = new SessionId(sessionIdBytes); SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder.encodeMetadata(ACCEPT, sessionId, BdfDictionary meta = messageEncoder.encodeMetadata(ACCEPT, sessionId,
m.getTimestamp(), false, false, false, timer); m.getTimestamp(), timer);
if (previousMessageId == null) { if (previousMessageId == null) {
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} else { } else {
@@ -163,7 +163,7 @@ class IntroductionValidator extends BdfMessageValidator {
SessionId sessionId = new SessionId(sessionIdBytes); SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder.encodeMetadata(type, sessionId, BdfDictionary meta = messageEncoder.encodeMetadata(type, sessionId,
m.getTimestamp(), false, false, false, timer); m.getTimestamp(), timer);
if (previousMessageId == null) { if (previousMessageId == null) {
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} else { } else {
@@ -190,7 +190,7 @@ class IntroductionValidator extends BdfMessageValidator {
SessionId sessionId = new SessionId(sessionIdBytes); SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder.encodeMetadata(AUTH, sessionId, 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); MessageId dependency = new MessageId(previousMessageId);
return new BdfMessageContext(meta, singletonList(dependency)); return new BdfMessageContext(meta, singletonList(dependency));
} }
@@ -210,7 +210,7 @@ class IntroductionValidator extends BdfMessageValidator {
SessionId sessionId = new SessionId(sessionIdBytes); SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder.encodeMetadata(ACTIVATE, sessionId, BdfDictionary meta = messageEncoder.encodeMetadata(ACTIVATE, sessionId,
m.getTimestamp(), false, false, false, NO_AUTO_DELETE_TIMER); m.getTimestamp(), NO_AUTO_DELETE_TIMER);
if (previousMessageId == null) { if (previousMessageId == null) {
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} else { } else {
@@ -231,7 +231,7 @@ class IntroductionValidator extends BdfMessageValidator {
SessionId sessionId = new SessionId(sessionIdBytes); SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder.encodeMetadata(type, sessionId, BdfDictionary meta = messageEncoder.encodeMetadata(type, sessionId,
m.getTimestamp(), false, false, false, NO_AUTO_DELETE_TIMER); m.getTimestamp(), NO_AUTO_DELETE_TIMER);
if (previousMessageId == null) { if (previousMessageId == null) {
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} else { } else {

View File

@@ -21,9 +21,14 @@ interface MessageEncoder {
BdfDictionary encodeRequestMetadata(long timestamp, BdfDictionary encodeRequestMetadata(long timestamp,
long autoDeleteTimer); long autoDeleteTimer);
BdfDictionary encodeMetadata(MessageType type,
@Nullable SessionId sessionId, long timestamp,
long autoDeleteTimer);
BdfDictionary encodeMetadata(MessageType type, BdfDictionary encodeMetadata(MessageType type,
@Nullable SessionId sessionId, long timestamp, boolean local, @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); 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.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_AUTO_DELETE_TIMER;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER; 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_LOCAL;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_SESSION_ID; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_SESSION_ID;
@@ -53,15 +54,24 @@ class MessageEncoderImpl implements MessageEncoder {
public BdfDictionary encodeRequestMetadata(long timestamp, public BdfDictionary encodeRequestMetadata(long timestamp,
long autoDeleteTimer) { long autoDeleteTimer) {
BdfDictionary meta = encodeMetadata(REQUEST, null, timestamp, BdfDictionary meta = encodeMetadata(REQUEST, null, timestamp,
false, false, false, autoDeleteTimer); autoDeleteTimer);
meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, false); meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, false);
return meta; 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 @Override
public BdfDictionary encodeMetadata(MessageType type, public BdfDictionary encodeMetadata(MessageType type,
@Nullable SessionId sessionId, long timestamp, boolean local, @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(); BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_MESSAGE_TYPE, type.getValue()); meta.put(MSG_KEY_MESSAGE_TYPE, type.getValue());
if (sessionId != null) if (sessionId != null)
@@ -75,6 +85,9 @@ class MessageEncoderImpl implements MessageEncoder {
if (autoDeleteTimer != NO_AUTO_DELETE_TIMER) { if (autoDeleteTimer != NO_AUTO_DELETE_TIMER) {
meta.put(MSG_KEY_AUTO_DELETE_TIMER, autoDeleteTimer); meta.put(MSG_KEY_AUTO_DELETE_TIMER, autoDeleteTimer);
} }
if (isAutoDecline) {
meta.put(MSG_KEY_IS_AUTO_DECLINE, isAutoDecline);
}
return meta; return meta;
} }

View File

@@ -14,11 +14,11 @@ class MessageMetadata {
@Nullable @Nullable
private final SessionId sessionId; private final SessionId sessionId;
private final long timestamp, autoDeleteTimer; 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, MessageMetadata(MessageType type, @Nullable SessionId sessionId,
long timestamp, boolean local, boolean read, boolean visible, long timestamp, boolean local, boolean read, boolean visible,
boolean available, long autoDeleteTimer) { boolean available, long autoDeleteTimer, boolean isAutoDecline) {
this.type = type; this.type = type;
this.sessionId = sessionId; this.sessionId = sessionId;
this.timestamp = timestamp; this.timestamp = timestamp;
@@ -27,6 +27,7 @@ class MessageMetadata {
this.visible = visible; this.visible = visible;
this.available = available; this.available = available;
this.autoDeleteTimer = autoDeleteTimer; this.autoDeleteTimer = autoDeleteTimer;
this.isAutoDecline = isAutoDecline;
} }
MessageType getMessageType() { MessageType getMessageType() {
@@ -61,4 +62,8 @@ class MessageMetadata {
public long getAutoDeleteTimer() { public long getAutoDeleteTimer() {
return autoDeleteTimer; 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.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_AUTO_DELETE_TIMER;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER; 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_LOCAL;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_SESSION_ID; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_SESSION_ID;
@@ -46,7 +47,8 @@ class MessageParserImpl implements MessageParser {
} }
@Override @Override
public BdfDictionary getRequestsAvailableToAnswerQuery(SessionId sessionId) { public BdfDictionary getRequestsAvailableToAnswerQuery(
SessionId sessionId) {
return BdfDictionary.of( return BdfDictionary.of(
new BdfEntry(MSG_KEY_AVAILABLE_TO_ANSWER, true), new BdfEntry(MSG_KEY_AVAILABLE_TO_ANSWER, true),
new BdfEntry(MSG_KEY_MESSAGE_TYPE, REQUEST.getValue()), 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 visible = d.getBoolean(MSG_KEY_VISIBLE_IN_UI);
boolean available = d.getBoolean(MSG_KEY_AVAILABLE_TO_ANSWER, false); boolean available = d.getBoolean(MSG_KEY_AVAILABLE_TO_ANSWER, false);
long timer = d.getLong(MSG_KEY_AUTO_DELETE_TIMER, NO_AUTO_DELETE_TIMER); 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, return new MessageMetadata(type, sessionId, timestamp, local, read,
visible, available, timer); visible, available, timer, isAutoDecline);
} }
@Override @Override

View File

@@ -15,7 +15,14 @@ interface ProtocolEngine<S extends Session<?>> {
S onAcceptAction(Transaction txn, S session) throws DbException; 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) S onRequestMessage(Transaction txn, S session, RequestMessage m)
throws DbException, FormatException; throws DbException, FormatException;

View File

@@ -102,7 +102,7 @@ public abstract class AbstractAutoDeleteTest extends
@FunctionalInterface @FunctionalInterface
protected interface HeaderConsumer { protected interface HeaderConsumer {
void accept(ConversationMessageHeader header); void accept(ConversationMessageHeader header) throws DbException;
} }
protected void forEachHeader(BriarIntegrationTestComponent component, 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()); listener2.getResponse().getIntroducedAuthor().getName());
assertFalse(listener2.getResponse().canSucceed()); 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(listener1.succeeded);
assertFalse(listener2.succeeded); assertFalse(listener2.succeeded);

View File

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

View File

@@ -640,8 +640,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase {
private void expectEncodeMetadata(MessageType type, long autoDeleteTimer) { private void expectEncodeMetadata(MessageType type, long autoDeleteTimer) {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(messageEncoder).encodeMetadata(type, sessionId, oneOf(messageEncoder).encodeMetadata(type, sessionId,
message.getTimestamp(), false, false, false, message.getTimestamp(), autoDeleteTimer);
autoDeleteTimer);
will(returnValue(meta)); will(returnValue(meta));
}}); }});
} }

View File

@@ -107,7 +107,7 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase {
@Test @Test
public void testMessageMetadata() throws FormatException { public void testMessageMetadata() throws FormatException {
BdfDictionary d = messageEncoder.encodeMetadata(ABORT, sessionId, 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); MessageMetadata meta = messageParser.parseMetadata(d);
assertEquals(ABORT, meta.getMessageType()); assertEquals(ABORT, meta.getMessageType());