mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-16 12:49:55 +01:00
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:
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
|||||||
@@ -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));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
|
|||||||
@@ -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));
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user