Merge branch '1836-delete-group-messages' into '804-self-destructing-messages'

Automatically decline incoming private group invitations when they self-destruct

See merge request briar/briar!1389
This commit is contained in:
akwizgran
2021-03-03 16:56:16 +00:00
32 changed files with 882 additions and 237 deletions

View File

@@ -209,6 +209,10 @@ class ConversationVisitor implements
text = ctx.getString( text = ctx.getString(
R.string.groups_invitations_response_accepted_sent, R.string.groups_invitations_response_accepted_sent,
contactName.getValue()); contactName.getValue());
} else if (r.isAutoDecline()) {
text = ctx.getString(
R.string.groups_invitations_response_declined_auto,
contactName.getValue());
} else { } else {
text = ctx.getString( text = ctx.getString(
R.string.groups_invitations_response_declined_sent, R.string.groups_invitations_response_declined_sent,

View File

@@ -365,6 +365,7 @@
</plurals> </plurals>
<string name="groups_invitations_response_accepted_sent">You accepted the group invitation from %s.</string> <string name="groups_invitations_response_accepted_sent">You accepted the group invitation from %s.</string>
<string name="groups_invitations_response_declined_sent">You declined the group invitation from %s.</string> <string name="groups_invitations_response_declined_sent">You declined the group invitation from %s.</string>
<string name="groups_invitations_response_declined_auto">The group invitation from %s was automatically declined.</string>
<string name="groups_invitations_response_accepted_received">%s accepted the group invitation.</string> <string name="groups_invitations_response_accepted_received">%s accepted the group invitation.</string>
<string name="groups_invitations_response_declined_received">%s declined the group invitation.</string> <string name="groups_invitations_response_declined_received">%s declined the group invitation.</string>
<string name="sharing_status_groups">Only the creator can invite new members to the group. Below are all current members of the group.</string> <string name="sharing_status_groups">Only the creator can invite new members to the group. Below are all current members of the group.</string>

View File

@@ -15,7 +15,7 @@ public class BlogInvitationResponse extends InvitationResponse {
SessionId sessionId, boolean accept, GroupId shareableId, SessionId sessionId, boolean accept, GroupId shareableId,
long autoDeleteTimer) { long autoDeleteTimer) {
super(id, groupId, time, local, read, sent, seen, sessionId, super(id, groupId, time, local, read, sent, seen, sessionId,
accept, shareableId, autoDeleteTimer); accept, shareableId, autoDeleteTimer, false);
} }
@Override @Override

View File

@@ -12,14 +12,16 @@ import javax.annotation.concurrent.Immutable;
public abstract class ConversationResponse extends ConversationMessageHeader { public abstract class ConversationResponse extends ConversationMessageHeader {
private final SessionId sessionId; private final SessionId sessionId;
private final boolean accepted; private final boolean accepted, isAutoDecline;
public ConversationResponse(MessageId id, GroupId groupId, long time, public ConversationResponse(MessageId id, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen, boolean local, boolean read, boolean sent, boolean seen,
SessionId sessionId, boolean accepted, long autoDeleteTimer) { SessionId sessionId, boolean accepted, long autoDeleteTimer,
boolean isAutoDecline) {
super(id, groupId, time, local, read, sent, seen, autoDeleteTimer); super(id, groupId, time, local, read, sent, seen, autoDeleteTimer);
this.sessionId = sessionId; this.sessionId = sessionId;
this.accepted = accepted; this.accepted = accepted;
this.isAutoDecline = isAutoDecline;
} }
public SessionId getSessionId() { public SessionId getSessionId() {
@@ -30,4 +32,7 @@ public abstract class ConversationResponse extends ConversationMessageHeader {
return accepted; return accepted;
} }
public boolean isAutoDecline() {
return isAutoDecline;
}
} }

View File

@@ -18,7 +18,7 @@ public class ForumInvitationResponse extends InvitationResponse {
SessionId sessionId, boolean accept, GroupId shareableId, SessionId sessionId, boolean accept, GroupId shareableId,
long autoDeleteTimer) { long autoDeleteTimer) {
super(id, groupId, time, local, read, sent, seen, sessionId, super(id, groupId, time, local, read, sent, seen, sessionId,
accept, shareableId, autoDeleteTimer); accept, shareableId, autoDeleteTimer, false);
} }
@Override @Override

View File

@@ -1,13 +1,13 @@
package org.briarproject.briar.api.introduction; package org.briarproject.briar.api.introduction;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.conversation.ConversationMessageVisitor; import org.briarproject.briar.api.conversation.ConversationMessageVisitor;
import org.briarproject.briar.api.conversation.ConversationResponse; import org.briarproject.briar.api.conversation.ConversationResponse;
import org.briarproject.briar.api.identity.AuthorInfo;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@@ -28,7 +28,7 @@ public class IntroductionResponse extends ConversationResponse {
AuthorInfo introducedAuthorInfo, Role role, boolean canSucceed, AuthorInfo introducedAuthorInfo, Role role, boolean canSucceed,
long autoDeleteTimer) { long autoDeleteTimer) {
super(messageId, groupId, time, local, read, sent, seen, sessionId, super(messageId, groupId, time, local, read, sent, seen, sessionId,
accepted, autoDeleteTimer); accepted, autoDeleteTimer, false);
this.introducedAuthor = author; this.introducedAuthor = author;
this.introducedAuthorInfo = introducedAuthorInfo; this.introducedAuthorInfo = introducedAuthorInfo;
this.ourRole = role; this.ourRole = role;

View File

@@ -16,9 +16,9 @@ public class GroupInvitationResponse extends InvitationResponse {
public GroupInvitationResponse(MessageId id, GroupId groupId, long time, public GroupInvitationResponse(MessageId id, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen, boolean local, boolean read, boolean sent, boolean seen,
SessionId sessionId, boolean accept, GroupId shareableId, SessionId sessionId, boolean accept, GroupId shareableId,
long autoDeleteTimer) { long autoDeleteTimer, boolean isAutoDecline) {
super(id, groupId, time, local, read, sent, seen, sessionId, super(id, groupId, time, local, read, sent, seen, sessionId,
accept, shareableId, autoDeleteTimer); accept, shareableId, autoDeleteTimer, isAutoDecline);
} }
@Override @Override

View File

@@ -12,9 +12,9 @@ public abstract class InvitationResponse extends ConversationResponse {
public InvitationResponse(MessageId id, GroupId groupId, long time, public InvitationResponse(MessageId id, GroupId groupId, long time,
boolean local, boolean read, boolean sent, boolean seen, boolean local, boolean read, boolean sent, boolean seen,
SessionId sessionId, boolean accepted, GroupId shareableId, SessionId sessionId, boolean accepted, GroupId shareableId,
long autoDeleteTimer) { long autoDeleteTimer, boolean isAutoDecline) {
super(id, groupId, time, local, read, sent, seen, sessionId, accepted, super(id, groupId, time, local, read, sent, seen, sessionId, accepted,
autoDeleteTimer); autoDeleteTimer, isAutoDecline);
this.shareableId = shareableId; this.shareableId = shareableId;
} }

View File

@@ -7,6 +7,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.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -19,13 +20,16 @@ import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.briar.api.autodelete.AutoDeleteManager; import org.briarproject.briar.api.autodelete.AutoDeleteManager;
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.conversation.ConversationManager; import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.privategroup.GroupMessage; import org.briarproject.briar.api.privategroup.GroupMessage;
import org.briarproject.briar.api.privategroup.GroupMessageFactory; import org.briarproject.briar.api.privategroup.GroupMessageFactory;
import org.briarproject.briar.api.privategroup.PrivateGroup; import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.PrivateGroupFactory; import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
import org.briarproject.briar.api.privategroup.PrivateGroupManager; import org.briarproject.briar.api.privategroup.PrivateGroupManager;
import org.briarproject.briar.api.privategroup.event.GroupInvitationResponseReceivedEvent;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager; import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
import java.util.Collection; import java.util.Collection;
@@ -132,6 +136,10 @@ abstract class AbstractProtocolEngine<S extends Session<?>>
privateGroup.getCreator(), privateGroup.getSalt(), text, privateGroup.getCreator(), privateGroup.getSalt(), text,
signature, timer); signature, timer);
sendMessage(txn, m, INVITE, privateGroup.getId(), true, timer); sendMessage(txn, m, INVITE, privateGroup.getId(), true, 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.encodeInviteMessage(s.getContactGroupId(), m = messageEncoder.encodeInviteMessage(s.getContactGroupId(),
privateGroup.getId(), timestamp, privateGroup.getName(), privateGroup.getId(), timestamp, privateGroup.getName(),
@@ -162,6 +170,10 @@ abstract class AbstractProtocolEngine<S extends Session<?>>
s.getLastLocalMessageId(), timer); s.getLastLocalMessageId(), timer);
sendMessage(txn, m, JOIN, s.getPrivateGroupId(), visibleInUi, sendMessage(txn, m, JOIN, s.getPrivateGroupId(), visibleInUi,
timer); 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.encodeJoinMessage(s.getContactGroupId(), m = messageEncoder.encodeJoinMessage(s.getContactGroupId(),
s.getPrivateGroupId(), localTimestamp, s.getPrivateGroupId(), localTimestamp,
@@ -172,15 +184,20 @@ abstract class AbstractProtocolEngine<S extends Session<?>>
return m; return m;
} }
Message sendLeaveMessage(Transaction txn, S s, boolean visibleInUi) Message sendLeaveMessage(Transaction txn, S s) throws DbException {
throws DbException { return sendLeaveMessage(txn, s, false, false);
}
Message sendLeaveMessage(Transaction txn, S s, boolean visibleInUi,
boolean isAutoDecline) throws DbException {
if (!visibleInUi && isAutoDecline) throw new IllegalArgumentException();
Message m; Message m;
long localTimestamp = visibleInUi long localTimestamp = visibleInUi
? getTimestampForVisibleMessage(txn, s) ? getTimestampForVisibleMessage(txn, s)
: getTimestampForInvisibleMessage(s); : getTimestampForInvisibleMessage(s);
ContactId c = getContactId(txn, s.getContactGroupId()); ContactId c = getContactId(txn, s.getContactGroupId());
if (contactSupportsAutoDeletion(txn, c)) { if (contactSupportsAutoDeletion(txn, c)) {
// Set auto-delete timer if manually accepting an invitation // Set auto-delete timer if declining an invitation
long timer = NO_AUTO_DELETE_TIMER; long timer = NO_AUTO_DELETE_TIMER;
if (visibleInUi) { if (visibleInUi) {
timer = autoDeleteManager.getAutoDeleteTimer(txn, c, timer = autoDeleteManager.getAutoDeleteTimer(txn, c,
@@ -190,7 +207,22 @@ abstract class AbstractProtocolEngine<S extends Session<?>>
s.getPrivateGroupId(), localTimestamp, s.getPrivateGroupId(), localTimestamp,
s.getLastLocalMessageId(), timer); s.getLastLocalMessageId(), timer);
sendMessage(txn, m, LEAVE, s.getPrivateGroupId(), visibleInUi, sendMessage(txn, m, LEAVE, s.getPrivateGroupId(), visibleInUi,
timer); 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
SessionId sessionId =
new SessionId(s.getPrivateGroupId().getBytes());
GroupInvitationResponse response = new GroupInvitationResponse(
m.getId(), s.getContactGroupId(), m.getTimestamp(),
true, true, false, false, sessionId, false,
s.getPrivateGroupId(), timer, true);
Event e = new GroupInvitationResponseReceivedEvent(response, c);
txn.attach(e);
}
} else { } else {
m = messageEncoder.encodeLeaveMessage(s.getContactGroupId(), m = messageEncoder.encodeLeaveMessage(s.getContactGroupId(),
s.getPrivateGroupId(), localTimestamp, s.getPrivateGroupId(), localTimestamp,
@@ -309,9 +341,17 @@ abstract class AbstractProtocolEngine<S extends Session<?>>
private void sendMessage(Transaction txn, Message m, MessageType type, private void sendMessage(Transaction txn, Message m, MessageType type,
GroupId privateGroupId, boolean visibleInConversation, GroupId privateGroupId, boolean visibleInConversation,
long autoDeleteTimer) throws DbException { long autoDeleteTimer) throws DbException {
sendMessage(txn, m, type, privateGroupId, visibleInConversation,
autoDeleteTimer, false);
}
private void sendMessage(Transaction txn, Message m, MessageType type,
GroupId privateGroupId, boolean visibleInConversation,
long autoDeleteTimer, boolean isAutoDecline) throws DbException {
BdfDictionary meta = messageEncoder.encodeMetadata(type, BdfDictionary meta = messageEncoder.encodeMetadata(type,
privateGroupId, m.getTimestamp(), true, true, privateGroupId, m.getTimestamp(), true, true,
visibleInConversation, false, false, autoDeleteTimer); visibleInConversation, false, false, autoDeleteTimer,
isAutoDecline);
try { try {
clientHelper.addLocalMessage(txn, m, meta, true, false); clientHelper.addLocalMessage(txn, m, meta, true, false);
} catch (FormatException e) { } catch (FormatException e) {

View File

@@ -84,8 +84,8 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
} }
@Override @Override
public CreatorSession onLeaveAction(Transaction txn, CreatorSession s) public CreatorSession onLeaveAction(Transaction txn, CreatorSession s,
throws DbException { boolean isAutoDecline) throws DbException {
switch (s.getState()) { switch (s.getState()) {
case START: case START:
case DISSOLVED: case DISSOLVED:
@@ -180,7 +180,7 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
throw new DbException(e); // Invalid group metadata throw new DbException(e); // Invalid group metadata
} }
// Send a LEAVE message // Send a LEAVE message
Message sent = sendLeaveMessage(txn, s, false); Message sent = sendLeaveMessage(txn, s);
// Move to the DISSOLVED state // Move to the DISSOLVED state
return new CreatorSession(s.getContactGroupId(), s.getPrivateGroupId(), return new CreatorSession(s.getContactGroupId(), s.getPrivateGroupId(),
sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(), sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(),
@@ -276,6 +276,6 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
SessionId sessionId = new SessionId(m.getPrivateGroupId().getBytes()); SessionId sessionId = new SessionId(m.getPrivateGroupId().getBytes());
return new GroupInvitationResponse(m.getId(), m.getContactGroupId(), return new GroupInvitationResponse(m.getId(), m.getContactGroupId(),
m.getTimestamp(), false, false, false, false, sessionId, m.getTimestamp(), false, false, false, false, sessionId,
accept, m.getPrivateGroupId(), m.getAutoDeleteTimer()); accept, m.getPrivateGroupId(), m.getAutoDeleteTimer(), false);
} }
} }

View File

@@ -11,6 +11,7 @@ interface GroupInvitationConstants {
String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer"; String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer";
String MSG_KEY_INVITATION_ACCEPTED = "invitationAccepted"; String MSG_KEY_INVITATION_ACCEPTED = "invitationAccepted";
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_IS_SESSION = "isSession"; String SESSION_KEY_IS_SESSION = "isSession";

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.privategroup.invitation; package org.briarproject.briar.privategroup.invitation;
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;
@@ -24,6 +25,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;
@@ -52,6 +54,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
import static org.briarproject.briar.privategroup.invitation.CreatorState.START; import static org.briarproject.briar.privategroup.invitation.CreatorState.START;
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT; import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE; import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
@@ -65,7 +68,7 @@ import static org.briarproject.briar.privategroup.invitation.Role.PEER;
@NotNullByDefault @NotNullByDefault
class GroupInvitationManagerImpl extends ConversationClientImpl class GroupInvitationManagerImpl extends ConversationClientImpl
implements GroupInvitationManager, OpenDatabaseHook, ContactHook, implements GroupInvitationManager, OpenDatabaseHook, ContactHook,
PrivateGroupHook, ClientVersioningHook { PrivateGroupHook, ClientVersioningHook, CleanupHook {
private final ClientVersioningManager clientVersioningManager; private final ClientVersioningManager clientVersioningManager;
private final ContactGroupFactory contactGroupFactory; private final ContactGroupFactory contactGroupFactory;
@@ -148,6 +151,11 @@ class GroupInvitationManagerImpl 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 = getSessionId(meta.getPrivateGroupId()); SessionId sessionId = getSessionId(meta.getPrivateGroupId());
StoredSession ss = getSession(txn, m.getGroupId(), sessionId); StoredSession ss = getSession(txn, m.getGroupId(), sessionId);
@@ -301,7 +309,13 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
@Override @Override
public void respondToInvitation(ContactId c, SessionId sessionId, public void respondToInvitation(ContactId c, SessionId sessionId,
boolean accept) throws DbException { boolean accept) throws DbException {
Transaction txn = db.startTransaction(false); db.transaction(false,
txn -> respondToInvitation(txn, c, sessionId, accept, false));
}
private void respondToInvitation(Transaction txn, ContactId c,
SessionId sessionId, boolean accept, boolean isAutoDecline)
throws DbException {
try { try {
// Look up the session // Look up the session
Contact contact = db.getContact(txn, c); Contact contact = db.getContact(txn, c);
@@ -313,14 +327,12 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
.parseInviteeSession(contactGroupId, ss.bdfSession); .parseInviteeSession(contactGroupId, ss.bdfSession);
// Handle the join or leave action // Handle the join or leave action
if (accept) session = inviteeEngine.onJoinAction(txn, session); if (accept) session = inviteeEngine.onJoinAction(txn, session);
else session = inviteeEngine.onLeaveAction(txn, session); else session =
inviteeEngine.onLeaveAction(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);
} }
} }
@@ -356,7 +368,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
} else if (type == LocalAction.JOIN) { } else if (type == LocalAction.JOIN) {
return engine.onJoinAction(txn, session); return engine.onJoinAction(txn, session);
} else if (type == LocalAction.LEAVE) { } else if (type == LocalAction.LEAVE) {
return engine.onLeaveAction(txn, session); return engine.onLeaveAction(txn, session, false);
} else if (type == LocalAction.MEMBER_ADDED) { } else if (type == LocalAction.MEMBER_ADDED) {
return engine.onMemberAddedAction(txn, session); return engine.onMemberAddedAction(txn, session);
} else { } else {
@@ -425,7 +437,8 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
return new GroupInvitationResponse(m, contactGroupId, return new GroupInvitationResponse(m, contactGroupId,
meta.getTimestamp(), meta.isLocal(), meta.isRead(), meta.getTimestamp(), meta.isLocal(), meta.isRead(),
status.isSent(), status.isSeen(), sessionId, accept, status.isSent(), status.isSeen(), sessionId, accept,
meta.getPrivateGroupId(), meta.getAutoDeleteTimer()); meta.getPrivateGroupId(), meta.getAutoDeleteTimer(),
meta.isAutoDecline());
} }
@Override @Override
@@ -686,7 +699,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
// get ID of the contact group // get ID of the contact group
GroupId g = getContactGroup(db.getContact(txn, c)).getId(); GroupId g = getContactGroup(db.getContact(txn, c)).getId();
// get metadata for all messages in the // get metadata for all messages in the group
// (these are sessions *and* protocol messages) // (these are sessions *and* protocol messages)
Map<MessageId, BdfDictionary> metadata; Map<MessageId, BdfDictionary> metadata;
try { try {
@@ -762,6 +775,57 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
return result; return result;
} }
@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 =
getSessionId(messageMetadata.getPrivateGroupId());
DeletableSession deletableSession = sessions.get(sessionId);
if (deletableSession == null) {
StoredSession ss = getSession(txn, g, sessionId);
if (ss == null) throw new DbException();
Session<?> session =
sessionParser.parseSession(g, ss.bdfSession);
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 InviteeState &&
session.state.isAwaitingResponse()) {
respondToInvitation(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));
}
@Override @Override
public Set<MessageId> getMessageIds(Transaction txn, ContactId c) public Set<MessageId> getMessageIds(Transaction txn, ContactId c)
throws DbException { throws DbException {

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.privategroup.invitation; package org.briarproject.briar.privategroup.invitation;
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;
@@ -41,7 +42,8 @@ public class GroupInvitationModule {
ValidationManager validationManager, ContactManager contactManager, ValidationManager validationManager, ContactManager contactManager,
PrivateGroupManager privateGroupManager, PrivateGroupManager privateGroupManager,
ConversationManager conversationManager, ConversationManager conversationManager,
ClientVersioningManager clientVersioningManager) { ClientVersioningManager clientVersioningManager,
CleanupManager cleanupManager) {
lifecycleManager.registerOpenDatabaseHook(groupInvitationManager); lifecycleManager.registerOpenDatabaseHook(groupInvitationManager);
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION, validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
groupInvitationManager); groupInvitationManager);
@@ -56,6 +58,8 @@ public class GroupInvitationModule {
PrivateGroupManager.MAJOR_VERSION, PrivateGroupManager.MAJOR_VERSION,
PrivateGroupManager.MINOR_VERSION, PrivateGroupManager.MINOR_VERSION,
groupInvitationManager.getPrivateGroupClientVersioningHook()); groupInvitationManager.getPrivateGroupClientVersioningHook());
cleanupManager.registerCleanupHook(CLIENT_ID, MAJOR_VERSION,
groupInvitationManager);
return groupInvitationManager; return groupInvitationManager;
} }

View File

@@ -110,8 +110,7 @@ class GroupInvitationValidator extends BdfMessageValidator {
} }
// Create the metadata // Create the metadata
BdfDictionary meta = messageEncoder.encodeMetadata(INVITE, BdfDictionary meta = messageEncoder.encodeMetadata(INVITE,
privateGroup.getId(), m.getTimestamp(), false, false, false, privateGroup.getId(), m.getTimestamp(), timer);
false, false, timer);
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} }
@@ -132,8 +131,7 @@ class GroupInvitationValidator extends BdfMessageValidator {
} }
BdfDictionary meta = messageEncoder.encodeMetadata(JOIN, BdfDictionary meta = messageEncoder.encodeMetadata(JOIN,
new GroupId(privateGroupId), m.getTimestamp(), false, false, new GroupId(privateGroupId), m.getTimestamp(), timer);
false, false, false, timer);
if (previousMessageId == null) { if (previousMessageId == null) {
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} else { } else {
@@ -160,8 +158,7 @@ class GroupInvitationValidator extends BdfMessageValidator {
} }
BdfDictionary meta = messageEncoder.encodeMetadata(LEAVE, BdfDictionary meta = messageEncoder.encodeMetadata(LEAVE,
new GroupId(privateGroupId), m.getTimestamp(), false, false, new GroupId(privateGroupId), m.getTimestamp(), timer);
false, false, false, timer);
if (previousMessageId == null) { if (previousMessageId == null) {
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} else { } else {
@@ -177,8 +174,8 @@ class GroupInvitationValidator extends BdfMessageValidator {
byte[] privateGroupId = body.getRaw(1); byte[] privateGroupId = body.getRaw(1);
checkLength(privateGroupId, UniqueId.LENGTH); checkLength(privateGroupId, UniqueId.LENGTH);
BdfDictionary meta = messageEncoder.encodeMetadata(ABORT, BdfDictionary meta = messageEncoder.encodeMetadata(ABORT,
new GroupId(privateGroupId), m.getTimestamp(), false, false, new GroupId(privateGroupId), m.getTimestamp(),
false, false, false, NO_AUTO_DELETE_TIMER); NO_AUTO_DELETE_TIMER);
return new BdfMessageContext(meta); return new BdfMessageContext(meta);
} }
} }

View File

@@ -89,8 +89,8 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
} }
@Override @Override
public InviteeSession onLeaveAction(Transaction txn, InviteeSession s) public InviteeSession onLeaveAction(Transaction txn, InviteeSession s,
throws DbException { boolean isAutoDecline) throws DbException {
switch (s.getState()) { switch (s.getState()) {
case START: case START:
case LEFT: case LEFT:
@@ -98,7 +98,7 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
case ERROR: case ERROR:
return s; // Ignored in these states return s; // Ignored in these states
case INVITED: case INVITED:
return onLocalDecline(txn, s); return onLocalDecline(txn, s, isAutoDecline);
case ACCEPTED: case ACCEPTED:
case JOINED: case JOINED:
return onLocalLeave(txn, s); return onLocalLeave(txn, s);
@@ -203,14 +203,14 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
s.getInviteTimestamp(), ACCEPTED); s.getInviteTimestamp(), ACCEPTED);
} }
private InviteeSession onLocalDecline(Transaction txn, InviteeSession s) private InviteeSession onLocalDecline(Transaction txn, InviteeSession s,
throws DbException { boolean isAutoDecline) throws DbException {
// Mark the invite message unavailable to answer // Mark the invite message unavailable to answer
MessageId inviteId = s.getLastRemoteMessageId(); MessageId inviteId = s.getLastRemoteMessageId();
if (inviteId == null) throw new IllegalStateException(); if (inviteId == null) throw new IllegalStateException();
markMessageAvailableToAnswer(txn, inviteId, false); markMessageAvailableToAnswer(txn, inviteId, false);
// Send a LEAVE message // Send a LEAVE message
Message sent = sendLeaveMessage(txn, s, true); Message sent = sendLeaveMessage(txn, s, true, isAutoDecline);
// Track the message // Track the message
messageTracker.trackOutgoingMessage(txn, sent); messageTracker.trackOutgoingMessage(txn, sent);
// Move to the START state // Move to the START state
@@ -222,7 +222,7 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
private InviteeSession onLocalLeave(Transaction txn, InviteeSession s) private InviteeSession onLocalLeave(Transaction txn, InviteeSession s)
throws DbException { throws DbException {
// Send a LEAVE message // Send a LEAVE message
Message sent = sendLeaveMessage(txn, s, false); Message sent = sendLeaveMessage(txn, s);
try { try {
// Make the private group invisible to the contact // Make the private group invisible to the contact
setPrivateGroupVisibility(txn, s, INVISIBLE); setPrivateGroupVisibility(txn, s, INVISIBLE);

View File

@@ -14,7 +14,11 @@ interface MessageEncoder {
BdfDictionary encodeMetadata(MessageType type, GroupId privateGroupId, BdfDictionary encodeMetadata(MessageType type, GroupId privateGroupId,
long timestamp, boolean local, boolean read, boolean visible, long timestamp, boolean local, boolean read, boolean visible,
boolean available, boolean accepted, long autoDeleteTimer); boolean available, boolean accepted, long autoDeleteTimer,
boolean isAutoDecline);
BdfDictionary encodeMetadata(MessageType type, GroupId privateGroupId,
long timestamp, long autoDeleteTimer);
void setVisibleInUi(BdfDictionary meta, boolean visible); void setVisibleInUi(BdfDictionary meta, boolean visible);

View File

@@ -20,6 +20,7 @@ import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_AUTO_DELETE_TIMER; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_AUTO_DELETE_TIMER;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_AVAILABLE_TO_ANSWER; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_INVITATION_ACCEPTED; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_INVITATION_ACCEPTED;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_IS_AUTO_DECLINE;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_LOCAL; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_LOCAL;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_MESSAGE_TYPE; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_PRIVATE_GROUP_ID; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_PRIVATE_GROUP_ID;
@@ -48,7 +49,7 @@ class MessageEncoderImpl implements MessageEncoder {
public BdfDictionary encodeMetadata(MessageType type, public BdfDictionary encodeMetadata(MessageType type,
GroupId privateGroupId, long timestamp, boolean local, boolean read, GroupId privateGroupId, long timestamp, boolean local, boolean read,
boolean visible, boolean available, boolean accepted, boolean visible, boolean available, boolean accepted,
long autoDeleteTimer) { 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());
meta.put(MSG_KEY_PRIVATE_GROUP_ID, privateGroupId); meta.put(MSG_KEY_PRIVATE_GROUP_ID, privateGroupId);
@@ -61,9 +62,19 @@ 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;
} }
@Override
public BdfDictionary encodeMetadata(MessageType type,
GroupId privateGroupId, long timestamp, long autoDeleteTimer) {
return encodeMetadata(type, privateGroupId, timestamp, false, false,
false, false, false, autoDeleteTimer, false);
}
@Override @Override
public void setVisibleInUi(BdfDictionary meta, boolean visible) { public void setVisibleInUi(BdfDictionary meta, boolean visible) {
meta.put(MSG_KEY_VISIBLE_IN_UI, visible); meta.put(MSG_KEY_VISIBLE_IN_UI, visible);

View File

@@ -13,10 +13,12 @@ class MessageMetadata {
private final GroupId privateGroupId; private final GroupId privateGroupId;
private final long timestamp, autoDeleteTimer; private final long timestamp, autoDeleteTimer;
private final boolean local, read, visible, available, accepted; private final boolean local, read, visible, available, accepted;
private final boolean isAutoDecline;
MessageMetadata(MessageType type, GroupId privateGroupId, MessageMetadata(MessageType type, GroupId privateGroupId,
long timestamp, boolean local, boolean read, boolean visible, long timestamp, boolean local, boolean read, boolean visible,
boolean available, boolean accepted, long autoDeleteTimer) { boolean available, boolean accepted, long autoDeleteTimer,
boolean isAutoDecline) {
this.privateGroupId = privateGroupId; this.privateGroupId = privateGroupId;
this.type = type; this.type = type;
this.timestamp = timestamp; this.timestamp = timestamp;
@@ -26,6 +28,7 @@ class MessageMetadata {
this.available = available; this.available = available;
this.accepted = accepted; this.accepted = accepted;
this.autoDeleteTimer = autoDeleteTimer; this.autoDeleteTimer = autoDeleteTimer;
this.isAutoDecline = isAutoDecline;
} }
MessageType getMessageType() { MessageType getMessageType() {
@@ -58,7 +61,7 @@ class MessageMetadata {
/** /**
* Returns true if the invitation was accepted. * Returns true if the invitation was accepted.
* * <p>
* Only applies to messages of type INVITE. * Only applies to messages of type INVITE.
*/ */
public boolean wasAccepted() { public boolean wasAccepted() {
@@ -68,4 +71,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.client.MessageTrackerConstants.MSG_KEY_READ
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_AUTO_DELETE_TIMER; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_AUTO_DELETE_TIMER;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_AVAILABLE_TO_ANSWER; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_INVITATION_ACCEPTED; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_INVITATION_ACCEPTED;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_IS_AUTO_DECLINE;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_LOCAL; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_LOCAL;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_MESSAGE_TYPE; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_PRIVATE_GROUP_ID; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_PRIVATE_GROUP_ID;
@@ -82,8 +83,9 @@ class MessageParserImpl implements MessageParser {
boolean accepted = meta.getBoolean(MSG_KEY_INVITATION_ACCEPTED, false); boolean accepted = meta.getBoolean(MSG_KEY_INVITATION_ACCEPTED, false);
long timer = meta.getLong(MSG_KEY_AUTO_DELETE_TIMER, long timer = meta.getLong(MSG_KEY_AUTO_DELETE_TIMER,
NO_AUTO_DELETE_TIMER); NO_AUTO_DELETE_TIMER);
boolean isAutoDecline = meta.getBoolean(MSG_KEY_IS_AUTO_DECLINE, false);
return new MessageMetadata(type, privateGroupId, timestamp, local, read, return new MessageMetadata(type, privateGroupId, timestamp, local, read,
visible, available, accepted, timer); visible, available, accepted, timer, isAutoDecline);
} }
@Override @Override

View File

@@ -85,8 +85,8 @@ class PeerProtocolEngine extends AbstractProtocolEngine<PeerSession> {
} }
@Override @Override
public PeerSession onLeaveAction(Transaction txn, PeerSession s) public PeerSession onLeaveAction(Transaction txn, PeerSession s,
throws DbException { boolean isAutoDecline) throws DbException {
switch (s.getState()) { switch (s.getState()) {
case START: case START:
case AWAIT_MEMBER: case AWAIT_MEMBER:
@@ -213,7 +213,7 @@ class PeerProtocolEngine extends AbstractProtocolEngine<PeerSession> {
private PeerSession onLocalLeaveFromBothJoined(Transaction txn, private PeerSession onLocalLeaveFromBothJoined(Transaction txn,
PeerSession s) throws DbException { PeerSession s) throws DbException {
// Send a LEAVE message // Send a LEAVE message
Message sent = sendLeaveMessage(txn, s, false); Message sent = sendLeaveMessage(txn, s);
try { try {
// Make the private group invisible to the contact // Make the private group invisible to the contact
setPrivateGroupVisibility(txn, s, INVISIBLE); setPrivateGroupVisibility(txn, s, INVISIBLE);
@@ -229,7 +229,7 @@ class PeerProtocolEngine extends AbstractProtocolEngine<PeerSession> {
private PeerSession onLocalLeaveFromLocalJoined(Transaction txn, private PeerSession onLocalLeaveFromLocalJoined(Transaction txn,
PeerSession s) throws DbException { PeerSession s) throws DbException {
// Send a LEAVE message // Send a LEAVE message
Message sent = sendLeaveMessage(txn, s, false); Message sent = sendLeaveMessage(txn, s);
try { try {
// Make the private group invisible to the contact // Make the private group invisible to the contact
setPrivateGroupVisibility(txn, s, INVISIBLE); setPrivateGroupVisibility(txn, s, INVISIBLE);

View File

@@ -16,7 +16,14 @@ interface ProtocolEngine<S extends Session<?>> {
S onJoinAction(Transaction txn, S session) throws DbException; S onJoinAction(Transaction txn, S session) throws DbException;
S onLeaveAction(Transaction txn, S session) throws DbException; /**
* Leaves the group or declines an invitation.
*
* @param isAutoDecline true if automatically declined due to deletion
* and false if initiated by the user.
*/
S onLeaveAction(Transaction txn, S session, boolean isAutoDecline)
throws DbException;
S onMemberAddedAction(Transaction txn, S session) throws DbException; S onMemberAddedAction(Transaction txn, S session) throws DbException;

View File

@@ -0,0 +1,180 @@
package org.briarproject.briar.autodelete;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.system.TimeTravelModule;
import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.conversation.ConversationManager.ConversationClient;
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.test.BriarIntegrationTest;
import org.briarproject.briar.test.BriarIntegrationTestComponent;
import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent;
import org.junit.Before;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import javax.annotation.Nonnull;
import static java.util.Collections.sort;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.briarproject.bramble.api.cleanup.CleanupManager.BATCH_DELAY_MS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public abstract class AbstractAutoDeleteTest extends
BriarIntegrationTest<BriarIntegrationTestComponent> {
protected final long startTime = System.currentTimeMillis();
protected abstract ConversationClient getConversationClient(
BriarIntegrationTestComponent component);
@Override
protected void createComponents() {
BriarIntegrationTestComponent component =
DaggerBriarIntegrationTestComponent.builder().build();
BriarIntegrationTestComponent.Helper.injectEagerSingletons(component);
component.inject(this);
c0 = DaggerBriarIntegrationTestComponent.builder()
.testDatabaseConfigModule(new TestDatabaseConfigModule(t0Dir))
.timeTravelModule(new TimeTravelModule(true))
.build();
BriarIntegrationTestComponent.Helper.injectEagerSingletons(c0);
c1 = DaggerBriarIntegrationTestComponent.builder()
.testDatabaseConfigModule(new TestDatabaseConfigModule(t1Dir))
.timeTravelModule(new TimeTravelModule(true))
.build();
BriarIntegrationTestComponent.Helper.injectEagerSingletons(c1);
c2 = DaggerBriarIntegrationTestComponent.builder()
.testDatabaseConfigModule(new TestDatabaseConfigModule(t2Dir))
.timeTravelModule(new TimeTravelModule(true))
.build();
BriarIntegrationTestComponent.Helper.injectEagerSingletons(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();
// Run the initial cleanup task that was scheduled at startup
c0.getTimeTravel().addCurrentTimeMillis(BATCH_DELAY_MS);
c1.getTimeTravel().addCurrentTimeMillis(BATCH_DELAY_MS);
c2.getTimeTravel().addCurrentTimeMillis(BATCH_DELAY_MS);
}
protected List<ConversationMessageHeader> getMessageHeaders(
BriarIntegrationTestComponent component, ContactId contactId)
throws Exception {
DatabaseComponent db = component.getDatabaseComponent();
ConversationClient conversationClient =
getConversationClient(component);
return sortHeaders(db.transactionWithResult(true, txn ->
conversationClient.getMessageHeaders(txn, contactId)));
}
@SuppressWarnings({"UseCompareMethod", "Java8ListSort"}) // Animal Sniffer
protected List<ConversationMessageHeader> sortHeaders(
Collection<ConversationMessageHeader> in) {
List<ConversationMessageHeader> out = new ArrayList<>(in);
sort(out, (a, b) ->
Long.valueOf(a.getTimestamp()).compareTo(b.getTimestamp()));
return out;
}
@FunctionalInterface
protected interface HeaderConsumer {
void accept(ConversationMessageHeader header);
}
protected void forEachHeader(BriarIntegrationTestComponent component,
ContactId contactId, int size, HeaderConsumer consumer)
throws Exception {
List<ConversationMessageHeader> headers =
getMessageHeaders(component, contactId);
assertEquals(size, headers.size());
for (ConversationMessageHeader h : headers) consumer.accept(h);
}
protected long getAutoDeleteTimer(BriarIntegrationTestComponent component,
ContactId contactId) throws DbException {
DatabaseComponent db = component.getDatabaseComponent();
AutoDeleteManager autoDeleteManager = component.getAutoDeleteManager();
return db.transactionWithResult(false,
txn -> autoDeleteManager.getAutoDeleteTimer(txn, contactId));
}
protected void markMessageRead(BriarIntegrationTestComponent component,
Contact contact, MessageId messageId) throws Exception {
MessagingManager messagingManager = component.getMessagingManager();
ConversationClient conversationClient =
getConversationClient(component);
GroupId groupId = conversationClient.getContactGroup(contact).getId();
messagingManager.setReadFlag(groupId, messageId, true);
waitForEvents(component);
}
protected void assertGroupCount(BriarIntegrationTestComponent component,
ContactId contactId, int messageCount, int unreadCount)
throws DbException {
DatabaseComponent db = component.getDatabaseComponent();
ConversationClient conversationClient =
getConversationClient(component);
GroupCount gc = db.transactionWithResult(true, txn ->
conversationClient.getGroupCount(txn, contactId));
assertEquals("messageCount", messageCount, gc.getMsgCount());
assertEquals("unreadCount", unreadCount, gc.getUnreadCount());
}
/**
* Broadcasts a marker event and waits for it to be delivered, which
* indicates that all previously broadcast events have been delivered.
*/
protected void waitForEvents(BriarIntegrationTestComponent component)
throws Exception {
CountDownLatch latch = new CountDownLatch(1);
MarkerEvent marker = new MarkerEvent();
EventBus eventBus = component.getEventBus();
eventBus.addListener(new EventListener() {
@Override
public void eventOccurred(@Nonnull Event e) {
if (e == marker) {
latch.countDown();
eventBus.removeListener(this);
}
}
});
eventBus.broadcast(marker);
if (!latch.await(1, MINUTES)) fail();
}
private static class MarkerEvent extends Event {
}
}

View File

@@ -1,44 +1,29 @@
package org.briarproject.briar.messaging; package org.briarproject.briar.messaging;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
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.MessageDeletedException; import org.briarproject.bramble.api.db.MessageDeletedException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.system.TimeTravelModule;
import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.briar.api.attachment.AttachmentHeader; import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.autodelete.AutoDeleteManager; import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.conversation.ConversationManager; import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.conversation.ConversationManager.ConversationClient;
import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage; import org.briarproject.briar.api.messaging.PrivateMessage;
import org.briarproject.briar.api.messaging.PrivateMessageFactory; import org.briarproject.briar.api.messaging.PrivateMessageFactory;
import org.briarproject.briar.test.BriarIntegrationTest; import org.briarproject.briar.autodelete.AbstractAutoDeleteTest;
import org.briarproject.briar.test.BriarIntegrationTestComponent; import org.briarproject.briar.test.BriarIntegrationTestComponent;
import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch;
import javax.annotation.Nonnull;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.sort;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.briarproject.bramble.api.cleanup.CleanupManager.BATCH_DELAY_MS; import static org.briarproject.bramble.api.cleanup.CleanupManager.BATCH_DELAY_MS;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.MIN_AUTO_DELETE_TIMER_MS; import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.MIN_AUTO_DELETE_TIMER_MS;
@@ -47,56 +32,13 @@ import static org.briarproject.briar.messaging.MessagingConstants.MISSING_ATTACH
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class AutoDeleteIntegrationTest public class AutoDeleteIntegrationTest extends AbstractAutoDeleteTest {
extends BriarIntegrationTest<BriarIntegrationTestComponent> {
@Override @Override
protected void createComponents() { protected ConversationClient getConversationClient(
BriarIntegrationTestComponent component = BriarIntegrationTestComponent component) {
DaggerBriarIntegrationTestComponent.builder().build(); return component.getMessagingManager();
BriarIntegrationTestComponent.Helper.injectEagerSingletons(component);
component.inject(this);
c0 = DaggerBriarIntegrationTestComponent.builder()
.testDatabaseConfigModule(new TestDatabaseConfigModule(t0Dir))
.timeTravelModule(new TimeTravelModule(true))
.build();
BriarIntegrationTestComponent.Helper.injectEagerSingletons(c0);
c1 = DaggerBriarIntegrationTestComponent.builder()
.testDatabaseConfigModule(new TestDatabaseConfigModule(t1Dir))
.timeTravelModule(new TimeTravelModule(true))
.build();
BriarIntegrationTestComponent.Helper.injectEagerSingletons(c1);
c2 = DaggerBriarIntegrationTestComponent.builder()
.testDatabaseConfigModule(new TestDatabaseConfigModule(t2Dir))
.timeTravelModule(new TimeTravelModule(true))
.build();
BriarIntegrationTestComponent.Helper.injectEagerSingletons(c2);
// Use different times to avoid creating identical messages that are
// treated as redundant copies of the same message (#1907)
try {
long now = System.currentTimeMillis();
c0.getTimeTravel().setCurrentTimeMillis(now);
c1.getTimeTravel().setCurrentTimeMillis(now + 1);
c2.getTimeTravel().setCurrentTimeMillis(now + 2);
} catch (InterruptedException e) {
fail();
}
}
@Override
@Before
public void setUp() throws Exception {
super.setUp();
// Run the initial cleanup task that was scheduled at startup
c0.getTimeTravel().addCurrentTimeMillis(BATCH_DELAY_MS);
c1.getTimeTravel().addCurrentTimeMillis(BATCH_DELAY_MS);
c2.getTimeTravel().addCurrentTimeMillis(BATCH_DELAY_MS);
} }
@Test @Test
@@ -216,7 +158,6 @@ public class AutoDeleteIntegrationTest
assertEquals(1, getMessageHeaders(c1, contactId0From1).size()); assertEquals(1, getMessageHeaders(c1, contactId0From1).size());
// 1 marks the message as read - this starts 1's timer // 1 marks the message as read - this starts 1's timer
markMessageRead(c1, contact0From1, messageId); markMessageRead(c1, contact0From1, messageId);
waitForEvents(c1);
assertGroupCount(c1, contactId0From1, 1, 0); assertGroupCount(c1, contactId0From1, 1, 0);
// Before 1's timer elapses, 1 should still see the message // Before 1's timer elapses, 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1); c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
@@ -293,7 +234,6 @@ public class AutoDeleteIntegrationTest
assertEquals(1, getMessageHeaders(c1, contactId0From1).size()); assertEquals(1, getMessageHeaders(c1, contactId0From1).size());
// 1 marks the message as read - this starts 1's timer // 1 marks the message as read - this starts 1's timer
markMessageRead(c1, contact0From1, messageId0); markMessageRead(c1, contact0From1, messageId0);
waitForEvents(c1);
assertGroupCount(c1, contactId0From1, 1, 0); assertGroupCount(c1, contactId0From1, 1, 0);
// Before 1's timer elapses, 1 should still see the message // Before 1's timer elapses, 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1); c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
@@ -355,7 +295,6 @@ public class AutoDeleteIntegrationTest
assertEquals(0, getMessageHeaders(c1, contactId0From1).size()); assertEquals(0, getMessageHeaders(c1, contactId0From1).size());
// 0 marks the message as read - this starts 0's timer // 0 marks the message as read - this starts 0's timer
markMessageRead(c0, contact1From0, messageId1); markMessageRead(c0, contact1From0, messageId1);
waitForEvents(c0);
assertGroupCount(c0, contactId1From0, 1, 0); assertGroupCount(c0, contactId1From0, 1, 0);
// Before 0's timer elapses, 0 should still see the message // Before 0's timer elapses, 0 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1); c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
@@ -426,7 +365,6 @@ public class AutoDeleteIntegrationTest
assertFalse(messageIsDeleted(c1, attachmentHeader.getMessageId())); assertFalse(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// 1 marks the message as read - this starts 1's timer // 1 marks the message as read - this starts 1's timer
markMessageRead(c1, contact0From1, messageId); markMessageRead(c1, contact0From1, messageId);
waitForEvents(c1);
assertGroupCount(c1, contactId0From1, 1, 0); assertGroupCount(c1, contactId0From1, 1, 0);
// Before 1's timer elapses, 1 should still see the message // Before 1's timer elapses, 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1); c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
@@ -501,7 +439,6 @@ public class AutoDeleteIntegrationTest
assertEquals(1, getMessageHeaders(c1, contactId0From1).size()); assertEquals(1, getMessageHeaders(c1, contactId0From1).size());
// 1 marks the message as read - this starts 1's timer // 1 marks the message as read - this starts 1's timer
markMessageRead(c1, contact0From1, messageId); markMessageRead(c1, contact0From1, messageId);
waitForEvents(c1);
assertGroupCount(c1, contactId0From1, 1, 0); assertGroupCount(c1, contactId0From1, 1, 0);
// Before 1's timer elapses, 1 should still see the message // Before 1's timer elapses, 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1); c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
@@ -603,7 +540,6 @@ public class AutoDeleteIntegrationTest
assertTrue(messageIsDeleted(c1, attachmentHeader.getMessageId())); assertTrue(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// 1 marks the message as read - this starts 1's timer // 1 marks the message as read - this starts 1's timer
markMessageRead(c1, contact0From1, messageId); markMessageRead(c1, contact0From1, messageId);
waitForEvents(c1);
assertGroupCount(c1, contactId0From1, 1, 0); assertGroupCount(c1, contactId0From1, 1, 0);
// Before 1's timer elapses, 1 should still see the message // Before 1's timer elapses, 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1); c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
@@ -711,7 +647,6 @@ public class AutoDeleteIntegrationTest
assertFalse(messageIsDeleted(c1, attachmentHeader.getMessageId())); assertFalse(messageIsDeleted(c1, attachmentHeader.getMessageId()));
// 1 marks the message as read - this starts 1's timer // 1 marks the message as read - this starts 1's timer
markMessageRead(c1, contact0From1, messageId); markMessageRead(c1, contact0From1, messageId);
waitForEvents(c1);
assertGroupCount(c1, contactId0From1, 1, 0); assertGroupCount(c1, contactId0From1, 1, 0);
// Before 1's timer elapses, 1 should still see the message // Before 1's timer elapses, 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1); c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
@@ -808,33 +743,6 @@ public class AutoDeleteIntegrationTest
db.transaction(false, txn -> db.setMessageShared(txn, messageId)); db.transaction(false, txn -> db.setMessageShared(txn, messageId));
} }
private List<ConversationMessageHeader> getMessageHeaders(
BriarIntegrationTestComponent component, ContactId contactId)
throws Exception {
DatabaseComponent db = component.getDatabaseComponent();
MessagingManager messagingManager = component.getMessagingManager();
return sortHeaders(db.transactionWithResult(true, txn ->
messagingManager.getMessageHeaders(txn, contactId)));
}
private long getAutoDeleteTimer(BriarIntegrationTestComponent component,
ContactId contactId) throws DbException {
DatabaseComponent db = component.getDatabaseComponent();
AutoDeleteManager autoDeleteManager = component.getAutoDeleteManager();
return db.transactionWithResult(false,
txn -> autoDeleteManager.getAutoDeleteTimer(txn, contactId));
}
private void markMessageRead(BriarIntegrationTestComponent component,
Contact contact, MessageId messageId) throws DbException {
MessagingManager messagingManager = component.getMessagingManager();
GroupId groupId = messagingManager.getContactGroup(contact).getId();
messagingManager.setReadFlag(groupId, messageId, true);
}
private boolean messageIsDeleted(BriarIntegrationTestComponent component, private boolean messageIsDeleted(BriarIntegrationTestComponent component,
MessageId messageId) throws DbException { MessageId messageId) throws DbException {
DatabaseComponent db = component.getDatabaseComponent(); DatabaseComponent db = component.getDatabaseComponent();
@@ -847,49 +755,4 @@ public class AutoDeleteIntegrationTest
} }
} }
private void assertGroupCount(BriarIntegrationTestComponent component,
ContactId contactId, int messageCount, int unreadCount)
throws DbException {
DatabaseComponent db = component.getDatabaseComponent();
MessagingManager messagingManager = component.getMessagingManager();
GroupCount gc = db.transactionWithResult(true, txn ->
messagingManager.getGroupCount(txn, contactId));
assertEquals(messageCount, gc.getMsgCount());
assertEquals(unreadCount, gc.getUnreadCount());
}
@SuppressWarnings({"UseCompareMethod", "Java8ListSort"}) // Animal Sniffer
private List<ConversationMessageHeader> sortHeaders(
Collection<ConversationMessageHeader> in) {
List<ConversationMessageHeader> out = new ArrayList<>(in);
sort(out, (a, b) ->
Long.valueOf(a.getTimestamp()).compareTo(b.getTimestamp()));
return out;
}
/**
* Broadcasts a marker event and waits for it to be delivered, which
* indicates that all previously broadcast events have been delivered.
*/
private void waitForEvents(BriarIntegrationTestComponent component)
throws Exception {
CountDownLatch latch = new CountDownLatch(1);
MarkerEvent marker = new MarkerEvent();
EventBus eventBus = component.getEventBus();
eventBus.addListener(new EventListener() {
@Override
public void eventOccurred(@Nonnull Event e) {
if (e == marker) {
latch.countDown();
eventBus.removeListener(this);
}
}
});
eventBus.broadcast(marker);
if (!latch.await(1, MINUTES)) fail();
}
private static class MarkerEvent extends Event {
}
} }

View File

@@ -194,7 +194,7 @@ abstract class AbstractProtocolEngineTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(messageEncoder).encodeMetadata(type, privateGroupId, oneOf(messageEncoder).encodeMetadata(type, privateGroupId,
message.getTimestamp(), true, true, visible, false, false, message.getTimestamp(), true, true, visible, false, false,
NO_AUTO_DELETE_TIMER); NO_AUTO_DELETE_TIMER, false);
will(returnValue(meta)); will(returnValue(meta));
oneOf(clientHelper).addLocalMessage(txn, message, meta, true, oneOf(clientHelper).addLocalMessage(txn, message, meta, true,
false); false);

View File

@@ -0,0 +1,456 @@
package org.briarproject.briar.privategroup.invitation;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.conversation.ConversationManager.ConversationClient;
import org.briarproject.briar.api.privategroup.GroupMessage;
import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRequest;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
import org.briarproject.briar.autodelete.AbstractAutoDeleteTest;
import org.briarproject.briar.test.BriarIntegrationTestComponent;
import org.junit.Before;
import org.junit.Test;
import javax.annotation.Nullable;
import static org.briarproject.bramble.api.cleanup.CleanupManager.BATCH_DELAY_MS;
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.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class AutoDeleteIntegrationTest extends AbstractAutoDeleteTest {
private PrivateGroup privateGroup;
private PrivateGroupManager groupManager0;
private GroupInvitationManager groupInvitationManager0,
groupInvitationManager1;
@Before
@Override
public void setUp() throws Exception {
super.setUp();
groupManager0 = c0.getPrivateGroupManager();
groupInvitationManager0 = c0.getGroupInvitationManager();
groupInvitationManager1 = c1.getGroupInvitationManager();
privateGroup = addPrivateGroup("Testgroup", startTime);
}
@Override
protected ConversationClient getConversationClient(
BriarIntegrationTestComponent component) {
return component.getGroupInvitationManager();
}
@Test
public void testInvitationAutoDecline() throws Exception {
setAutoDeleteTimer(c0, contact1From0.getId(), MIN_AUTO_DELETE_TIMER_MS);
// Send invitation
sendInvitation(privateGroup, contact1From0.getId(), "Join this!");
// The message should have been added to 0's view of the conversation
assertGroupCount(c0, contactId1From0, 1, 0);
forEachHeader(c0, contactId1From0, 1, h -> {
// The message should have the expected timer
assertEquals(MIN_AUTO_DELETE_TIMER_MS, h.getAutoDeleteTimer());
});
// Sync the message to 1
sync0To1(1, true);
// Sync the ack to 0 - this starts 0's timer
ack1To0(1);
waitForEvents(c0);
// The message should have been added to 1's view of the conversation
assertGroupCount(c1, contactId0From1, 1, 1);
forEachHeader(c1, contactId0From1, 1, h -> {
// The message should have the expected timer
assertEquals(MIN_AUTO_DELETE_TIMER_MS, h.getAutoDeleteTimer());
});
// Both peers should be using the new timer
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
getAutoDeleteTimer(c0, contactId1From0));
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
getAutoDeleteTimer(c1, contactId0From1));
// Before 0's timer elapses, both peers should still see the message
long timerLatency = MIN_AUTO_DELETE_TIMER_MS + BATCH_DELAY_MS;
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
assertGroupCount(c0, contactId1From0, 1, 0);
assertEquals(1, getMessageHeaders(c0, contactId1From0).size());
assertGroupCount(c1, contactId0From1, 1, 1);
assertEquals(1, getMessageHeaders(c1, contactId0From1).size());
// When 0's timer has elapsed, the message should be deleted from 0's
// view of the conversation but 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
assertGroupCount(c0, contactId1From0, 0, 0);
assertEquals(0, getMessageHeaders(c0, contactId1From0).size());
assertGroupCount(c1, contactId0From1, 1, 1);
assertEquals(1, getMessageHeaders(c1, contactId0From1).size());
// 1 marks the message as read - this starts 1's timer
final MessageId messageId0 =
getMessageHeaders(c1, contactId0From1).get(0).getId();
markMessageRead(c1, contact0From1, messageId0);
assertGroupCount(c1, contactId0From1, 1, 0);
// Before 1's timer elapses, 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
assertGroupCount(c0, contactId1From0, 0, 0);
assertEquals(0, getMessageHeaders(c0, contactId1From0).size());
assertGroupCount(c1, contactId0From1, 1, 0);
assertEquals(1, getMessageHeaders(c1, contactId0From1).size());
// When 1's timer has elapsed, the message should be deleted from 1's
// view of the conversation and the invitation auto-declined
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
assertGroupCount(c0, contactId1From0, 0, 0);
assertEquals(0, getMessageHeaders(c0, contactId1From0).size());
assertGroupCount(c1, contactId0From1, 1, 0);
forEachHeader(c1, contactId0From1, 1, h -> {
// The only message is not the same as before, but declined response
assertNotEquals(messageId0, h.getId());
assertTrue(h instanceof GroupInvitationResponse);
assertFalse(((GroupInvitationResponse) h).wasAccepted());
assertTrue(((GroupInvitationResponse) h).isAutoDecline());
// The auto-decline message should have the expected timer
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
h.getAutoDeleteTimer());
});
// Sync the auto-decline message to 0
sync1To0(1, true);
// Sync the ack to 1 - this starts 1's timer
ack0To1(1);
waitForEvents(c1);
// 0 can invite 1 again
assertTrue(groupInvitationManager0
.isInvitationAllowed(contact1From0, privateGroup.getId()));
// Before 1's timer elapses, 1 should still see the auto-decline message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
assertGroupCount(c0, contactId1From0, 1, 1);
assertEquals(1, getMessageHeaders(c0, contactId1From0).size());
assertGroupCount(c1, contactId0From1, 1, 0);
assertEquals(1, getMessageHeaders(c1, contactId0From1).size());
// When 1's timer has elapsed, the auto-decline message should be
// deleted from 1's view of the conversation
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
assertGroupCount(c0, contactId1From0, 1, 1);
assertEquals(1, getMessageHeaders(c0, contactId1From0).size());
assertGroupCount(c1, contactId0From1, 0, 0);
assertEquals(0, getMessageHeaders(c1, contactId0From1).size());
// 0 marks the message as read - this starts 0's timer
MessageId messageId1 =
getMessageHeaders(c0, contactId1From0).get(0).getId();
markMessageRead(c0, contact1From0, messageId1);
assertGroupCount(c0, contactId1From0, 1, 0);
// Before 0's timer elapses, 0 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
assertGroupCount(c0, contactId1From0, 1, 0);
assertEquals(1, getMessageHeaders(c0, contactId1From0).size());
// When 0's timer has elapsed, the message should be deleted from 0's
// view of the conversation
c0.getTimeTravel().addCurrentTimeMillis(1);
assertGroupCount(c0, contactId1From0, 0, 0);
assertEquals(0, getMessageHeaders(c0, contactId1From0).size());
// 0 can invite 1 again and really does invite
assertTrue(groupInvitationManager0
.isInvitationAllowed(contact1From0, privateGroup.getId()));
sendInvitation(privateGroup, contact1From0.getId(),
"Join this faster please!");
sync0To1(1, true);
assertGroupCount(c1, contactId0From1, 1, 1);
}
@Test
public void testAutoDeleteDoesNotRemoveOtherSessions() throws Exception {
PrivateGroup pg = addPrivateGroup("Another one", startTime + 1);
// Send invitation for another group without timer
sendInvitation(pg, contact1From0.getId(), null);
sync0To1(1, true);
ack1To0(1);
waitForEvents(c0);
// The message should have been added the views of the conversation
assertGroupCount(c0, contactId1From0, 1, 0);
assertGroupCount(c1, contactId0From1, 1, 1);
// The message should have no timer for either peer
forEachHeader(c0, contactId1From0, 1, h ->
assertEquals(NO_AUTO_DELETE_TIMER, h.getAutoDeleteTimer()));
forEachHeader(c1, contactId0From1, 1, h ->
assertEquals(NO_AUTO_DELETE_TIMER, h.getAutoDeleteTimer()));
// enable timer
setAutoDeleteTimer(c0, contact1From0.getId(), MIN_AUTO_DELETE_TIMER_MS);
// Send invitation, ACK it and check group counts
sendInvitation(privateGroup, contact1From0.getId(), "Join this!");
sync0To1(1, true);
ack1To0(1);
waitForEvents(c0);
assertGroupCount(c0, contactId1From0, 2, 0);
assertGroupCount(c1, contactId0From1, 2, 2);
// Both peers should be using the new timer
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
getAutoDeleteTimer(c0, contactId1From0));
assertEquals(MIN_AUTO_DELETE_TIMER_MS,
getAutoDeleteTimer(c1, contactId0From1));
// Before 0's timer elapses, both peers should still see the message
long timerLatency = MIN_AUTO_DELETE_TIMER_MS + BATCH_DELAY_MS;
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
assertGroupCount(c0, contactId1From0, 2, 0);
assertEquals(2, getMessageHeaders(c0, contactId1From0).size());
assertGroupCount(c1, contactId0From1, 2, 2);
assertEquals(2, getMessageHeaders(c1, contactId0From1).size());
// When 0's timer has elapsed, the message should be deleted from 0's
// view of the conversation but 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
assertGroupCount(c0, contactId1From0, 1, 0);
assertEquals(1, getMessageHeaders(c0, contactId1From0).size());
assertGroupCount(c1, contactId0From1, 2, 2);
assertEquals(2, getMessageHeaders(c1, contactId0From1).size());
// 1 marks all the message as read - this starts 1's timer for 2nd msg
forEachHeader(c1, contactId0From1, 2, h -> {
try {
markMessageRead(c1, contact0From1, h.getId());
} catch (Exception e) {
fail();
}
});
assertGroupCount(c1, contactId0From1, 2, 0);
// Before 1's timer elapses, 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
assertGroupCount(c0, contactId1From0, 1, 0);
assertEquals(1, getMessageHeaders(c0, contactId1From0).size());
assertGroupCount(c1, contactId0From1, 2, 0);
assertEquals(2, getMessageHeaders(c1, contactId0From1).size());
// When 1's timer has elapsed, the message should be deleted from 1's
// view of the conversation and the invitation auto-declined
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
assertGroupCount(c0, contactId1From0, 1, 0);
assertEquals(1, getMessageHeaders(c0, contactId1From0).size());
// 1's total count is still 2, because of the added auto-decline
assertGroupCount(c1, contactId0From1, 2, 0);
forEachHeader(c1, contactId0From1, 2, h -> {
if (h instanceof GroupInvitationRequest) {
// the request is for the first invitation
assertEquals(pg.getId(),
((GroupInvitationRequest) h).getNameable().getId());
} else {
assertTrue(h instanceof GroupInvitationResponse);
GroupInvitationResponse r = (GroupInvitationResponse) h;
// is auto-decline for 2nd invitation
assertEquals(privateGroup.getId(), r.getShareableId());
assertTrue(r.isAutoDecline());
assertFalse(r.wasAccepted());
}
});
// Sync the auto-decline message to 0
sync1To0(1, true);
// Sync the ack to 1 - this starts 1's timer
ack0To1(1);
waitForEvents(c1);
// 0 marks the message as read - this starts 0's timer
GroupInvitationResponse autoDeclineMessage = (GroupInvitationResponse)
getMessageHeaders(c0, contactId1From0).get(1);
markMessageRead(c0, contact1From0, autoDeclineMessage.getId());
assertGroupCount(c0, contactId1From0, 2, 0);
assertGroupCount(c1, contactId0From1, 2, 0);
// Timer of auto-decline elapses for both peers at the same time
c0.getTimeTravel().addCurrentTimeMillis(timerLatency);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency);
assertGroupCount(c0, contactId1From0, 1, 0);
assertGroupCount(c1, contactId0From1, 1, 0);
// 1 responds to first invitation (that had no timer)
groupInvitationManager1.respondToInvitation(contactId0From1, pg, true);
// Sync the accept response message to 0
sync1To0(1, true);
// Sync the ack (and creator's join messages (group + protocol) to 1
// this starts 1's timer
sync0To1(2, true);
waitForEvents(c1);
assertGroupCount(c0, contactId1From0, 2, 1);
assertGroupCount(c1, contactId0From1, 2, 0);
forEachHeader(c1, contactId0From1, 2, h -> {
if (h instanceof GroupInvitationRequest) {
// the request is for the first invitation
assertEquals(pg.getId(),
((GroupInvitationRequest) h).getNameable().getId());
} else {
assertTrue(h instanceof GroupInvitationResponse);
GroupInvitationResponse r = (GroupInvitationResponse) h;
// is accept for 1nd invitation
assertEquals(pg.getId(), r.getShareableId());
assertFalse(r.isAutoDecline());
assertTrue(r.wasAccepted());
}
});
// Before 1's timer elapses, 1 should still see the message
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
assertGroupCount(c0, contactId1From0, 2, 1);
assertEquals(2, getMessageHeaders(c0, contactId1From0).size());
assertGroupCount(c1, contactId0From1, 2, 0);
assertEquals(2, getMessageHeaders(c1, contactId0From1).size());
// When 1's timer has elapsed, the message should be deleted from 1's
// view of the conversation
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
assertGroupCount(c0, contactId1From0, 2, 1);
assertEquals(2, getMessageHeaders(c0, contactId1From0).size());
assertGroupCount(c1, contactId0From1, 1, 0);
forEachHeader(c1, contactId0From1, 1, h -> {
assertTrue(h instanceof GroupInvitationRequest);
assertTrue(((GroupInvitationRequest) h).wasAnswered());
assertTrue(((GroupInvitationRequest) h).canBeOpened());
});
// 0 reads all messages
forEachHeader(c0, contactId1From0, 2, h -> {
try {
if (!h.isRead()) markMessageRead(c0, contact1From0, h.getId());
} catch (Exception e) {
fail();
}
});
assertGroupCount(c0, contactId1From0, 2, 0);
// Before 0's timer elapses, 0 should still see the messages
c0.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency - 1);
assertGroupCount(c0, contactId1From0, 2, 0);
assertGroupCount(c1, contactId0From1, 1, 0);
// When 0's timer has elapsed, the messages should be deleted from 0's
// view of the conversation, only the initial invitation remains
c0.getTimeTravel().addCurrentTimeMillis(1);
c1.getTimeTravel().addCurrentTimeMillis(1);
assertGroupCount(c0, contactId1From0, 1, 0);
assertEquals(1, getMessageHeaders(c0, contactId1From0).size());
assertGroupCount(c1, contactId0From1, 1, 0);
assertEquals(1, getMessageHeaders(c1, contactId0From1).size());
// 1 joined the PrivateGroup
assertEquals(pg,
c1.getPrivateGroupManager().getPrivateGroup(pg.getId()));
assertFalse(groupInvitationManager0
.isInvitationAllowed(contact1From0, pg.getId()));
}
@Test
public void testResponseAfterSenderDeletedInvitation() throws Exception {
setAutoDeleteTimer(c0, contact1From0.getId(), MIN_AUTO_DELETE_TIMER_MS);
// Send invitation
sendInvitation(privateGroup, contact1From0.getId(), "Join this!");
assertGroupCount(c0, contactId1From0, 1, 0);
// Sync the message to 1
sync0To1(1, true);
// Sync the ack to 0 - this starts 0's timer
ack1To0(1);
waitForEvents(c0);
assertGroupCount(c1, contactId0From1, 1, 1);
// When 0's timer has elapsed, the message should be deleted from 0's
// view of the conversation but 1 should still see the message
long timerLatency = MIN_AUTO_DELETE_TIMER_MS + BATCH_DELAY_MS;
c0.getTimeTravel().addCurrentTimeMillis(timerLatency);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency);
assertGroupCount(c0, contactId1From0, 0, 0);
assertEquals(0, getMessageHeaders(c0, contactId1From0).size());
assertGroupCount(c1, contactId0From1, 1, 1);
assertEquals(1, getMessageHeaders(c1, contactId0From1).size());
// 1 marks message as read - this starts 1's timer
markMessageRead(c1, contact0From1,
getMessageHeaders(c1, contactId0From1).get(0).getId());
// 1 responds to invitation
groupInvitationManager1
.respondToInvitation(contactId0From1, privateGroup, false);
// Sync the decline response message to 0
sync1To0(1, true);
// Sync the ack to 1 - this starts 1's timer
ack0To1(1);
waitForEvents(c1);
assertGroupCount(c0, contactId1From0, 1, 1);
assertGroupCount(c1, contactId0From1, 2, 0);
// 0 marks the message as read - this starts 0's timer
GroupInvitationResponse message1 = (GroupInvitationResponse)
getMessageHeaders(c0, contactId1From0).get(0);
markMessageRead(c0, contact1From0, message1.getId());
assertGroupCount(c0, contactId1From0, 1, 0);
assertGroupCount(c1, contactId0From1, 2, 0);
// both peers delete all messages after their timers expire
c0.getTimeTravel().addCurrentTimeMillis(timerLatency);
c1.getTimeTravel().addCurrentTimeMillis(timerLatency);
assertGroupCount(c0, contactId1From0, 0, 0);
assertEquals(0, getMessageHeaders(c0, contactId1From0).size());
assertGroupCount(c1, contactId0From1, 0, 0);
assertEquals(0, getMessageHeaders(c1, contactId0From1).size());
}
private PrivateGroup addPrivateGroup(String name, long timestamp)
throws DbException {
PrivateGroup pg = privateGroupFactory.createPrivateGroup(name, author0);
GroupMessage joinMsg0 = groupMessageFactory
.createJoinMessage(pg.getId(), timestamp, author0);
groupManager0.addPrivateGroup(pg, joinMsg0, true);
return pg;
}
private void sendInvitation(PrivateGroup pg, ContactId contactId,
@Nullable String text) throws DbException {
DatabaseComponent db0 = c0.getDatabaseComponent();
long timestamp = db0.transactionWithResult(true, txn ->
c0.getConversationManager()
.getTimestampForOutgoingMessage(txn, contactId));
byte[] signature = groupInvitationFactory.signInvitation(contact1From0,
pg.getId(), timestamp, author0.getPrivateKey());
long timer = getAutoDeleteTimer(c0, contactId, timestamp);
groupInvitationManager0.sendInvitation(pg.getId(), contactId, text,
timestamp, signature, timer);
}
}

View File

@@ -123,19 +123,19 @@ public class CreatorProtocolEngineTest extends AbstractProtocolEngineTest {
@Test @Test
public void testOnLeaveActionFromStart() throws Exception { public void testOnLeaveActionFromStart() throws Exception {
CreatorSession session = getDefaultSession(START); CreatorSession session = getDefaultSession(START);
assertEquals(session, engine.onLeaveAction(txn, session)); assertEquals(session, engine.onLeaveAction(txn, session, false));
} }
@Test @Test
public void testOnLeaveActionFromDissolved() throws Exception { public void testOnLeaveActionFromDissolved() throws Exception {
CreatorSession session = getDefaultSession(DISSOLVED); CreatorSession session = getDefaultSession(DISSOLVED);
assertEquals(session, engine.onLeaveAction(txn, session)); assertEquals(session, engine.onLeaveAction(txn, session, false));
} }
@Test @Test
public void testOnLeaveActionFromError() throws Exception { public void testOnLeaveActionFromError() throws Exception {
CreatorSession session = getDefaultSession(ERROR); CreatorSession session = getDefaultSession(ERROR);
assertEquals(session, engine.onLeaveAction(txn, session)); assertEquals(session, engine.onLeaveAction(txn, session, false));
} }
@Test @Test
@@ -143,7 +143,7 @@ public class CreatorProtocolEngineTest extends AbstractProtocolEngineTest {
CreatorSession session = getDefaultSession(INVITED); CreatorSession session = getDefaultSession(INVITED);
expectOnLocalLeave(); expectOnLocalLeave();
CreatorSession newSession = engine.onLeaveAction(txn, session); CreatorSession newSession = engine.onLeaveAction(txn, session, false);
assertEquals(DISSOLVED, newSession.getState()); assertEquals(DISSOLVED, newSession.getState());
assertEquals(messageId, newSession.getLastLocalMessageId()); assertEquals(messageId, newSession.getLastLocalMessageId());
assertEquals(lastRemoteMessageId, newSession.getLastRemoteMessageId()); assertEquals(lastRemoteMessageId, newSession.getLastRemoteMessageId());
@@ -157,7 +157,7 @@ public class CreatorProtocolEngineTest extends AbstractProtocolEngineTest {
CreatorSession session = getDefaultSession(JOINED); CreatorSession session = getDefaultSession(JOINED);
expectOnLocalLeave(); expectOnLocalLeave();
CreatorSession newSession = engine.onLeaveAction(txn, session); CreatorSession newSession = engine.onLeaveAction(txn, session, false);
assertEquals(DISSOLVED, newSession.getState()); assertEquals(DISSOLVED, newSession.getState());
assertEquals(messageId, newSession.getLastLocalMessageId()); assertEquals(messageId, newSession.getLastLocalMessageId());
assertEquals(lastRemoteMessageId, newSession.getLastRemoteMessageId()); assertEquals(lastRemoteMessageId, newSession.getLastRemoteMessageId());
@@ -171,7 +171,7 @@ public class CreatorProtocolEngineTest extends AbstractProtocolEngineTest {
CreatorSession session = getDefaultSession(LEFT); CreatorSession session = getDefaultSession(LEFT);
expectOnLocalLeave(); expectOnLocalLeave();
CreatorSession newSession = engine.onLeaveAction(txn, session); CreatorSession newSession = engine.onLeaveAction(txn, session, false);
assertEquals(DISSOLVED, newSession.getState()); assertEquals(DISSOLVED, newSession.getState());
assertEquals(messageId, newSession.getLastLocalMessageId()); assertEquals(messageId, newSession.getLastLocalMessageId());
assertEquals(lastRemoteMessageId, newSession.getLastRemoteMessageId()); assertEquals(lastRemoteMessageId, newSession.getLastRemoteMessageId());

View File

@@ -21,6 +21,7 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.TestUtils; import org.briarproject.bramble.test.TestUtils;
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;
@@ -329,10 +330,11 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(messageParser).parseMetadata(meta); oneOf(messageParser).parseMetadata(meta);
will(returnValue(messageMetadata)); will(returnValue(messageMetadata));
oneOf(messageMetadata).getAutoDeleteTimer();
will(returnValue(NO_AUTO_DELETE_TIMER));
oneOf(messageMetadata).getPrivateGroupId(); oneOf(messageMetadata).getPrivateGroupId();
will(returnValue(privateGroup.getId())); will(returnValue(privateGroup.getId()));
}}); }});
} }
private void expectIncomingMessage(Role role, MessageType type) private void expectIncomingMessage(Role role, MessageType type)
@@ -530,15 +532,13 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
public void testRespondToInvitationWithoutSession() throws Exception { public void testRespondToInvitationWithoutSession() throws Exception {
SessionId sessionId = new SessionId(getRandomId()); SessionId sessionId = new SessionId(getRandomId());
context.checking(new Expectations() {{ context.checking(new DbExpectations() {{
oneOf(db).startTransaction(false); oneOf(db).transaction(with(false), withDbRunnable(txn));
will(returnValue(txn));
oneOf(db).getContact(txn, contactId); oneOf(db).getContact(txn, contactId);
will(returnValue(contact)); will(returnValue(contact));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(db).endTransaction(txn);
}}); }});
expectGetSession(noResults, sessionId, contactGroup.getId()); expectGetSession(noResults, sessionId, contactGroup.getId());
@@ -582,9 +582,8 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
private void expectRespondToInvitation(SessionId sessionId, boolean accept) private void expectRespondToInvitation(SessionId sessionId, boolean accept)
throws Exception { throws Exception {
expectGetSession(oneResult, sessionId, contactGroup.getId()); expectGetSession(oneResult, sessionId, contactGroup.getId());
context.checking(new Expectations() {{ context.checking(new DbExpectations() {{
oneOf(db).startTransaction(false); oneOf(db).transaction(with(false), withDbRunnable(txn));
will(returnValue(txn));
oneOf(db).getContact(txn, contactId); oneOf(db).getContact(txn, contactId);
will(returnValue(contact)); will(returnValue(contact));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
@@ -594,10 +593,8 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
.parseInviteeSession(contactGroup.getId(), bdfSession); .parseInviteeSession(contactGroup.getId(), bdfSession);
will(returnValue(inviteeSession)); will(returnValue(inviteeSession));
if (accept) oneOf(inviteeEngine).onJoinAction(txn, inviteeSession); if (accept) oneOf(inviteeEngine).onJoinAction(txn, inviteeSession);
else oneOf(inviteeEngine).onLeaveAction(txn, inviteeSession); else oneOf(inviteeEngine).onLeaveAction(txn, inviteeSession, false);
will(returnValue(inviteeSession)); will(returnValue(inviteeSession));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}}); }});
expectStoreSession(inviteeSession, storageMessage.getId()); expectStoreSession(inviteeSession, storageMessage.getId());
} }
@@ -656,10 +653,10 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
long time1 = 1L, time2 = 2L; long time1 = 1L, time2 = 2L;
MessageMetadata messageMetadata1 = MessageMetadata messageMetadata1 =
new MessageMetadata(INVITE, privateGroup.getId(), time1, true, new MessageMetadata(INVITE, privateGroup.getId(), time1, true,
true, true, false, true, NO_AUTO_DELETE_TIMER); true, true, false, true, NO_AUTO_DELETE_TIMER, false);
MessageMetadata messageMetadata2 = MessageMetadata messageMetadata2 =
new MessageMetadata(JOIN, privateGroup.getId(), time2, true, new MessageMetadata(JOIN, privateGroup.getId(), time2, true,
true, true, true, false, NO_AUTO_DELETE_TIMER); true, true, true, false, NO_AUTO_DELETE_TIMER, false);
InviteMessage invite = InviteMessage invite =
new InviteMessage(message.getId(), contactGroup.getId(), new InviteMessage(message.getId(), contactGroup.getId(),
privateGroup.getId(), time1, "name", author, privateGroup.getId(), time1, "name", author,
@@ -879,7 +876,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
oneOf(sessionParser) oneOf(sessionParser)
.parseCreatorSession(contactGroup.getId(), bdfSession); .parseCreatorSession(contactGroup.getId(), bdfSession);
will(returnValue(creatorSession)); will(returnValue(creatorSession));
oneOf(creatorEngine).onLeaveAction(txn, creatorSession); oneOf(creatorEngine).onLeaveAction(txn, creatorSession, false);
will(returnValue(creatorSession)); will(returnValue(creatorSession));
// session 2 // session 2
oneOf(sessionParser).getRole(bdfSession2); oneOf(sessionParser).getRole(bdfSession2);
@@ -887,7 +884,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
oneOf(sessionParser) oneOf(sessionParser)
.parseInviteeSession(contactGroup2.getId(), bdfSession2); .parseInviteeSession(contactGroup2.getId(), bdfSession2);
will(returnValue(inviteeSession)); will(returnValue(inviteeSession));
oneOf(inviteeEngine).onLeaveAction(txn, inviteeSession); oneOf(inviteeEngine).onLeaveAction(txn, inviteeSession, false);
will(returnValue(inviteeSession)); will(returnValue(inviteeSession));
// session 3 // session 3
oneOf(sessionParser).getRole(bdfSession3); oneOf(sessionParser).getRole(bdfSession3);
@@ -895,7 +892,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
oneOf(sessionParser) oneOf(sessionParser)
.parsePeerSession(contactGroup3.getId(), bdfSession3); .parsePeerSession(contactGroup3.getId(), bdfSession3);
will(returnValue(peerSession)); will(returnValue(peerSession));
oneOf(peerEngine).onLeaveAction(txn, peerSession); oneOf(peerEngine).onLeaveAction(txn, peerSession, false);
will(returnValue(peerSession)); will(returnValue(peerSession));
}}); }});

View File

@@ -636,8 +636,7 @@ public class GroupInvitationValidatorTest extends ValidatorTestCase {
long autoDeleteTimer, BdfDictionary metadata) { long autoDeleteTimer, BdfDictionary metadata) {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(messageEncoder).encodeMetadata(type, message.getGroupId(), oneOf(messageEncoder).encodeMetadata(type, message.getGroupId(),
message.getTimestamp(), false, false, false, false, false, message.getTimestamp(), autoDeleteTimer);
autoDeleteTimer);
will(returnValue(metadata)); will(returnValue(metadata));
}}); }});
} }

View File

@@ -193,25 +193,25 @@ public class InviteeProtocolEngineTest extends AbstractProtocolEngineTest {
@Test @Test
public void testOnLeaveActionFromStart() throws Exception { public void testOnLeaveActionFromStart() throws Exception {
InviteeSession session = getDefaultSession(START); InviteeSession session = getDefaultSession(START);
assertEquals(session, engine.onLeaveAction(txn, session)); assertEquals(session, engine.onLeaveAction(txn, session, false));
} }
@Test @Test
public void testOnLeaveActionFromLeft() throws Exception { public void testOnLeaveActionFromLeft() throws Exception {
InviteeSession session = getDefaultSession(LEFT); InviteeSession session = getDefaultSession(LEFT);
assertEquals(session, engine.onLeaveAction(txn, session)); assertEquals(session, engine.onLeaveAction(txn, session, false));
} }
@Test @Test
public void testOnLeaveActionFromDissolved() throws Exception { public void testOnLeaveActionFromDissolved() throws Exception {
InviteeSession session = getDefaultSession(DISSOLVED); InviteeSession session = getDefaultSession(DISSOLVED);
assertEquals(session, engine.onLeaveAction(txn, session)); assertEquals(session, engine.onLeaveAction(txn, session, false));
} }
@Test @Test
public void testOnLeaveActionFromError() throws Exception { public void testOnLeaveActionFromError() throws Exception {
InviteeSession session = getDefaultSession(ERROR); InviteeSession session = getDefaultSession(ERROR);
assertEquals(session, engine.onLeaveAction(txn, session)); assertEquals(session, engine.onLeaveAction(txn, session, false));
} }
@Test @Test
@@ -223,7 +223,7 @@ public class InviteeProtocolEngineTest extends AbstractProtocolEngineTest {
}}); }});
InviteeSession session = getDefaultSession(INVITED); InviteeSession session = getDefaultSession(INVITED);
InviteeSession newSession = engine.onLeaveAction(txn, session); InviteeSession newSession = engine.onLeaveAction(txn, session, false);
assertEquals(START, newSession.getState()); assertEquals(START, newSession.getState());
assertSessionRecordedSentMessage(newSession); assertSessionRecordedSentMessage(newSession);
@@ -245,7 +245,7 @@ public class InviteeProtocolEngineTest extends AbstractProtocolEngineTest {
expectSendLeaveMessage(false); expectSendLeaveMessage(false);
expectSetPrivateGroupVisibility(INVISIBLE); expectSetPrivateGroupVisibility(INVISIBLE);
InviteeSession session = getDefaultSession(ACCEPTED); InviteeSession session = getDefaultSession(ACCEPTED);
InviteeSession newSession = engine.onLeaveAction(txn, session); InviteeSession newSession = engine.onLeaveAction(txn, session, false);
assertEquals(LEFT, newSession.getState()); assertEquals(LEFT, newSession.getState());
assertSessionRecordedSentMessage(newSession); assertSessionRecordedSentMessage(newSession);
@@ -257,7 +257,7 @@ public class InviteeProtocolEngineTest extends AbstractProtocolEngineTest {
expectSendLeaveMessage(false); expectSendLeaveMessage(false);
expectSetPrivateGroupVisibility(INVISIBLE); expectSetPrivateGroupVisibility(INVISIBLE);
InviteeSession session = getDefaultSession(JOINED); InviteeSession session = getDefaultSession(JOINED);
InviteeSession newSession = engine.onLeaveAction(txn, session); InviteeSession newSession = engine.onLeaveAction(txn, session, false);
assertEquals(LEFT, newSession.getState()); assertEquals(LEFT, newSession.getState());
assertSessionRecordedSentMessage(newSession); assertSessionRecordedSentMessage(newSession);

View File

@@ -145,31 +145,31 @@ public class PeerProtocolEngineTest extends AbstractProtocolEngineTest {
@Test @Test
public void testOnLeaveActionFromStart() throws Exception { public void testOnLeaveActionFromStart() throws Exception {
PeerSession session = getDefaultSession(START); PeerSession session = getDefaultSession(START);
assertEquals(session, engine.onLeaveAction(txn, session)); assertEquals(session, engine.onLeaveAction(txn, session, false));
} }
@Test @Test
public void testOnLeaveActionFromAwaitMember() throws Exception { public void testOnLeaveActionFromAwaitMember() throws Exception {
PeerSession session = getDefaultSession(AWAIT_MEMBER); PeerSession session = getDefaultSession(AWAIT_MEMBER);
assertEquals(session, engine.onLeaveAction(txn, session)); assertEquals(session, engine.onLeaveAction(txn, session, false));
} }
@Test @Test
public void testOnLeaveActionFromNeitherJoined() throws Exception { public void testOnLeaveActionFromNeitherJoined() throws Exception {
PeerSession session = getDefaultSession(NEITHER_JOINED); PeerSession session = getDefaultSession(NEITHER_JOINED);
assertEquals(session, engine.onLeaveAction(txn, session)); assertEquals(session, engine.onLeaveAction(txn, session, false));
} }
@Test @Test
public void testOnLeaveActionFromLocalLeft() throws Exception { public void testOnLeaveActionFromLocalLeft() throws Exception {
PeerSession session = getDefaultSession(LOCAL_LEFT); PeerSession session = getDefaultSession(LOCAL_LEFT);
assertEquals(session, engine.onLeaveAction(txn, session)); assertEquals(session, engine.onLeaveAction(txn, session, false));
} }
@Test @Test
public void testOnLeaveActionFromError() throws Exception { public void testOnLeaveActionFromError() throws Exception {
PeerSession session = getDefaultSession(ERROR); PeerSession session = getDefaultSession(ERROR);
assertEquals(session, engine.onLeaveAction(txn, session)); assertEquals(session, engine.onLeaveAction(txn, session, false));
} }
@Test @Test
@@ -178,7 +178,7 @@ public class PeerProtocolEngineTest extends AbstractProtocolEngineTest {
expectSendLeaveMessage(false); expectSendLeaveMessage(false);
expectSetPrivateGroupVisibility(INVISIBLE); expectSetPrivateGroupVisibility(INVISIBLE);
PeerSession newSession = engine.onLeaveAction(txn, session); PeerSession newSession = engine.onLeaveAction(txn, session, false);
assertEquals(NEITHER_JOINED, newSession.getState()); assertEquals(NEITHER_JOINED, newSession.getState());
assertSessionRecordedSentMessage(newSession); assertSessionRecordedSentMessage(newSession);
@@ -191,7 +191,7 @@ public class PeerProtocolEngineTest extends AbstractProtocolEngineTest {
expectSendLeaveMessage(false); expectSendLeaveMessage(false);
expectSetPrivateGroupVisibility(INVISIBLE); expectSetPrivateGroupVisibility(INVISIBLE);
PeerSession newSession = engine.onLeaveAction(txn, session); PeerSession newSession = engine.onLeaveAction(txn, session, false);
assertEquals(LOCAL_LEFT, newSession.getState()); assertEquals(LOCAL_LEFT, newSession.getState());
assertSessionRecordedSentMessage(newSession); assertSessionRecordedSentMessage(newSession);

View File

@@ -28,6 +28,7 @@ import org.briarproject.briar.api.introduction.IntroductionManager;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessageFactory; import org.briarproject.briar.api.messaging.PrivateMessageFactory;
import org.briarproject.briar.api.privategroup.PrivateGroupManager; import org.briarproject.briar.api.privategroup.PrivateGroupManager;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager; import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
import org.briarproject.briar.attachment.AttachmentModule; import org.briarproject.briar.attachment.AttachmentModule;
import org.briarproject.briar.autodelete.AutoDeleteModule; import org.briarproject.briar.autodelete.AutoDeleteModule;
@@ -120,6 +121,8 @@ public interface BriarIntegrationTestComponent
GroupInvitationManager getGroupInvitationManager(); GroupInvitationManager getGroupInvitationManager();
GroupInvitationFactory getGroupInvitationFactory();
IntroductionManager getIntroductionManager(); IntroductionManager getIntroductionManager();
MessageTracker getMessageTracker(); MessageTracker getMessageTracker();

View File

@@ -51,7 +51,7 @@ public class BriarTestUtils {
byte[] linkBytes = new byte[RAW_LINK_BYTES]; byte[] linkBytes = new byte[RAW_LINK_BYTES];
byte[] publicKey = keyPair.getPublic().getEncoded(); byte[] publicKey = keyPair.getPublic().getEncoded();
linkBytes[0] = FORMAT_VERSION; linkBytes[0] = FORMAT_VERSION;
arraycopy(publicKey,0, linkBytes, 1, RAW_LINK_BYTES - 1); arraycopy(publicKey, 0, linkBytes, 1, RAW_LINK_BYTES - 1);
return ("briar://" + Base32.encode(linkBytes)).toLowerCase(); return ("briar://" + Base32.encode(linkBytes)).toLowerCase();
} }