Merge branch '475-new-sharing-client' into 'master'

New Forum Sharing Client

This is very similar to how the private group invitations work and I am sure there's still some tiny bugs that I didn't catch.

All existing integration tests either pass or have been modified to pass.

Once this has been merged, the code should be usable for blog sharing as well.

Closes #475

See merge request !467
This commit is contained in:
Torsten Grote
2017-01-03 19:30:48 +00:00
78 changed files with 3809 additions and 1937 deletions

View File

@@ -1020,13 +1020,13 @@ public class ConversationActivity extends BriarActivity
@DatabaseExecutor
private void respondToForumRequest(SessionId id, boolean accept)
throws DbException {
forumSharingManager.respondToInvitation(id, accept);
forumSharingManager.respondToInvitation(contactId, id, accept);
}
@DatabaseExecutor
private void respondToBlogRequest(SessionId id, boolean accept)
throws DbException {
blogSharingManager.respondToInvitation(id, accept);
blogSharingManager.respondToInvitation(contactId, id, accept);
}
@DatabaseExecutor

View File

@@ -170,8 +170,7 @@ abstract class ConversationItem {
} else if (ir instanceof GroupInvitationRequest) {
text = ctx.getString(
R.string.groups_invitations_invitation_sent,
contactName,
((GroupInvitationRequest) ir).getGroupName());
contactName, ir.getShareable().getName());
} else {
throw new IllegalArgumentException("Unknown InvitationRequest");
}
@@ -194,8 +193,7 @@ abstract class ConversationItem {
} else if (ir instanceof GroupInvitationRequest) {
text = ctx.getString(
R.string.groups_invitations_invitation_received,
contactName,
((GroupInvitationRequest) ir).getGroupName());
contactName, ir.getShareable().getName());
type = GROUP;
} else {
throw new IllegalArgumentException("Unknown InvitationRequest");
@@ -203,7 +201,7 @@ abstract class ConversationItem {
return new ConversationRequestItem(ir.getId(),
ir.getGroupId(), type, ir.getSessionId(), text,
ir.getMessage(), ir.getTimestamp(), ir.isRead(),
ir.getInvitedGroupId(), !ir.isAvailable(),
ir.getShareable().getId(), !ir.isAvailable(),
ir.canBeOpened());
}
}

View File

@@ -10,9 +10,11 @@ import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.messaging.ConversationManager;
import java.util.Collection;
import java.util.concurrent.Executor;
@@ -22,6 +24,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable
@NotNullByDefault
@@ -31,14 +34,19 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
private final static Logger LOG =
Logger.getLogger(ShareBlogControllerImpl.class.getName());
private final ConversationManager conversationManager;
private final BlogSharingManager blogSharingManager;
private final Clock clock;
@Inject
ShareBlogControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager,
BlogSharingManager blogSharingManager) {
ConversationManager conversationManager,
BlogSharingManager blogSharingManager, Clock clock) {
super(dbExecutor, lifecycleManager, contactManager);
this.conversationManager = conversationManager;
this.blogSharingManager = blogSharingManager;
this.clock = clock;
}
@Override
@@ -48,15 +56,19 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
@Override
public void share(final GroupId g, final Collection<ContactId> contacts,
final String msg,
final String message,
final ExceptionHandler<DbException> handler) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
String msg = isNullOrEmpty(message) ? null : message;
for (ContactId c : contacts) {
try {
blogSharingManager.sendInvitation(g, c, msg);
long time = Math.max(clock.currentTimeMillis(),
conversationManager.getGroupCount(c)
.getLatestMsgTime() + 1);
blogSharingManager.sendInvitation(g, c, msg, time);
} catch (NoSuchContactException | NoSuchGroupException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);

View File

@@ -10,9 +10,11 @@ import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.messaging.ConversationManager;
import java.util.Collection;
import java.util.concurrent.Executor;
@@ -22,6 +24,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable
@NotNullByDefault
@@ -31,14 +34,19 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
private final static Logger LOG =
Logger.getLogger(ShareForumControllerImpl.class.getName());
private final ConversationManager conversationManager;
private final ForumSharingManager forumSharingManager;
private final Clock clock;
@Inject
ShareForumControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager,
ForumSharingManager forumSharingManager) {
ConversationManager conversationManager,
ForumSharingManager forumSharingManager, Clock clock) {
super(dbExecutor, lifecycleManager, contactManager);
this.conversationManager = conversationManager;
this.forumSharingManager = forumSharingManager;
this.clock = clock;
}
@Override
@@ -48,15 +56,19 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
@Override
public void share(final GroupId g, final Collection<ContactId> contacts,
final String msg,
final String message,
final ExceptionHandler<DbException> handler) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
String msg = isNullOrEmpty(message) ? null : message;
for (ContactId c : contacts) {
try {
forumSharingManager.sendInvitation(g, c, msg);
long time = Math.max(clock.currentTimeMillis(),
conversationManager.getGroupCount(c)
.getLatestMsgTime() + 1);
forumSharingManager.sendInvitation(g, c, msg, time);
} catch (NoSuchContactException | NoSuchGroupException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);

View File

@@ -1,7 +1,10 @@
package org.briarproject.briar.api.blog;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.SessionId;
@@ -10,7 +13,7 @@ import org.briarproject.briar.api.sharing.InvitationRequest;
import javax.annotation.Nullable;
@NotNullByDefault
public class BlogInvitationRequest extends InvitationRequest {
public class BlogInvitationRequest extends InvitationRequest<Blog> {
private final String blogAuthorName;
@@ -19,9 +22,12 @@ public class BlogInvitationRequest extends InvitationRequest {
@Nullable String message, GroupId blogId,
boolean available, boolean canBeOpened, long time,
boolean local, boolean sent, boolean seen, boolean read) {
super(id, sessionId, groupId, contactId, message, blogId, available,
canBeOpened, time, local, sent, seen, read);
// TODO pass a proper blog here when redoing the BlogSharingManager
super(id, groupId, time, local, sent, seen, read, sessionId,
new Blog(new Group(blogId, BlogManager.CLIENT_ID, new byte[0]),
new Author(new AuthorId(new byte[AuthorId.LENGTH]),
blogAuthorName, new byte[0])), contactId,
message, available, canBeOpened);
this.blogAuthorName = blogAuthorName;
}

View File

@@ -14,9 +14,8 @@ public class BlogInvitationResponse extends InvitationResponse {
GroupId groupId, ContactId contactId, GroupId blogId,
boolean accept, long time, boolean local, boolean sent,
boolean seen, boolean read) {
super(id, sessionId, groupId, contactId, blogId, accept, time, local,
sent, seen, read);
super(id, groupId, time, local, sent, seen, read, sessionId, blogId,
contactId, accept);
}
}

View File

@@ -12,23 +12,18 @@ import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class ForumInvitationRequest extends InvitationRequest {
public class ForumInvitationRequest extends InvitationRequest<Forum> {
private final String forumName;
public ForumInvitationRequest(MessageId id, SessionId sessionId,
GroupId groupId, ContactId contactId, GroupId forumId,
String forumName, @Nullable String message, boolean available,
boolean canBeOpened, long time, boolean local, boolean sent,
boolean seen, boolean read) {
super(id, sessionId, groupId, contactId, message, forumId, available,
canBeOpened, time, local, sent, seen, read);
this.forumName = forumName;
public ForumInvitationRequest(MessageId id, GroupId groupId, long time,
boolean local, boolean sent, boolean seen, boolean read,
SessionId sessionId, Forum forum, ContactId contactId,
@Nullable String message, boolean available, boolean canBeOpened) {
super(id, groupId, time, local, sent, seen, read, sessionId, forum,
contactId, message, available, canBeOpened);
}
public String getForumName() {
return forumName;
return getShareable().getName();
}
}

View File

@@ -13,13 +13,12 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault
public class ForumInvitationResponse extends InvitationResponse {
public ForumInvitationResponse(MessageId id, SessionId sessionId,
GroupId groupId, ContactId contactId, GroupId forumId,
boolean accept, long time, boolean local, boolean sent,
boolean seen, boolean read) {
super(id, sessionId, groupId, contactId, forumId, accept, time, local,
sent, seen, read);
public ForumInvitationResponse(MessageId id, GroupId groupId, long time,
boolean local, boolean sent, boolean seen, boolean read,
SessionId sessionId, GroupId forumId, ContactId contactId,
boolean accept) {
super(id, groupId, time, local, sent, seen, read, sessionId, forumId,
contactId, accept);
}
}

View File

@@ -27,6 +27,11 @@ public interface ForumManager {
*/
Forum addForum(String name) throws DbException;
/**
* Subscribes to a forum within the given {@link Transaction}.
*/
void addForum(Transaction txn, Forum f) throws DbException;
/**
* Unsubscribes from a forum.
*/

View File

@@ -1,21 +1,20 @@
package org.briarproject.briar.api.forum.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.forum.ForumInvitationResponse;
import org.briarproject.briar.api.sharing.event.InvitationResponseReceivedEvent;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class ForumInvitationResponseReceivedEvent extends
InvitationResponseReceivedEvent {
private final String forumName;
public ForumInvitationResponseReceivedEvent(String forumName,
ContactId contactId, ForumInvitationResponse response) {
public ForumInvitationResponseReceivedEvent(ContactId contactId,
ForumInvitationResponse response) {
super(contactId, response);
this.forumName = forumName;
}
public String getForumName() {
return forumName;
}
}

View File

@@ -1,11 +1,11 @@
package org.briarproject.briar.api.privategroup.invitation;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.sharing.InvitationRequest;
import javax.annotation.Nullable;
@@ -13,28 +13,14 @@ import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class GroupInvitationRequest extends InvitationRequest {
public class GroupInvitationRequest extends InvitationRequest<PrivateGroup> {
private final String groupName;
private final Author creator;
public GroupInvitationRequest(MessageId id, SessionId sessionId,
GroupId groupId, ContactId contactId, @Nullable String message,
GroupId privateGroupId, String groupName, Author creator,
boolean available, boolean canBeOpened, long time,
boolean local, boolean sent, boolean seen, boolean read) {
super(id, sessionId, groupId, contactId, message, privateGroupId,
available, canBeOpened, time, local, sent, seen, read);
this.groupName = groupName;
this.creator = creator;
}
public String getGroupName() {
return groupName;
}
public Author getCreator() {
return creator;
public GroupInvitationRequest(MessageId id, GroupId groupId, long time,
boolean local, boolean sent, boolean seen, boolean read,
SessionId sessionId, PrivateGroup shareable, ContactId contactId,
@Nullable String message, boolean available, boolean canBeOpened) {
super(id, groupId, time, local, sent, seen, read, sessionId, shareable,
contactId, message, available, canBeOpened);
}
}

View File

@@ -13,11 +13,12 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault
public class GroupInvitationResponse extends InvitationResponse {
public GroupInvitationResponse(MessageId id, SessionId sessionId,
GroupId groupId, ContactId contactId, GroupId privateGroupId,
boolean accept, long time, boolean local, boolean sent,
boolean seen, boolean read) {
super(id, sessionId, groupId, contactId, privateGroupId, accept, time,
local, sent, seen, read);
public GroupInvitationResponse(MessageId id, GroupId groupId, long time,
boolean local, boolean sent, boolean seen, boolean read,
SessionId sessionId, GroupId shareableId, ContactId contactId,
boolean accept) {
super(id, groupId, time, local, sent, seen, read, sessionId,
shareableId, contactId, accept);
}
}

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.sync.GroupId;
@Deprecated
public interface InvitationFactory<I extends SharingMessage.Invitation> {
I build(GroupId groupId, BdfDictionary d) throws FormatException;

View File

@@ -7,25 +7,22 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.BaseMessageHeader;
import org.briarproject.briar.api.client.SessionId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public abstract class InvitationMessage extends BaseMessageHeader {
public class InvitationMessage extends BaseMessageHeader {
private final SessionId sessionId;
private final ContactId contactId;
private final GroupId invitedGroupId;
public InvitationMessage(MessageId id, SessionId sessionId, GroupId groupId,
ContactId contactId, GroupId invitedGroupId, long time,
boolean local, boolean sent, boolean seen, boolean read) {
public InvitationMessage(MessageId id, GroupId groupId, long time,
boolean local, boolean sent, boolean seen, boolean read,
SessionId sessionId, ContactId contactId) {
super(id, groupId, time, local, read, sent, seen);
this.sessionId = sessionId;
this.contactId = contactId;
this.invitedGroupId = invitedGroupId;
}
public SessionId getSessionId() {
@@ -36,9 +33,4 @@ public abstract class InvitationMessage extends BaseMessageHeader {
return contactId;
}
@Nullable
public GroupId getInvitedGroupId() {
return invitedGroupId;
}
}

View File

@@ -11,20 +11,19 @@ import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public abstract class InvitationRequest extends InvitationMessage {
public class InvitationRequest<S extends Shareable> extends InvitationMessage {
private final S shareable;
@Nullable
private final String message;
private final boolean available, canBeOpened;
public InvitationRequest(MessageId id, SessionId sessionId, GroupId groupId,
ContactId contactId, @Nullable String message,
GroupId invitedGroupId, boolean available,
boolean canBeOpened, long time, boolean local, boolean sent,
boolean seen, boolean read) {
super(id, sessionId, groupId, contactId, invitedGroupId, time, local,
sent, seen, read);
if (available && canBeOpened) throw new IllegalArgumentException();
public InvitationRequest(MessageId id, GroupId groupId, long time,
boolean local, boolean sent, boolean seen, boolean read,
SessionId sessionId, S shareable, ContactId contactId,
@Nullable String message, boolean available, boolean canBeOpened) {
super(id, groupId, time, local, sent, seen, read, sessionId, contactId);
this.shareable = shareable;
this.message = message;
this.available = available;
this.canBeOpened = canBeOpened;
@@ -43,4 +42,8 @@ public abstract class InvitationRequest extends InvitationMessage {
return canBeOpened;
}
public S getShareable() {
return shareable;
}
}

View File

@@ -10,21 +10,26 @@ import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public abstract class InvitationResponse extends InvitationMessage {
public class InvitationResponse extends InvitationMessage {
private final GroupId shareableId;
private final boolean accept;
public InvitationResponse(MessageId id, SessionId sessionId,
GroupId groupId, ContactId contactId,
GroupId invitedGroupId, boolean accept, long time,
boolean local, boolean sent, boolean seen, boolean read) {
super(id, sessionId, groupId, contactId, invitedGroupId, time, local,
sent, seen, read);
public InvitationResponse(MessageId id, GroupId groupId,
long time, boolean local, boolean sent, boolean seen,
boolean read, SessionId sessionId, GroupId shareableId,
ContactId contactId, boolean accept) {
super(id, groupId, time, local, sent, seen, read, sessionId, contactId);
this.shareableId = shareableId;
this.accept = accept;
}
public boolean wasAccepted() {
return accept;
}
public GroupId getShareableId() {
return shareableId;
}
}

View File

@@ -15,34 +15,63 @@ public interface SharingConstants {
*/
int MAX_INVITATION_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
@Deprecated
String CONTACT_ID = "contactId";
@Deprecated
String GROUP_ID = "groupId";
@Deprecated
String TO_BE_SHARED_BY_US = "toBeSharedByUs";
@Deprecated
String SHARED_BY_US = "sharedByUs";
@Deprecated
String SHARED_WITH_US = "sharedWithUs";
@Deprecated
String TYPE = "type";
@Deprecated
String SESSION_ID = "sessionId";
@Deprecated
String STORAGE_ID = "storageId";
@Deprecated
String STATE = "state";
@Deprecated
String LOCAL = "local";
@Deprecated
String TIME = "time";
@Deprecated
String IS_SHARER = "isSharer";
@Deprecated
String SHAREABLE_ID = "shareableId";
@Deprecated
String INVITATION_MSG = "invitationMsg";
@Deprecated
String INVITATION_ID = "invitationId";
@Deprecated
String RESPONSE_ID = "responseId";
@Deprecated
int SHARE_MSG_TYPE_INVITATION = 1;
@Deprecated
int SHARE_MSG_TYPE_ACCEPT = 2;
@Deprecated
int SHARE_MSG_TYPE_DECLINE = 3;
@Deprecated
int SHARE_MSG_TYPE_LEAVE = 4;
@Deprecated
int SHARE_MSG_TYPE_ABORT = 5;
@Deprecated
int TASK_ADD_SHAREABLE_TO_LIST_SHARED_WITH_US = 0;
@Deprecated
int TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US = 1;
@Deprecated
int TASK_ADD_SHARED_SHAREABLE = 2;
@Deprecated
int TASK_ADD_SHAREABLE_TO_LIST_TO_BE_SHARED_BY_US = 3;
@Deprecated
int TASK_REMOVE_SHAREABLE_FROM_LIST_TO_BE_SHARED_BY_US = 4;
@Deprecated
int TASK_SHARE_SHAREABLE = 5;
@Deprecated
int TASK_UNSHARE_SHAREABLE_SHARED_BY_US = 6;
@Deprecated
int TASK_UNSHARE_SHAREABLE_SHARED_WITH_US = 7;
}

View File

@@ -20,8 +20,8 @@ public interface SharingManager<S extends Shareable>
* Sends an invitation to share the given group with the given contact
* and sends an optional message along with it.
*/
void sendInvitation(GroupId groupId, ContactId contactId,
@Nullable String message) throws DbException;
void sendInvitation(GroupId shareableId, ContactId contactId,
@Nullable String message, long timestamp) throws DbException;
/**
* Responds to a pending group invitation
@@ -32,7 +32,7 @@ public interface SharingManager<S extends Shareable>
/**
* Responds to a pending group invitation
*/
void respondToInvitation(SessionId id, boolean accept)
void respondToInvitation(ContactId c, SessionId id, boolean accept)
throws DbException;
/**

View File

@@ -21,6 +21,7 @@ import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE
import static org.briarproject.briar.api.sharing.SharingConstants.TIME;
import static org.briarproject.briar.api.sharing.SharingConstants.TYPE;
@Deprecated
@NotNullByDefault
public interface SharingMessage {

View File

@@ -108,6 +108,11 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
return f;
}
@Override
public void addForum(Transaction txn, Forum f) throws DbException {
db.addGroup(txn, f.getGroup());
}
@Override
public void removeForum(Forum f) throws DbException {
Transaction txn = db.startTransaction(false);

View File

@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
@@ -177,7 +176,7 @@ abstract class AbstractProtocolEngine<S extends Session>
void subscribeToPrivateGroup(Transaction txn, MessageId inviteId)
throws DbException, FormatException {
InviteMessage invite = getInviteMessage(txn, inviteId);
InviteMessage invite = messageParser.getInviteMessage(txn, inviteId);
PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup(
invite.getGroupName(), invite.getCreator(), invite.getSalt());
long timestamp =
@@ -197,14 +196,6 @@ abstract class AbstractProtocolEngine<S extends Session>
session.getInviteTimestamp()) + 1);
}
private InviteMessage getInviteMessage(Transaction txn, MessageId m)
throws DbException, FormatException {
Message message = clientHelper.getMessage(txn, m);
if (message == null) throw new DbException();
BdfList body = clientHelper.toList(message);
return messageParser.parseInviteMessage(message, body);
}
private void sendMessage(Transaction txn, Message m, MessageType type,
GroupId privateGroupId, boolean visibleInConversation)
throws DbException {

View File

@@ -254,8 +254,8 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
private GroupInvitationResponse createInvitationResponse(
GroupInvitationMessage m, ContactId c, boolean accept) {
SessionId sessionId = new SessionId(m.getPrivateGroupId().getBytes());
return new GroupInvitationResponse(m.getId(), sessionId,
m.getContactGroupId(), c, m.getPrivateGroupId(), accept,
m.getTimestamp(), false, false, true, false);
return new GroupInvitationResponse(m.getId(), m.getContactGroupId(),
m.getTimestamp(), false, false, true, false, sessionId,
m.getPrivateGroupId(), c, accept);
}
}

View File

@@ -71,7 +71,6 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
private final ProtocolEngine<CreatorSession> creatorEngine;
private final ProtocolEngine<InviteeSession> inviteeEngine;
private final ProtocolEngine<PeerSession> peerEngine;
private final Group localGroup;
@Inject
GroupInvitationManagerImpl(DatabaseComponent db,
@@ -93,12 +92,10 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
creatorEngine = engineFactory.createCreatorEngine();
inviteeEngine = engineFactory.createInviteeEngine();
peerEngine = engineFactory.createPeerEngine();
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID);
}
@Override
public void createLocalState(Transaction txn) throws DbException {
db.addGroup(txn, localGroup);
// Ensure we've set things up for any pre-existing contacts
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
}
@@ -404,22 +401,15 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
throws DbException, FormatException {
SessionId sessionId = getSessionId(meta.getPrivateGroupId());
// Look up the invite message to get the details of the private group
InviteMessage invite = getInviteMessage(txn, m);
InviteMessage invite = messageParser.getInviteMessage(txn, m);
PrivateGroup pg = privateGroupFactory
.createPrivateGroup(invite.getGroupName(), invite.getCreator(),
invite.getSalt());
boolean canBeOpened = db.containsGroup(txn, invite.getPrivateGroupId());
return new GroupInvitationRequest(m, sessionId, contactGroupId, c,
invite.getMessage(), invite.getPrivateGroupId(),
invite.getGroupName(), invite.getCreator(),
meta.isAvailableToAnswer(), canBeOpened, meta.getTimestamp(),
meta.isLocal(), status.isSent(), status.isSeen(),
meta.isRead());
}
private InviteMessage getInviteMessage(Transaction txn, MessageId m)
throws DbException, FormatException {
Message message = clientHelper.getMessage(txn, m);
if (message == null) throw new DbException();
BdfList body = clientHelper.toList(message);
return messageParser.parseInviteMessage(message, body);
return new GroupInvitationRequest(m, contactGroupId,
meta.getTimestamp(), meta.isLocal(), status.isSent(),
status.isSeen(), meta.isRead(), sessionId, pg, c,
invite.getMessage(), meta.isAvailableToAnswer(), canBeOpened);
}
private GroupInvitationResponse parseInvitationResponse(ContactId c,
@@ -427,10 +417,10 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
MessageStatus status, boolean accept)
throws DbException, FormatException {
SessionId sessionId = getSessionId(meta.getPrivateGroupId());
return new GroupInvitationResponse(m, sessionId, contactGroupId, c,
meta.getPrivateGroupId(), accept, meta.getTimestamp(),
meta.isLocal(), status.isSent(), status.isSeen(),
meta.isRead());
return new GroupInvitationResponse(m, contactGroupId,
meta.getTimestamp(), meta.isLocal(), status.isSent(),
status.isSeen(), meta.isRead(), sessionId,
meta.getPrivateGroupId(), c, accept);
}
@Override
@@ -481,7 +471,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
private GroupInvitationItem parseGroupInvitationItem(Transaction txn,
Contact c, MessageId m) throws DbException, FormatException {
InviteMessage invite = getInviteMessage(txn, m);
InviteMessage invite = messageParser.getInviteMessage(txn, m);
PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup(
invite.getGroupName(), invite.getCreator(), invite.getSalt());
return new GroupInvitationItem(privateGroup, c);

View File

@@ -234,8 +234,9 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
// Broadcast an event
PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup(
m.getGroupName(), m.getCreator(), m.getSalt());
txn.attach(new GroupInvitationRequestReceivedEvent(privateGroup,
contactId, createInvitationRequest(m, contactId)));
txn.attach(
new GroupInvitationRequestReceivedEvent(privateGroup, contactId,
createInvitationRequest(m, privateGroup, contactId)));
// Move to the INVITED state
return new InviteeSession(s.getContactGroupId(), s.getPrivateGroupId(),
s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(),
@@ -317,11 +318,11 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
}
private GroupInvitationRequest createInvitationRequest(InviteMessage m,
ContactId c) {
PrivateGroup pg, ContactId c) {
SessionId sessionId = new SessionId(m.getPrivateGroupId().getBytes());
return new GroupInvitationRequest(m.getId(), sessionId,
m.getContactGroupId(), c, m.getMessage(), m.getPrivateGroupId(),
m.getGroupName(), m.getCreator(), true, false, m.getTimestamp(),
false, false, true, false);
return new GroupInvitationRequest(m.getId(), m.getContactGroupId(),
m.getTimestamp(), false, false, true, false, sessionId, pg, c,
m.getMessage(), true, false);
}
}

View File

@@ -3,9 +3,12 @@ package org.briarproject.briar.privategroup.invitation;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
@NotNullByDefault
interface MessageParser {
@@ -18,6 +21,9 @@ interface MessageParser {
MessageMetadata parseMetadata(BdfDictionary meta) throws FormatException;
InviteMessage getInviteMessage(Transaction txn, MessageId m)
throws DbException, FormatException;
InviteMessage parseInviteMessage(Message m, BdfList body)
throws FormatException;

View File

@@ -1,9 +1,12 @@
package org.briarproject.briar.privategroup.invitation;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -31,12 +34,14 @@ class MessageParserImpl implements MessageParser {
private final AuthorFactory authorFactory;
private final PrivateGroupFactory privateGroupFactory;
private final ClientHelper clientHelper;
@Inject
MessageParserImpl(AuthorFactory authorFactory,
PrivateGroupFactory privateGroupFactory) {
PrivateGroupFactory privateGroupFactory, ClientHelper clientHelper) {
this.authorFactory = authorFactory;
this.privateGroupFactory = privateGroupFactory;
this.clientHelper = clientHelper;
}
@Override
@@ -78,6 +83,15 @@ class MessageParserImpl implements MessageParser {
visible, available);
}
@Override
public InviteMessage getInviteMessage(Transaction txn, MessageId m)
throws DbException, FormatException {
Message message = clientHelper.getMessage(txn, m);
if (message == null) throw new DbException();
BdfList body = clientHelper.toList(message);
return parseInviteMessage(message, body);
}
@Override
public InviteMessage parseInviteMessage(Message m, BdfList body)
throws FormatException {

View File

@@ -0,0 +1,19 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class AbortMessage extends SharingMessage {
AbortMessage(MessageId id, GroupId contactGroupId, GroupId shareableId,
long timestamp, @Nullable MessageId previousMessageId) {
super(id, contactGroupId, shareableId, timestamp, previousMessageId);
}
}

View File

@@ -0,0 +1,19 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class AcceptMessage extends SharingMessage {
AcceptMessage(MessageId id, @Nullable MessageId previousMessageId,
GroupId contactGroupId, GroupId shareableId, long timestamp) {
super(id, contactGroupId, shareableId, timestamp, previousMessageId);
}
}

View File

@@ -52,7 +52,7 @@ import static org.briarproject.briar.api.sharing.SharingConstants.RESPONSE_ID;
@Immutable
@NotNullByDefault
class BlogSharingManagerImpl extends
SharingManagerImpl<Blog, BlogInvitation, BlogInviteeSessionState, BlogSharerSessionState, BlogInvitationRequestReceivedEvent, BlogInvitationResponseReceivedEvent>
OldSharingManagerImpl<Blog, BlogInvitation, BlogInviteeSessionState, BlogSharerSessionState, BlogInvitationRequestReceivedEvent, BlogInvitationResponseReceivedEvent>
implements BlogSharingManager, RemoveBlogHook {
private final ContactManager contactManager;
@@ -159,7 +159,7 @@ class BlogSharingManagerImpl extends
}
@Override
protected InvitationFactory<BlogInvitation, BlogSharerSessionState> getIFactory() {
protected OldInvitationFactory<BlogInvitation, BlogSharerSessionState> getIFactory() {
return iFactory;
}
@@ -251,7 +251,7 @@ class BlogSharingManagerImpl extends
}
private static class IFactory implements
InvitationFactory<BlogInvitation, BlogSharerSessionState> {
OldInvitationFactory<BlogInvitation, BlogSharerSessionState> {
@Override
public BlogInvitation build(GroupId groupId, BdfDictionary d)
throws FormatException {

View File

@@ -0,0 +1,20 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class DeclineMessage extends SharingMessage {
DeclineMessage(MessageId id, GroupId contactGroupId,
GroupId shareableId, long timestamp,
@Nullable MessageId previousMessageId) {
super(id, contactGroupId, shareableId, timestamp, previousMessageId);
}
}

View File

@@ -0,0 +1,39 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.forum.Forum;
import org.briarproject.briar.api.forum.ForumInvitationRequest;
import org.briarproject.briar.api.forum.ForumInvitationResponse;
import javax.inject.Inject;
public class ForumInvitationFactoryImpl implements InvitationFactory<Forum> {
@Inject
ForumInvitationFactoryImpl() {
}
@Override
public ForumInvitationRequest createInvitationRequest(boolean local,
boolean sent, boolean seen, boolean read, InviteMessage<Forum> m,
ContactId c, boolean available, boolean canBeOpened) {
SessionId sessionId = new SessionId(m.getShareableId().getBytes());
return new ForumInvitationRequest(m.getId(), m.getContactGroupId(),
m.getTimestamp(), local, sent, seen, read, sessionId,
m.getShareable(), c, m.getMessage(), available, canBeOpened);
}
@Override
public ForumInvitationResponse createInvitationResponse(MessageId id,
GroupId contactGroupId, long time, boolean local, boolean sent,
boolean seen, boolean read, GroupId shareableId,
ContactId contactId, boolean accept) {
SessionId sessionId = new SessionId(shareableId.getBytes());
return new ForumInvitationResponse(id, contactGroupId, time, local,
sent, seen, read, sessionId, shareableId, contactId, accept);
}
}

View File

@@ -1,47 +0,0 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.SessionId;
import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_NAME;
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT;
@NotThreadSafe
@NotNullByDefault
class ForumInviteeSessionState extends InviteeSessionState {
private final String forumName;
private final byte[] forumSalt;
ForumInviteeSessionState(SessionId sessionId, MessageId storageId,
GroupId groupId, State state, ContactId contactId, GroupId forumId,
String forumName, byte[] forumSalt, MessageId invitationId) {
super(sessionId, storageId, groupId, state, contactId, forumId,
invitationId);
this.forumName = forumName;
this.forumSalt = forumSalt;
}
@Override
public BdfDictionary toBdfDictionary() {
BdfDictionary d = super.toBdfDictionary();
d.put(FORUM_NAME, getForumName());
d.put(FORUM_SALT, getForumSalt());
return d;
}
String getForumName() {
return forumName;
}
byte[] getForumSalt() {
return forumSalt;
}
}

View File

@@ -0,0 +1,34 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.forum.Forum;
import org.briarproject.briar.api.forum.ForumFactory;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class ForumMessageParserImpl extends MessageParserImpl<Forum> {
private final ForumFactory forumFactory;
@Inject
ForumMessageParserImpl(ClientHelper clientHelper,
ForumFactory forumFactory) {
super(clientHelper);
this.forumFactory = forumFactory;
}
@Override
protected Forum createShareable(BdfList descriptor)
throws FormatException {
String name = descriptor.getString(0);
byte[] salt = descriptor.getRaw(1);
return forumFactory.createForum(name, salt);
}
}

View File

@@ -0,0 +1,93 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
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.db.Transaction;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.forum.Forum;
import org.briarproject.briar.api.forum.ForumInvitationRequest;
import org.briarproject.briar.api.forum.ForumInvitationResponse;
import org.briarproject.briar.api.forum.ForumManager;
import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.forum.event.ForumInvitationRequestReceivedEvent;
import org.briarproject.briar.api.forum.event.ForumInvitationResponseReceivedEvent;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class ForumProtocolEngineImpl extends ProtocolEngineImpl<Forum> {
private final ForumManager forumManager;
private final InvitationFactory<Forum> invitationFactory;
@Inject
ForumProtocolEngineImpl(DatabaseComponent db,
ClientHelper clientHelper, MessageEncoder messageEncoder,
MessageParser<Forum> messageParser, MessageTracker messageTracker,
Clock clock, ForumManager forumManager,
InvitationFactory<Forum> invitationFactory) {
super(db, clientHelper, messageEncoder, messageParser, messageTracker,
clock);
this.forumManager = forumManager;
this.invitationFactory = invitationFactory;
}
@Override
Event getInvitationRequestReceivedEvent(InviteMessage<Forum> m,
ContactId contactId, boolean available, boolean canBeOpened) {
ForumInvitationRequest request =
(ForumInvitationRequest) invitationFactory
.createInvitationRequest(false, false, true, false, m,
contactId, available, canBeOpened);
return new ForumInvitationRequestReceivedEvent(m.getShareable(),
contactId, request);
}
@Override
Event getInvitationResponseReceivedEvent(AcceptMessage m,
ContactId contactId) {
ForumInvitationResponse response =
(ForumInvitationResponse) invitationFactory
.createInvitationResponse(m.getId(),
m.getContactGroupId(), m.getTimestamp(), false,
false, true, false, m.getShareableId(),
contactId, true);
return new ForumInvitationResponseReceivedEvent(contactId, response);
}
@Override
Event getInvitationResponseReceivedEvent(DeclineMessage m,
ContactId contactId) {
ForumInvitationResponse response =
(ForumInvitationResponse) invitationFactory
.createInvitationResponse(m.getId(),
m.getContactGroupId(), m.getTimestamp(), false,
false, true, false, m.getShareableId(),
contactId, true);
return new ForumInvitationResponseReceivedEvent(contactId, response);
}
@Override
protected ClientId getClientId() {
return ForumSharingManager.CLIENT_ID;
}
@Override
protected void addShareable(Transaction txn, MessageId inviteId)
throws DbException, FormatException {
InviteMessage<Forum> invite =
messageParser.getInviteMessage(txn, inviteId);
forumManager.addForum(txn, invite.getShareable());
}
}

View File

@@ -1,49 +0,0 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.SessionId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_NAME;
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT;
@NotThreadSafe
@NotNullByDefault
class ForumSharerSessionState extends SharerSessionState {
private final String forumName;
private final byte[] forumSalt;
ForumSharerSessionState(SessionId sessionId, MessageId storageId,
GroupId groupId, State state, ContactId contactId, GroupId forumId,
String forumName, byte[] forumSalt,
@Nullable MessageId responseId) {
super(sessionId, storageId, groupId, state, contactId, forumId,
responseId);
this.forumName = forumName;
this.forumSalt = forumSalt;
}
@Override
public BdfDictionary toBdfDictionary() {
BdfDictionary d = super.toBdfDictionary();
d.put(FORUM_NAME, getForumName());
d.put(FORUM_SALT, getForumSalt());
return d;
}
String getForumName() {
return forumName;
}
byte[] getForumSalt() {
return forumSalt;
}
}

View File

@@ -1,78 +1,35 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.api.client.MessageQueueManager;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.forum.Forum;
import org.briarproject.briar.api.forum.ForumFactory;
import org.briarproject.briar.api.forum.ForumInvitationRequest;
import org.briarproject.briar.api.forum.ForumInvitationResponse;
import org.briarproject.briar.api.forum.ForumManager;
import org.briarproject.briar.api.forum.ForumManager.RemoveForumHook;
import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.forum.ForumSharingMessage.ForumInvitation;
import org.briarproject.briar.api.forum.event.ForumInvitationRequestReceivedEvent;
import org.briarproject.briar.api.forum.event.ForumInvitationResponseReceivedEvent;
import org.briarproject.briar.api.sharing.InvitationMessage;
import java.security.SecureRandom;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_NAME;
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT;
import static org.briarproject.briar.api.sharing.SharingConstants.INVITATION_ID;
import static org.briarproject.briar.api.sharing.SharingConstants.RESPONSE_ID;
@NotNullByDefault
class ForumSharingManagerImpl extends
SharingManagerImpl<Forum, ForumInvitation, ForumInviteeSessionState, ForumSharerSessionState, ForumInvitationRequestReceivedEvent, ForumInvitationResponseReceivedEvent>
class ForumSharingManagerImpl extends SharingManagerImpl<Forum>
implements ForumSharingManager, RemoveForumHook {
private final SFactory sFactory;
private final IFactory iFactory;
private final ISFactory isFactory;
private final SSFactory ssFactory;
private final IRFactory irFactory;
private final IRRFactory irrFactory;
@Inject
ForumSharingManagerImpl(ClientHelper clientHelper,
Clock clock, DatabaseComponent db,
ForumFactory forumFactory,
ForumManager forumManager,
MessageQueueManager messageQueueManager,
MetadataEncoder metadataEncoder,
MetadataParser metadataParser,
ForumSharingManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
MetadataParser metadataParser, MessageParser<Forum> messageParser,
SessionEncoder sessionEncoder, SessionParser sessionParser,
MessageTracker messageTracker,
ContactGroupFactory contactGroupFactory,
SecureRandom random, MessageTracker messageTracker) {
super(db, messageQueueManager, clientHelper, metadataParser,
metadataEncoder, random, contactGroupFactory, messageTracker,
clock);
sFactory = new SFactory(forumFactory, forumManager);
iFactory = new IFactory();
isFactory = new ISFactory();
ssFactory = new SSFactory();
irFactory = new IRFactory(sFactory);
irrFactory = new IRRFactory();
ProtocolEngine<Forum> engine,
InvitationFactory<Forum> invitationFactory) {
super(db, clientHelper, metadataParser, messageParser, sessionEncoder,
sessionParser, messageTracker, contactGroupFactory, engine,
invitationFactory);
}
@Override
@@ -80,221 +37,9 @@ class ForumSharingManagerImpl extends
return CLIENT_ID;
}
@Override
protected InvitationMessage createInvitationRequest(MessageId id,
ForumInvitation msg, ContactId contactId, GroupId forumId,
boolean available, boolean canBeOpened, long time,
boolean local, boolean sent, boolean seen, boolean read) {
return new ForumInvitationRequest(id, msg.getSessionId(),
msg.getGroupId(), contactId, forumId, msg.getForumName(),
msg.getMessage(), available, canBeOpened, time, local, sent,
seen, read);
}
@Override
protected InvitationMessage createInvitationResponse(MessageId id,
SessionId sessionId, GroupId groupId, ContactId contactId,
GroupId forumId, boolean accept, long time, boolean local,
boolean sent, boolean seen, boolean read) {
return new ForumInvitationResponse(id, sessionId, groupId, contactId,
forumId, accept, time, local, sent, seen, read);
}
@Override
protected ShareableFactory<Forum, ForumInvitation, ForumInviteeSessionState, ForumSharerSessionState> getSFactory() {
return sFactory;
}
@Override
protected InvitationFactory<ForumInvitation, ForumSharerSessionState> getIFactory() {
return iFactory;
}
@Override
protected InviteeSessionStateFactory<Forum, ForumInviteeSessionState> getISFactory() {
return isFactory;
}
@Override
protected SharerSessionStateFactory<Forum, ForumSharerSessionState> getSSFactory() {
return ssFactory;
}
@Override
protected InvitationReceivedEventFactory<ForumInviteeSessionState, ForumInvitationRequestReceivedEvent> getIRFactory() {
return irFactory;
}
@Override
protected InvitationResponseReceivedEventFactory<ForumSharerSessionState, ForumInvitationResponseReceivedEvent> getIRRFactory() {
return irrFactory;
}
@Override
public void removingForum(Transaction txn, Forum f) throws DbException {
removingShareable(txn, f);
}
private static class SFactory implements
ShareableFactory<Forum, ForumInvitation, ForumInviteeSessionState, ForumSharerSessionState> {
private final ForumFactory forumFactory;
private final ForumManager forumManager;
private SFactory(ForumFactory forumFactory, ForumManager forumManager) {
this.forumFactory = forumFactory;
this.forumManager = forumManager;
}
@Override
public BdfList encode(Forum f) {
return BdfList.of(f.getName(), f.getSalt());
}
@Override
public Forum get(Transaction txn, GroupId groupId)
throws DbException {
return forumManager.getForum(txn, groupId);
}
@Override
public Forum parse(BdfList shareable) throws FormatException {
return forumFactory
.createForum(shareable.getString(0), shareable.getRaw(1));
}
@Override
public Forum parse(ForumInvitation msg) {
return forumFactory
.createForum(msg.getForumName(), msg.getForumSalt());
}
@Override
public Forum parse(ForumInviteeSessionState state) {
return forumFactory
.createForum(state.getForumName(), state.getForumSalt());
}
@Override
public Forum parse(ForumSharerSessionState state) {
return forumFactory
.createForum(state.getForumName(), state.getForumSalt());
}
}
private static class IFactory implements
InvitationFactory<ForumInvitation, ForumSharerSessionState> {
@Override
public ForumInvitation build(GroupId groupId, BdfDictionary d)
throws FormatException {
return ForumInvitation.from(groupId, d);
}
@Override
public ForumInvitation build(ForumSharerSessionState localState,
long time) {
return new ForumInvitation(localState.getContactGroupId(),
localState.getSessionId(), localState.getForumName(),
localState.getForumSalt(), time, localState.getMessage());
}
}
private static class ISFactory implements
InviteeSessionStateFactory<Forum, ForumInviteeSessionState> {
@Override
public ForumInviteeSessionState build(SessionId sessionId,
MessageId storageId, GroupId groupId,
InviteeSessionState.State state, ContactId contactId,
GroupId forumId, BdfDictionary d) throws FormatException {
String forumName = d.getString(FORUM_NAME);
byte[] forumSalt = d.getRaw(FORUM_SALT);
MessageId invitationId = new MessageId(d.getRaw(INVITATION_ID));
return new ForumInviteeSessionState(sessionId, storageId,
groupId, state, contactId, forumId, forumName, forumSalt,
invitationId);
}
@Override
public ForumInviteeSessionState build(SessionId sessionId,
MessageId storageId, GroupId groupId,
InviteeSessionState.State state, ContactId contactId,
Forum forum, MessageId invitationId) {
return new ForumInviteeSessionState(sessionId, storageId,
groupId, state, contactId, forum.getId(), forum.getName(),
forum.getSalt(), invitationId);
}
}
private static class SSFactory implements
SharerSessionStateFactory<Forum, ForumSharerSessionState> {
@Override
public ForumSharerSessionState build(SessionId sessionId,
MessageId storageId, GroupId groupId,
SharerSessionState.State state, ContactId contactId,
GroupId forumId, BdfDictionary d) throws FormatException {
String forumName = d.getString(FORUM_NAME);
byte[] forumSalt = d.getRaw(FORUM_SALT);
MessageId responseId = null;
byte[] responseIdBytes = d.getOptionalRaw(RESPONSE_ID);
if (responseIdBytes != null)
responseId = new MessageId(responseIdBytes);
return new ForumSharerSessionState(sessionId, storageId,
groupId, state, contactId, forumId, forumName, forumSalt,
responseId);
}
@Override
public ForumSharerSessionState build(SessionId sessionId,
MessageId storageId, GroupId groupId,
SharerSessionState.State state, ContactId contactId,
Forum forum) {
return new ForumSharerSessionState(sessionId, storageId,
groupId, state, contactId, forum.getId(), forum.getName(),
forum.getSalt(), null);
}
}
private static class IRFactory implements
InvitationReceivedEventFactory<ForumInviteeSessionState, ForumInvitationRequestReceivedEvent> {
private final SFactory sFactory;
private IRFactory(SFactory sFactory) {
this.sFactory = sFactory;
}
@Override
public ForumInvitationRequestReceivedEvent build(
ForumInviteeSessionState localState, long time,
@Nullable String msg) {
Forum forum = sFactory.parse(localState);
ContactId contactId = localState.getContactId();
ForumInvitationRequest request = new ForumInvitationRequest(
localState.getInvitationId(), localState.getSessionId(),
localState.getContactGroupId(), contactId,
localState.getShareableId(), forum.getName(), msg, true,
false, time, false, false, false, false);
return new ForumInvitationRequestReceivedEvent(forum, contactId,
request);
}
}
private static class IRRFactory implements
InvitationResponseReceivedEventFactory<ForumSharerSessionState, ForumInvitationResponseReceivedEvent> {
@Override
public ForumInvitationResponseReceivedEvent build(
ForumSharerSessionState localState, boolean accept, long time) {
String name = localState.getForumName();
ContactId c = localState.getContactId();
MessageId responseId = localState.getResponseId();
if (responseId == null)
throw new IllegalStateException("No responseId");
ForumInvitationResponse response = new ForumInvitationResponse(
responseId, localState.getSessionId(),
localState.getContactGroupId(), localState.getContactId(),
localState.getShareableId(), accept, time, false, false,
false, false);
return new ForumInvitationResponseReceivedEvent(name, c, response);
}
}
}

View File

@@ -1,89 +1,47 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.BdfMessageContext;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.client.BdfQueueMessageValidator;
import org.briarproject.briar.api.forum.Forum;
import org.briarproject.briar.api.forum.ForumFactory;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_NAME;
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT;
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT_LENGTH;
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
import static org.briarproject.briar.api.sharing.SharingConstants.INVITATION_MSG;
import static org.briarproject.briar.api.sharing.SharingConstants.LOCAL;
import static org.briarproject.briar.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH;
import static org.briarproject.briar.api.sharing.SharingConstants.SESSION_ID;
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
import static org.briarproject.briar.api.sharing.SharingConstants.TIME;
import static org.briarproject.briar.api.sharing.SharingConstants.TYPE;
@Immutable
@NotNullByDefault
class ForumSharingValidator extends BdfQueueMessageValidator {
class ForumSharingValidator extends SharingValidator {
private final ForumFactory forumFactory;
@Inject
ForumSharingValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
super(clientHelper, metadataEncoder, clock);
ForumSharingValidator(MessageEncoder messageEncoder,
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
Clock clock, ForumFactory forumFactory) {
super(messageEncoder, clientHelper, metadataEncoder, clock);
this.forumFactory = forumFactory;
}
@Override
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException {
BdfDictionary d = new BdfDictionary();
long type = body.getLong(0);
byte[] id = body.getRaw(1);
checkLength(id, SessionId.LENGTH);
if (type == SHARE_MSG_TYPE_INVITATION) {
checkSize(body, 4, 5);
String name = body.getString(2);
checkLength(name, 1, MAX_FORUM_NAME_LENGTH);
byte[] salt = body.getRaw(3);
checkLength(salt, FORUM_SALT_LENGTH);
d.put(FORUM_NAME, name);
d.put(FORUM_SALT, salt);
if (body.size() > 4) {
String msg = body.getString(4);
checkLength(msg, 0, MAX_INVITATION_MESSAGE_LENGTH);
d.put(INVITATION_MSG, msg);
}
} else {
checkSize(body, 2);
if (type != SHARE_MSG_TYPE_ACCEPT &&
type != SHARE_MSG_TYPE_DECLINE &&
type != SHARE_MSG_TYPE_LEAVE &&
type != SHARE_MSG_TYPE_ABORT) {
throw new FormatException();
}
}
// Return the metadata
d.put(TYPE, type);
d.put(SESSION_ID, id);
d.put(LOCAL, false);
d.put(TIME, m.getTimestamp());
return new BdfMessageContext(d);
protected GroupId validateDescriptor(BdfList descriptor)
throws FormatException {
checkSize(descriptor, 2);
String name = descriptor.getString(0);
checkLength(name, 1, MAX_FORUM_NAME_LENGTH);
byte[] salt = descriptor.getRaw(1);
checkLength(salt, FORUM_SALT_LENGTH);
Forum forum = forumFactory.createForum(name, salt);
return forum.getId();
}
}

View File

@@ -1,11 +1,21 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.sharing.SharingMessage;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.sharing.InvitationRequest;
import org.briarproject.briar.api.sharing.InvitationResponse;
import org.briarproject.briar.api.sharing.Shareable;
@NotNullByDefault
interface InvitationFactory<I extends SharingMessage.Invitation, SS extends SharerSessionState>
extends org.briarproject.briar.api.sharing.InvitationFactory<I> {
public interface InvitationFactory<S extends Shareable> {
InvitationRequest<S> createInvitationRequest(boolean local, boolean sent,
boolean seen, boolean read, InviteMessage<S> m, ContactId c,
boolean available, boolean canBeOpened);
InvitationResponse createInvitationResponse(MessageId id,
GroupId contactGroupId, long time, boolean local, boolean sent,
boolean seen, boolean read, GroupId shareableId,
ContactId contactId, boolean accept);
I build(SS localState, long time);
}

View File

@@ -5,6 +5,7 @@ import org.briarproject.briar.api.sharing.event.InvitationRequestReceivedEvent;
import javax.annotation.Nullable;
@Deprecated
@NotNullByDefault
interface InvitationReceivedEventFactory<IS extends InviteeSessionState, IR extends InvitationRequestReceivedEvent> {

View File

@@ -3,6 +3,7 @@ package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.sharing.event.InvitationResponseReceivedEvent;
@Deprecated
@NotNullByDefault
interface InvitationResponseReceivedEventFactory<SS extends SharerSessionState, IRR extends InvitationResponseReceivedEvent> {

View File

@@ -0,0 +1,39 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.sharing.Shareable;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class InviteMessage<S extends Shareable> extends SharingMessage {
private final S shareable;
@Nullable
private final String message;
InviteMessage(MessageId id, @Nullable MessageId previousMessageId,
GroupId contactGroupId, S shareable, @Nullable String message,
long timestamp) {
super(id, contactGroupId, shareable.getId(), timestamp,
previousMessageId);
if (message != null && message.equals(""))
throw new IllegalArgumentException();
this.shareable = shareable;
this.message = message;
}
public S getShareable() {
return shareable;
}
@Nullable
public String getMessage() {
return message;
}
}

View File

@@ -28,6 +28,7 @@ import static org.briarproject.briar.api.sharing.SharingConstants.TASK_UNSHARE_S
import static org.briarproject.briar.api.sharing.SharingMessage.BaseMessage;
import static org.briarproject.briar.api.sharing.SharingMessage.SimpleMessage;
@Deprecated
@Immutable
@NotNullByDefault
class InviteeEngine<IS extends InviteeSessionState, IR extends InvitationRequestReceivedEvent>

View File

@@ -23,6 +23,7 @@ import static org.briarproject.briar.sharing.InviteeSessionState.Action.LOCAL_LE
import static org.briarproject.briar.sharing.InviteeSessionState.Action.REMOTE_INVITATION;
import static org.briarproject.briar.sharing.InviteeSessionState.Action.REMOTE_LEAVE;
@Deprecated
@NotThreadSafe
@NotNullByDefault
public abstract class InviteeSessionState extends SharingSessionState {

View File

@@ -8,6 +8,7 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.sharing.Shareable;
@Deprecated
interface InviteeSessionStateFactory<S extends Shareable, IS extends InviteeSessionState> {
IS build(SessionId sessionId, MessageId storageId, GroupId groupId,

View File

@@ -0,0 +1,20 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class LeaveMessage extends SharingMessage {
LeaveMessage(MessageId id, GroupId contactGroupId,
GroupId shareableId, long timestamp,
@Nullable MessageId previousMessageId) {
super(id, contactGroupId, shareableId, timestamp, previousMessageId);
}
}

View File

@@ -0,0 +1,39 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import javax.annotation.Nullable;
@NotNullByDefault
interface MessageEncoder {
BdfDictionary encodeMetadata(MessageType type, GroupId shareableId,
long timestamp, boolean local, boolean read, boolean visible,
boolean available);
void setVisibleInUi(BdfDictionary meta, boolean visible);
void setAvailableToAnswer(BdfDictionary meta, boolean available);
Message encodeInviteMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, BdfList descriptor,
@Nullable String message);
Message encodeAcceptMessage(GroupId contactGroupId, GroupId shareableId,
long timestamp, @Nullable MessageId previousMessageId);
Message encodeDeclineMessage(GroupId contactGroupId, GroupId shareableId,
long timestamp, @Nullable MessageId previousMessageId);
Message encodeLeaveMessage(GroupId contactGroupId, GroupId shareableId,
long timestamp, @Nullable MessageId previousMessageId);
Message encodeAbortMessage(GroupId contactGroupId, GroupId shareableId,
long timestamp, @Nullable MessageId previousMessageId);
}

View File

@@ -0,0 +1,137 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.MessageId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.briar.sharing.MessageType.ABORT;
import static org.briarproject.briar.sharing.MessageType.ACCEPT;
import static org.briarproject.briar.sharing.MessageType.DECLINE;
import static org.briarproject.briar.sharing.MessageType.INVITE;
import static org.briarproject.briar.sharing.MessageType.LEAVE;
import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_LOCAL;
import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_SHAREABLE_ID;
import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_READ;
import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_TIMESTAMP;
import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_VISIBLE_IN_UI;
@Immutable
@NotNullByDefault
class MessageEncoderImpl implements MessageEncoder {
private final ClientHelper clientHelper;
private final MessageFactory messageFactory;
@Inject
MessageEncoderImpl(ClientHelper clientHelper,
MessageFactory messageFactory) {
this.clientHelper = clientHelper;
this.messageFactory = messageFactory;
}
@Override
public BdfDictionary encodeMetadata(MessageType type,
GroupId shareableId, long timestamp, boolean local, boolean read,
boolean visible, boolean available) {
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_MESSAGE_TYPE, type.getValue());
meta.put(MSG_KEY_SHAREABLE_ID, shareableId);
meta.put(MSG_KEY_TIMESTAMP, timestamp);
meta.put(MSG_KEY_LOCAL, local);
meta.put(MSG_KEY_READ, read);
meta.put(MSG_KEY_VISIBLE_IN_UI, visible);
meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, available);
return meta;
}
@Override
public void setVisibleInUi(BdfDictionary meta, boolean visible) {
meta.put(MSG_KEY_VISIBLE_IN_UI, visible);
}
@Override
public void setAvailableToAnswer(BdfDictionary meta, boolean available) {
meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, available);
}
@Override
public Message encodeInviteMessage(GroupId contactGroupId, long timestamp,
@Nullable MessageId previousMessageId, BdfList descriptor,
@Nullable String message) {
if (message != null && message.equals(""))
throw new IllegalArgumentException();
BdfList body = BdfList.of(
INVITE.getValue(),
previousMessageId,
descriptor,
message
);
try {
return messageFactory.createMessage(contactGroupId, timestamp,
clientHelper.toByteArray(body));
} catch (FormatException e) {
throw new AssertionError(e);
}
}
@Override
public Message encodeAcceptMessage(GroupId contactGroupId,
GroupId shareableId, long timestamp,
@Nullable MessageId previousMessageId) {
return encodeMessage(ACCEPT, contactGroupId, shareableId, timestamp,
previousMessageId);
}
@Override
public Message encodeDeclineMessage(GroupId contactGroupId,
GroupId shareableId, long timestamp,
@Nullable MessageId previousMessageId) {
return encodeMessage(DECLINE, contactGroupId, shareableId, timestamp,
previousMessageId);
}
@Override
public Message encodeLeaveMessage(GroupId contactGroupId,
GroupId shareableId, long timestamp,
@Nullable MessageId previousMessageId) {
return encodeMessage(LEAVE, contactGroupId, shareableId, timestamp,
previousMessageId);
}
@Override
public Message encodeAbortMessage(GroupId contactGroupId,
GroupId shareableId, long timestamp,
@Nullable MessageId previousMessageId) {
return encodeMessage(ABORT, contactGroupId, shareableId, timestamp,
previousMessageId);
}
private Message encodeMessage(MessageType type, GroupId contactGroupId,
GroupId shareableId, long timestamp,
@Nullable MessageId previousMessageId) {
BdfList body = BdfList.of(
type.getValue(),
shareableId,
previousMessageId
);
try {
return messageFactory.createMessage(contactGroupId, timestamp,
clientHelper.toByteArray(body));
} catch (FormatException e) {
throw new AssertionError(e);
}
}
}

View File

@@ -0,0 +1,56 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class MessageMetadata {
private final MessageType type;
private final GroupId shareableId;
private final long timestamp;
private final boolean local, read, visible, available;
MessageMetadata(MessageType type, GroupId shareableId, long timestamp,
boolean local, boolean read, boolean visible, boolean available) {
this.shareableId = shareableId;
this.type = type;
this.timestamp = timestamp;
this.local = local;
this.read = read;
this.visible = visible;
this.available = available;
}
MessageType getMessageType() {
return type;
}
GroupId getShareableId() {
return shareableId;
}
long getTimestamp() {
return timestamp;
}
boolean isLocal() {
return local;
}
boolean isRead() {
return read;
}
boolean isVisibleInConversation() {
return visible;
}
boolean isAvailableToAnswer() {
return available;
}
}

View File

@@ -0,0 +1,43 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.sharing.Shareable;
@NotNullByDefault
interface MessageParser<S extends Shareable> {
BdfDictionary getMessagesVisibleInUiQuery();
BdfDictionary getInvitesAvailableToAnswerQuery();
BdfDictionary getInvitesAvailableToAnswerQuery(GroupId shareableId);
MessageMetadata parseMetadata(BdfDictionary meta) throws FormatException;
InviteMessage<S> getInviteMessage(Transaction txn, MessageId m)
throws DbException, FormatException;
InviteMessage<S> parseInviteMessage(Message m, BdfList body)
throws FormatException;
AcceptMessage parseAcceptMessage(Message m, BdfList body)
throws FormatException;
DeclineMessage parseDeclineMessage(Message m, BdfList body)
throws FormatException;
LeaveMessage parseLeaveMessage(Message m, BdfList body)
throws FormatException;
AbortMessage parseAbortMessage(Message m, BdfList body)
throws FormatException;
}

View File

@@ -0,0 +1,139 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.sharing.Shareable;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.sharing.MessageType.INVITE;
import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_LOCAL;
import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_READ;
import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_SHAREABLE_ID;
import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_TIMESTAMP;
import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_VISIBLE_IN_UI;
@Immutable
@NotNullByDefault
abstract class MessageParserImpl<S extends Shareable>
implements MessageParser<S> {
private final ClientHelper clientHelper;
MessageParserImpl(ClientHelper clientHelper) {
this.clientHelper = clientHelper;
}
@Override
public BdfDictionary getMessagesVisibleInUiQuery() {
return BdfDictionary.of(new BdfEntry(MSG_KEY_VISIBLE_IN_UI, true));
}
@Override
public BdfDictionary getInvitesAvailableToAnswerQuery() {
return BdfDictionary.of(
new BdfEntry(MSG_KEY_AVAILABLE_TO_ANSWER, true),
new BdfEntry(MSG_KEY_MESSAGE_TYPE, INVITE.getValue())
);
}
@Override
public BdfDictionary getInvitesAvailableToAnswerQuery(GroupId shareableId) {
return BdfDictionary.of(
new BdfEntry(MSG_KEY_AVAILABLE_TO_ANSWER, true),
new BdfEntry(MSG_KEY_MESSAGE_TYPE, INVITE.getValue()),
new BdfEntry(MSG_KEY_SHAREABLE_ID, shareableId)
);
}
@Override
public MessageMetadata parseMetadata(BdfDictionary meta)
throws FormatException {
MessageType type = MessageType
.fromValue(meta.getLong(MSG_KEY_MESSAGE_TYPE).intValue());
GroupId shareableId = new GroupId(meta.getRaw(MSG_KEY_SHAREABLE_ID));
long timestamp = meta.getLong(MSG_KEY_TIMESTAMP);
boolean local = meta.getBoolean(MSG_KEY_LOCAL);
boolean read = meta.getBoolean(MSG_KEY_READ, false);
boolean visible = meta.getBoolean(MSG_KEY_VISIBLE_IN_UI, false);
boolean available = meta.getBoolean(MSG_KEY_AVAILABLE_TO_ANSWER, false);
return new MessageMetadata(type, shareableId, timestamp, local, read,
visible, available);
}
@Override
public InviteMessage<S> getInviteMessage(Transaction txn, MessageId m)
throws DbException, FormatException {
Message message = clientHelper.getMessage(txn, m);
if (message == null) throw new DbException();
BdfList body = clientHelper.toList(message);
return parseInviteMessage(message, body);
}
@Override
public InviteMessage<S> parseInviteMessage(Message m, BdfList body)
throws FormatException {
byte[] b = body.getOptionalRaw(1);
MessageId previousMessageId = (b == null ? null : new MessageId(b));
BdfList descriptor = body.getList(2);
S shareable = createShareable(descriptor);
String message = body.getOptionalString(3);
return new InviteMessage<S>(m.getId(), previousMessageId,
m.getGroupId(), shareable, message, m.getTimestamp());
}
protected abstract S createShareable(BdfList descriptor)
throws FormatException;
@Override
public AcceptMessage parseAcceptMessage(Message m, BdfList body)
throws FormatException {
GroupId shareableId = new GroupId(body.getRaw(1));
byte[] b = body.getOptionalRaw(2);
MessageId previousMessageId = (b == null ? null : new MessageId(b));
return new AcceptMessage(m.getId(), previousMessageId, m.getGroupId(),
shareableId, m.getTimestamp());
}
@Override
public DeclineMessage parseDeclineMessage(Message m, BdfList body)
throws FormatException {
GroupId shareableId = new GroupId(body.getRaw(1));
byte[] b = body.getOptionalRaw(2);
MessageId previousMessageId = (b == null ? null : new MessageId(b));
return new DeclineMessage(m.getId(), m.getGroupId(), shareableId,
m.getTimestamp(), previousMessageId);
}
@Override
public LeaveMessage parseLeaveMessage(Message m, BdfList body)
throws FormatException {
GroupId shareableId = new GroupId(body.getRaw(1));
byte[] b = body.getOptionalRaw(2);
MessageId previousMessageId = (b == null ? null : new MessageId(b));
return new LeaveMessage(m.getId(), m.getGroupId(), shareableId,
m.getTimestamp(), previousMessageId);
}
@Override
public AbortMessage parseAbortMessage(Message m, BdfList body)
throws FormatException {
GroupId shareableId = new GroupId(body.getRaw(1));
byte[] b = body.getOptionalRaw(2);
MessageId previousMessageId = (b == null ? null : new MessageId(b));
return new AbortMessage(m.getId(), m.getGroupId(), shareableId,
m.getTimestamp(), previousMessageId);
}
}

View File

@@ -0,0 +1,29 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
enum MessageType {
INVITE(0), ACCEPT(1), DECLINE(2), LEAVE(3), ABORT(4);
private final int value;
MessageType(int value) {
this.value = value;
}
int getValue() {
return value;
}
static MessageType fromValue(int value) throws FormatException {
for (MessageType m : values()) if (m.value == value) return m;
throw new FormatException();
}
}

View File

@@ -0,0 +1,12 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.sharing.SharingMessage;
@Deprecated
@NotNullByDefault
interface OldInvitationFactory<I extends SharingMessage.Invitation, SS extends SharerSessionState>
extends org.briarproject.briar.api.sharing.InvitationFactory<I> {
I build(SS localState, long time);
}

View File

@@ -0,0 +1,39 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.sharing.Shareable;
import javax.annotation.Nullable;
@NotNullByDefault
interface ProtocolEngine<S extends Shareable> {
Session onInviteAction(Transaction txn, Session session,
@Nullable String message, long timestamp) throws DbException;
Session onAcceptAction(Transaction txn, Session session) throws DbException;
Session onDeclineAction(Transaction txn, Session session)
throws DbException;
Session onLeaveAction(Transaction txn, Session session) throws DbException;
Session onInviteMessage(Transaction txn, Session session,
InviteMessage<S> m) throws DbException, FormatException;
Session onAcceptMessage(Transaction txn, Session session, AcceptMessage m)
throws DbException, FormatException;
Session onDeclineMessage(Transaction txn, Session session, DeclineMessage m)
throws DbException, FormatException;
Session onLeaveMessage(Transaction txn, Session session, LeaveMessage m)
throws DbException, FormatException;
Session onAbortMessage(Transaction txn, Session session, AbortMessage m)
throws DbException, FormatException;
}

View File

@@ -0,0 +1,619 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Group.Visibility;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.sharing.Shareable;
import java.util.Map;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.briar.sharing.MessageType.ABORT;
import static org.briarproject.briar.sharing.MessageType.ACCEPT;
import static org.briarproject.briar.sharing.MessageType.DECLINE;
import static org.briarproject.briar.sharing.MessageType.INVITE;
import static org.briarproject.briar.sharing.MessageType.LEAVE;
import static org.briarproject.briar.sharing.SharingConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.briar.sharing.State.ERROR;
import static org.briarproject.briar.sharing.State.LOCAL_INVITED;
import static org.briarproject.briar.sharing.State.LOCAL_LEFT;
import static org.briarproject.briar.sharing.State.REMOTE_HANGING;
import static org.briarproject.briar.sharing.State.REMOTE_INVITED;
import static org.briarproject.briar.sharing.State.REMOTE_LEFT;
import static org.briarproject.briar.sharing.State.SHARING;
import static org.briarproject.briar.sharing.State.START;
@Immutable
@NotNullByDefault
abstract class ProtocolEngineImpl<S extends Shareable>
implements ProtocolEngine<S> {
protected final DatabaseComponent db;
protected final ClientHelper clientHelper;
protected final MessageParser<S> messageParser;
private final MessageEncoder messageEncoder;
private final MessageTracker messageTracker;
private final Clock clock;
ProtocolEngineImpl(DatabaseComponent db, ClientHelper clientHelper,
MessageEncoder messageEncoder, MessageParser<S> messageParser,
MessageTracker messageTracker, Clock clock) {
this.db = db;
this.clientHelper = clientHelper;
this.messageEncoder = messageEncoder;
this.messageParser = messageParser;
this.messageTracker = messageTracker;
this.clock = clock;
}
@Override
public Session onInviteAction(Transaction txn, Session s,
@Nullable String message, long timestamp) throws DbException {
switch (s.getState()) {
case START:
case REMOTE_LEFT:
return onLocalInvite(txn, s, message, timestamp);
case LOCAL_INVITED:
case REMOTE_INVITED:
case SHARING:
case LOCAL_LEFT:
case REMOTE_HANGING:
case ERROR:
throw new ProtocolStateException(); // Invalid in these states
default:
throw new AssertionError();
}
}
private Session onLocalInvite(Transaction txn, Session s,
@Nullable String message, long timestamp) throws DbException {
// Send an INVITE message
Message sent = sendInviteMessage(txn, s, message, timestamp);
// Track the message
messageTracker.trackOutgoingMessage(txn, sent);
// Make the shareable visible to the contact
try {
setShareableVisibility(txn, s, VISIBLE);
} catch (FormatException e) {
throw new DbException(e); // Invalid group metadata
}
// Move to the REMOTE_INVITED state
return new Session(REMOTE_INVITED, s.getContactGroupId(),
s.getShareableId(), sent.getId(), s.getLastRemoteMessageId(),
sent.getTimestamp(), s.getInviteTimestamp());
}
private Message sendInviteMessage(Transaction txn, Session s,
@Nullable String message, long timestamp) throws DbException {
Group g = db.getGroup(txn, s.getShareableId());
BdfList descriptor;
try {
descriptor = clientHelper.toList(g.getDescriptor());
} catch (FormatException e) {
throw new DbException(e); // Invalid group descriptor
}
long localTimestamp = Math.max(timestamp, getLocalTimestamp(s));
Message m = messageEncoder
.encodeInviteMessage(s.getContactGroupId(), localTimestamp,
s.getLastLocalMessageId(), descriptor, message);
sendMessage(txn, m, INVITE, s.getShareableId(), true);
return m;
}
@Override
public Session onAcceptAction(Transaction txn, Session s)
throws DbException {
switch (s.getState()) {
case LOCAL_INVITED:
return onLocalAccept(txn, s);
case START:
case REMOTE_INVITED:
case SHARING:
case LOCAL_LEFT:
case REMOTE_LEFT:
case REMOTE_HANGING:
case ERROR:
throw new ProtocolStateException(); // Invalid in these states
default:
throw new AssertionError();
}
}
private Session onLocalAccept(Transaction txn, Session s)
throws DbException {
// Mark the invite message unavailable to answer
MessageId inviteId = s.getLastRemoteMessageId();
if (inviteId == null) throw new IllegalStateException();
markMessageAvailableToAnswer(txn, inviteId, false);
// Send a ACCEPT message
Message sent = sendAcceptMessage(txn, s);
// Track the message
messageTracker.trackOutgoingMessage(txn, sent);
try {
// Add and subscribe to the shareable
addShareable(txn, inviteId);
// Share the shareable with the contact
setShareableVisibility(txn, s, SHARED);
} catch (FormatException e) {
throw new DbException(e); // Invalid group metadata
}
// Move to the SHARING state
return new Session(SHARING, s.getContactGroupId(), s.getShareableId(),
sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(),
s.getInviteTimestamp());
}
protected abstract void addShareable(Transaction txn, MessageId inviteId)
throws DbException, FormatException;
private Message sendAcceptMessage(Transaction txn, Session session)
throws DbException {
Message m = messageEncoder.encodeAcceptMessage(
session.getContactGroupId(), session.getShareableId(),
getLocalTimestamp(session), session.getLastLocalMessageId());
sendMessage(txn, m, ACCEPT, session.getShareableId(), true);
return m;
}
@Override
public Session onDeclineAction(Transaction txn, Session s)
throws DbException {
switch (s.getState()) {
case LOCAL_INVITED:
return onLocalDecline(txn, s);
case START:
case REMOTE_INVITED:
case SHARING:
case LOCAL_LEFT:
case REMOTE_LEFT:
case REMOTE_HANGING:
case ERROR:
throw new ProtocolStateException(); // Invalid in these states
default:
throw new AssertionError();
}
}
private Session onLocalDecline(Transaction txn, Session s)
throws DbException {
// Mark the invite message unavailable to answer
MessageId inviteId = s.getLastRemoteMessageId();
if (inviteId == null) throw new IllegalStateException();
markMessageAvailableToAnswer(txn, inviteId, false);
// Send a DECLINE message
Message sent = sendDeclineMessage(txn, s);
// Track the message
messageTracker.trackOutgoingMessage(txn, sent);
// Move to the START state
return new Session(START, s.getContactGroupId(), s.getShareableId(),
sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(),
s.getInviteTimestamp());
}
private Message sendDeclineMessage(Transaction txn, Session session)
throws DbException {
Message m = messageEncoder.encodeDeclineMessage(
session.getContactGroupId(), session.getShareableId(),
getLocalTimestamp(session), session.getLastLocalMessageId());
sendMessage(txn, m, DECLINE, session.getShareableId(), true);
return m;
}
@Override
public Session onLeaveAction(Transaction txn, Session s)
throws DbException {
switch (s.getState()) {
case REMOTE_INVITED:
return onLocalLeave(txn, s, REMOTE_HANGING);
case SHARING:
return onLocalLeave(txn, s, LOCAL_LEFT);
case REMOTE_LEFT:
return onLocalLeave(txn, s, START);
case START:
case LOCAL_INVITED:
case LOCAL_LEFT:
case REMOTE_HANGING:
case ERROR:
return s; // Ignored in this state
default:
throw new AssertionError();
}
}
private Session onLocalLeave(Transaction txn, Session s, State nextState)
throws DbException {
try {
// Stop sharing the shareable (not actually needed in REMOTE_LEFT)
setShareableVisibility(txn, s, INVISIBLE);
} catch (FormatException e) {
throw new DbException(e); // Invalid group metadata
}
// Send a LEAVE message
Message sent = sendLeaveMessage(txn, s);
// Move to the next state
return new Session(nextState, s.getContactGroupId(), s.getShareableId(),
sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(),
s.getInviteTimestamp());
}
private Message sendLeaveMessage(Transaction txn, Session session)
throws DbException {
Message m = messageEncoder.encodeLeaveMessage(
session.getContactGroupId(), session.getShareableId(),
getLocalTimestamp(session), session.getLastLocalMessageId());
sendMessage(txn, m, LEAVE, session.getShareableId(), false);
return m;
}
@Override
public Session onInviteMessage(Transaction txn, Session s,
InviteMessage<S> m) throws DbException, FormatException {
switch (s.getState()) {
case START:
case LOCAL_LEFT:
return onRemoteInvite(txn, s, m, true, LOCAL_INVITED);
case REMOTE_INVITED:
return onRemoteInviteWhenInvited(txn, s, m);
case REMOTE_HANGING:
return onRemoteInvite(txn, s, m, false, LOCAL_LEFT);
case LOCAL_INVITED:
case SHARING:
case REMOTE_LEFT:
return abort(txn, s); // Invalid in these states
case ERROR:
return s; // Ignored in this state
default:
throw new AssertionError();
}
}
private Session onRemoteInvite(Transaction txn, Session s,
InviteMessage<S> m, boolean available, State nextState)
throws DbException, FormatException {
// The timestamp must be higher than the last invite message, if any
if (m.getTimestamp() <= s.getInviteTimestamp()) return abort(txn, s);
// The dependency, if any, must be the last remote message
if (!isValidDependency(s, m.getPreviousMessageId()))
return abort(txn, s);
// Mark the invite message visible in the UI and (un)available to answer
markMessageVisibleInUi(txn, m.getId(), true);
markMessageAvailableToAnswer(txn, m.getId(), available);
// Track the message
messageTracker.trackMessage(txn, m.getContactGroupId(),
m.getTimestamp(), false);
// Broadcast an event
ContactId contactId = getContactId(txn, s.getContactGroupId());
txn.attach(getInvitationRequestReceivedEvent(m, contactId, available,
false));
// Move to the next state
return new Session(nextState, s.getContactGroupId(), s.getShareableId(),
s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(),
m.getTimestamp());
}
private Session onRemoteInviteWhenInvited(Transaction txn, Session s,
InviteMessage<S> m) throws DbException, FormatException {
// The timestamp must be higher than the last invite message, if any
if (m.getTimestamp() <= s.getInviteTimestamp()) return abort(txn, s);
// The dependency, if any, must be the last remote message
if (!isValidDependency(s, m.getPreviousMessageId()))
return abort(txn, s);
// Mark the invite message visible in the UI and unavailable to answer
markMessageVisibleInUi(txn, m.getId(), true);
markMessageAvailableToAnswer(txn, m.getId(), false);
// Track the message
messageTracker.trackMessage(txn, m.getContactGroupId(),
m.getTimestamp(), false);
// Share the shareable with the contact
setShareableVisibility(txn, s, SHARED);
// Broadcast an event
ContactId contactId = getContactId(txn, s.getContactGroupId());
txn.attach(
getInvitationRequestReceivedEvent(m, contactId, false, true));
// Move to the next state
return new Session(SHARING, s.getContactGroupId(), s.getShareableId(),
s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(),
m.getTimestamp());
}
abstract Event getInvitationRequestReceivedEvent(InviteMessage<S> m,
ContactId contactId, boolean available, boolean canBeOpened);
@Override
public Session onAcceptMessage(Transaction txn, Session s,
AcceptMessage m) throws DbException, FormatException {
switch (s.getState()) {
case REMOTE_INVITED:
return onRemoteAcceptWhenInvited(txn, s, m);
case REMOTE_HANGING:
return onRemoteAccept(txn, s, m, LOCAL_LEFT);
case START:
case LOCAL_INVITED:
case SHARING:
case LOCAL_LEFT:
case REMOTE_LEFT:
return abort(txn, s); // Invalid in these states
case ERROR:
return s; // Ignored in this state
default:
throw new AssertionError();
}
}
private Session onRemoteAccept(Transaction txn, Session s, AcceptMessage m,
State nextState) throws DbException, FormatException {
// The timestamp must be higher than the last invite message
if (m.getTimestamp() <= s.getInviteTimestamp()) return abort(txn, s);
// The dependency, if any, must be the last remote message
if (!isValidDependency(s, m.getPreviousMessageId()))
return abort(txn, s);
// Mark the response visible in the UI
markMessageVisibleInUi(txn, m.getId(), true);
// Track the message
messageTracker.trackMessage(txn, m.getContactGroupId(),
m.getTimestamp(), false);
// Broadcast an event
ContactId contactId = getContactId(txn, m.getContactGroupId());
txn.attach(getInvitationResponseReceivedEvent(m, contactId));
// Move to the next state
return new Session(nextState, s.getContactGroupId(), s.getShareableId(),
s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(),
s.getInviteTimestamp());
}
private Session onRemoteAcceptWhenInvited(Transaction txn, Session s,
AcceptMessage m) throws DbException, FormatException {
// Perform normal remote accept validation and operation
Session session = onRemoteAccept(txn, s, m, SHARING);
// Share the shareable with the contact
if (session.getState() != ERROR)
setShareableVisibility(txn, s, SHARED);
return session;
}
abstract Event getInvitationResponseReceivedEvent(AcceptMessage m,
ContactId contactId);
@Override
public Session onDeclineMessage(Transaction txn, Session s,
DeclineMessage m) throws DbException, FormatException {
switch (s.getState()) {
case REMOTE_INVITED:
case REMOTE_HANGING:
return onRemoteDecline(txn, s, m);
case START:
case LOCAL_INVITED:
case SHARING:
case LOCAL_LEFT:
case REMOTE_LEFT:
return abort(txn, s); // Invalid in these states
case ERROR:
return s; // Ignored in this state
default:
throw new AssertionError();
}
}
private Session onRemoteDecline(Transaction txn, Session s,
DeclineMessage m) throws DbException, FormatException {
// The timestamp must be higher than the last invite message
if (m.getTimestamp() <= s.getInviteTimestamp()) return abort(txn, s);
// The dependency, if any, must be the last remote message
if (!isValidDependency(s, m.getPreviousMessageId()))
return abort(txn, s);
// Mark the response visible in the UI
markMessageVisibleInUi(txn, m.getId(), true);
// Track the message
messageTracker.trackMessage(txn, m.getContactGroupId(),
m.getTimestamp(), false);
// Make the shareable invisible (not actually needed in REMOTE_HANGING)
try {
setShareableVisibility(txn, s, INVISIBLE);
} catch (FormatException e) {
throw new DbException(e); // Invalid group metadata
}
// Broadcast an event
ContactId contactId = getContactId(txn, m.getContactGroupId());
txn.attach(getInvitationResponseReceivedEvent(m, contactId));
// Move to the next state
return new Session(START, s.getContactGroupId(), s.getShareableId(),
s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(),
s.getInviteTimestamp());
}
abstract Event getInvitationResponseReceivedEvent(DeclineMessage m,
ContactId contactId);
@Override
public Session onLeaveMessage(Transaction txn, Session s,
LeaveMessage m) throws DbException, FormatException {
switch (s.getState()) {
case LOCAL_INVITED:
return onRemoteLeaveWhenInvited(txn, s, m, START);
case LOCAL_LEFT:
return onRemoteLeave(txn, s, m, START);
case SHARING:
return onRemoteLeaveWhenSharing(txn, s, m);
case START:
case REMOTE_INVITED:
case REMOTE_LEFT:
case REMOTE_HANGING:
return abort(txn, s); // Invalid in these states
case ERROR:
return s; // Ignored in this state
default:
throw new AssertionError();
}
}
private Session onRemoteLeaveWhenInvited(Transaction txn, Session s,
LeaveMessage m, State nextState)
throws DbException, FormatException {
// Carry out normal leave validation and operation
Session session = onRemoteLeave(txn, s, m, nextState);
// Mark any invite messages in the session unavailable to answer
if (session.getState() != ERROR)
markInvitesUnavailableToAnswer(txn, s);
// Move to the next state
return session;
}
private Session onRemoteLeave(Transaction txn, Session s,
LeaveMessage m, State nextState)
throws DbException, FormatException {
// The dependency, if any, must be the last remote message
if (!isValidDependency(s, m.getPreviousMessageId()))
return abort(txn, s);
// Move to the next state
return new Session(nextState, s.getContactGroupId(), s.getShareableId(),
s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(),
s.getInviteTimestamp());
}
private Session onRemoteLeaveWhenSharing(Transaction txn, Session s,
LeaveMessage m) throws DbException, FormatException {
// Carry out normal leave validation and operation
Session session = onRemoteLeave(txn, s, m, REMOTE_LEFT);
// Stop sharing the shareable with the contact
if (session.getState() != ERROR)
setShareableVisibility(txn, s, INVISIBLE);
// Move to the next state
return session;
}
@Override
public Session onAbortMessage(Transaction txn, Session s, AbortMessage m)
throws DbException, FormatException {
return abort(txn, s);
}
private Session abort(Transaction txn, Session s)
throws DbException, FormatException {
// If the session has already been aborted, do nothing
if (s.getState() == ERROR) return s;
// Mark any invite messages in the session unavailable to answer
markInvitesUnavailableToAnswer(txn, s);
// If we subscribe, make the shareable invisible to the contact
if (isSubscribed(txn, s.getShareableId()))
setShareableVisibility(txn, s, INVISIBLE);
// Send an ABORT message
Message sent = sendAbortMessage(txn, s);
// Move to the ERROR state
return new Session(ERROR, s.getContactGroupId(), s.getShareableId(),
sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(),
s.getInviteTimestamp());
}
private void markInvitesUnavailableToAnswer(Transaction txn, Session s)
throws DbException, FormatException {
GroupId shareableId = s.getShareableId();
BdfDictionary query =
messageParser.getInvitesAvailableToAnswerQuery(shareableId);
Map<MessageId, BdfDictionary> results =
clientHelper.getMessageMetadataAsDictionary(txn,
s.getContactGroupId(), query);
for (MessageId m : results.keySet())
markMessageAvailableToAnswer(txn, m, false);
}
private boolean isSubscribed(Transaction txn, GroupId g)
throws DbException {
if (!db.containsGroup(txn, g)) return false;
Group group = db.getGroup(txn, g);
return group.getClientId().equals(getClientId());
}
protected abstract ClientId getClientId();
private Message sendAbortMessage(Transaction txn, Session session)
throws DbException {
Message m = messageEncoder.encodeAbortMessage(
session.getContactGroupId(), session.getShareableId(),
getLocalTimestamp(session), session.getLastLocalMessageId());
sendMessage(txn, m, ABORT, session.getShareableId(), false);
return m;
}
private void sendMessage(Transaction txn, Message m, MessageType type,
GroupId shareableId, boolean visibleInConversation)
throws DbException {
BdfDictionary meta = messageEncoder.encodeMetadata(type, shareableId,
m.getTimestamp(), true, true, visibleInConversation, false);
try {
clientHelper.addLocalMessage(txn, m, meta, true);
} catch (FormatException e) {
throw new AssertionError(e);
}
}
private void markMessageAvailableToAnswer(Transaction txn, MessageId m,
boolean available) throws DbException {
BdfDictionary meta = new BdfDictionary();
messageEncoder.setAvailableToAnswer(meta, available);
try {
clientHelper.mergeMessageMetadata(txn, m, meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
}
private void markMessageVisibleInUi(Transaction txn, MessageId m,
boolean visible) throws DbException {
BdfDictionary meta = new BdfDictionary();
messageEncoder.setVisibleInUi(meta, visible);
try {
clientHelper.mergeMessageMetadata(txn, m, meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
}
private void setShareableVisibility(Transaction txn, Session session,
Visibility v) throws DbException, FormatException {
ContactId contactId = getContactId(txn, session.getContactGroupId());
db.setGroupVisibility(txn, contactId, session.getShareableId(), v);
}
private ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException, FormatException {
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn,
contactGroupId);
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
}
private boolean isValidDependency(Session session,
@Nullable MessageId dependency) {
MessageId expected = session.getLastRemoteMessageId();
if (dependency == null) return expected == null;
return expected != null && dependency.equals(expected);
}
private long getLocalTimestamp(Session session) {
return Math.max(clock.currentTimeMillis(),
Math.max(session.getLocalTimestamp(),
session.getInviteTimestamp()) + 1);
}
}

View File

@@ -0,0 +1,69 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.sharing.State.START;
@Immutable
@NotNullByDefault
class Session {
private final State state;
private final GroupId contactGroupId, shareableId;
@Nullable
private final MessageId lastLocalMessageId, lastRemoteMessageId;
private final long localTimestamp, inviteTimestamp;
Session(State state, GroupId contactGroupId, GroupId shareableId,
@Nullable MessageId lastLocalMessageId,
@Nullable MessageId lastRemoteMessageId, long localTimestamp,
long inviteTimestamp) {
this.state = state;
this.contactGroupId = contactGroupId;
this.shareableId = shareableId;
this.lastLocalMessageId = lastLocalMessageId;
this.lastRemoteMessageId = lastRemoteMessageId;
this.localTimestamp = localTimestamp;
this.inviteTimestamp = inviteTimestamp;
}
Session(GroupId contactGroupId, GroupId shareableId) {
this(START, contactGroupId, shareableId, null, null, 0, 0);
}
public State getState() {
return state;
}
GroupId getContactGroupId() {
return contactGroupId;
}
GroupId getShareableId() {
return shareableId;
}
@Nullable
MessageId getLastLocalMessageId() {
return lastLocalMessageId;
}
@Nullable
MessageId getLastRemoteMessageId() {
return lastRemoteMessageId;
}
long getLocalTimestamp() {
return localTimestamp;
}
long getInviteTimestamp() {
return inviteTimestamp;
}
}

View File

@@ -0,0 +1,11 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
interface SessionEncoder {
BdfDictionary encodeSession(Session s);
}

View File

@@ -0,0 +1,46 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_INVITE_TIMESTAMP;
import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID;
import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_LOCAL_TIMESTAMP;
import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_SESSION_ID;
import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_SHAREABLE_ID;
import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_STATE;
@Immutable
@NotNullByDefault
class SessionEncoderImpl implements SessionEncoder {
@Inject
SessionEncoderImpl() {
}
@Override
public BdfDictionary encodeSession(Session s) {
BdfDictionary d = new BdfDictionary();
d.put(SESSION_KEY_SESSION_ID, s.getShareableId());
d.put(SESSION_KEY_SHAREABLE_ID, s.getShareableId());
MessageId lastLocalMessageId = s.getLastLocalMessageId();
if (lastLocalMessageId == null)
d.put(SESSION_KEY_LAST_LOCAL_MESSAGE_ID, NULL_VALUE);
else d.put(SESSION_KEY_LAST_LOCAL_MESSAGE_ID, lastLocalMessageId);
MessageId lastRemoteMessageId = s.getLastRemoteMessageId();
if (lastRemoteMessageId == null)
d.put(SESSION_KEY_LAST_REMOTE_MESSAGE_ID, NULL_VALUE);
else d.put(SESSION_KEY_LAST_REMOTE_MESSAGE_ID, lastRemoteMessageId);
d.put(SESSION_KEY_LOCAL_TIMESTAMP, s.getLocalTimestamp());
d.put(SESSION_KEY_INVITE_TIMESTAMP, s.getInviteTimestamp());
d.put(SESSION_KEY_STATE, s.getState().getValue());
return d;
}
}

View File

@@ -0,0 +1,17 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.api.client.SessionId;
@NotNullByDefault
interface SessionParser {
BdfDictionary getSessionQuery(SessionId s);
Session parseSession(GroupId contactGroupId, BdfDictionary d)
throws FormatException;
}

View File

@@ -0,0 +1,75 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.SessionId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_INVITE_TIMESTAMP;
import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID;
import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_LOCAL_TIMESTAMP;
import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_SESSION_ID;
import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_SHAREABLE_ID;
import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_STATE;
@Immutable
@NotNullByDefault
class SessionParserImpl implements SessionParser {
@Inject
SessionParserImpl() {
}
@Override
public BdfDictionary getSessionQuery(SessionId s) {
return BdfDictionary.of(new BdfEntry(SESSION_KEY_SESSION_ID, s));
}
@Override
public Session parseSession(GroupId contactGroupId,
BdfDictionary d) throws FormatException {
return new Session(State.fromValue(getState(d)), contactGroupId,
getShareableId(d), getLastLocalMessageId(d),
getLastRemoteMessageId(d), getLocalTimestamp(d),
getInviteTimestamp(d));
}
private int getState(BdfDictionary d) throws FormatException {
return d.getLong(SESSION_KEY_STATE).intValue();
}
private GroupId getShareableId(BdfDictionary d) throws FormatException {
return new GroupId(d.getRaw(SESSION_KEY_SHAREABLE_ID));
}
@Nullable
private MessageId getLastLocalMessageId(BdfDictionary d)
throws FormatException {
byte[] b = d.getOptionalRaw(SESSION_KEY_LAST_LOCAL_MESSAGE_ID);
return b == null ? null : new MessageId(b);
}
@Nullable
private MessageId getLastRemoteMessageId(BdfDictionary d)
throws FormatException {
byte[] b = d.getOptionalRaw(SESSION_KEY_LAST_REMOTE_MESSAGE_ID);
return b == null ? null : new MessageId(b);
}
private long getLocalTimestamp(BdfDictionary d) throws FormatException {
return d.getLong(SESSION_KEY_LOCAL_TIMESTAMP);
}
private long getInviteTimestamp(BdfDictionary d) throws FormatException {
return d.getLong(SESSION_KEY_INVITE_TIMESTAMP);
}
}

View File

@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.api.sharing.Shareable;
import org.briarproject.briar.api.sharing.SharingMessage;
@Deprecated
@NotNullByDefault
interface ShareableFactory<S extends Shareable, I extends SharingMessage.Invitation, IS extends InviteeSessionState, SS extends SharerSessionState> {

View File

@@ -29,6 +29,7 @@ import static org.briarproject.briar.api.sharing.SharingMessage.SimpleMessage;
import static org.briarproject.briar.sharing.SharerSessionState.Action.REMOTE_ACCEPT;
import static org.briarproject.briar.sharing.SharerSessionState.Action.REMOTE_DECLINE;
@Deprecated
@Immutable
@NotNullByDefault
class SharerEngine<I extends Invitation, SS extends SharerSessionState, IRR extends InvitationResponseReceivedEvent>
@@ -37,12 +38,12 @@ class SharerEngine<I extends Invitation, SS extends SharerSessionState, IRR exte
private static final Logger LOG =
Logger.getLogger(SharerEngine.class.getName());
private final InvitationFactory<I, SS> invitationFactory;
private final OldInvitationFactory<I, SS> invitationFactory;
private final InvitationResponseReceivedEventFactory<SS, IRR>
invitationResponseReceivedEventFactory;
private final Clock clock;
SharerEngine(InvitationFactory<I, SS> invitationFactory,
SharerEngine(OldInvitationFactory<I, SS> invitationFactory,
InvitationResponseReceivedEventFactory<SS, IRR> invitationResponseReceivedEventFactory,
Clock clock) {
this.invitationFactory = invitationFactory;

View File

@@ -23,6 +23,7 @@ import static org.briarproject.briar.sharing.SharerSessionState.Action.REMOTE_AC
import static org.briarproject.briar.sharing.SharerSessionState.Action.REMOTE_DECLINE;
import static org.briarproject.briar.sharing.SharerSessionState.Action.REMOTE_LEAVE;
@Deprecated
@NotThreadSafe
@NotNullByDefault
public abstract class SharerSessionState extends SharingSessionState {

View File

@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.sharing.Shareable;
@Deprecated
@NotNullByDefault
interface SharerSessionStateFactory<S extends Shareable, SS extends SharerSessionState> {

View File

@@ -0,0 +1,28 @@
package org.briarproject.briar.sharing;
import org.briarproject.briar.client.MessageTrackerConstants;
interface SharingConstants {
// Group metadata keys
String GROUP_KEY_CONTACT_ID = "contactId";
// Message metadata keys
String MSG_KEY_MESSAGE_TYPE = "messageType";
String MSG_KEY_SHAREABLE_ID = "shareableId";
String MSG_KEY_TIMESTAMP = "timestamp";
String MSG_KEY_READ = MessageTrackerConstants.MSG_KEY_READ;
String MSG_KEY_LOCAL = "local";
String MSG_KEY_VISIBLE_IN_UI = "visibleInUi";
String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer";
// Session keys
String SESSION_KEY_STATE = "state";
String SESSION_KEY_SESSION_ID = "sessionId";
String SESSION_KEY_SHAREABLE_ID = "shareableId";
String SESSION_KEY_LAST_LOCAL_MESSAGE_ID = "lastLocalMessageId";
String SESSION_KEY_LAST_REMOTE_MESSAGE_ID = "lastRemoteMessageId";
String SESSION_KEY_LOCAL_TIMESTAMP = "localTimestamp";
String SESSION_KEY_INVITE_TIMESTAMP = "inviteTimestamp";
}

View File

@@ -0,0 +1,50 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
abstract class SharingMessage {
private final MessageId id;
private final GroupId contactGroupId, shareableId;
private final long timestamp;
@Nullable
private final MessageId previousMessageId;
SharingMessage(MessageId id, GroupId contactGroupId, GroupId shareableId,
long timestamp, @Nullable MessageId previousMessageId) {
this.id = id;
this.previousMessageId = previousMessageId;
this.contactGroupId = contactGroupId;
this.shareableId = shareableId;
this.timestamp = timestamp;
}
MessageId getId() {
return id;
}
GroupId getContactGroupId() {
return contactGroupId;
}
GroupId getShareableId() {
return shareableId;
}
long getTimestamp() {
return timestamp;
}
@Nullable
public MessageId getPreviousMessageId() {
return previousMessageId;
}
}

View File

@@ -4,10 +4,13 @@ import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.sync.ValidationManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.client.MessageQueueManager;
import org.briarproject.briar.api.forum.Forum;
import org.briarproject.briar.api.forum.ForumFactory;
import org.briarproject.briar.api.forum.ForumManager;
import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.messaging.ConversationManager;
@@ -68,14 +71,15 @@ public class SharingModule {
@Provides
@Singleton
ForumSharingValidator provideForumSharingValidator(
MessageQueueManager messageQueueManager, ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
ValidationManager validationManager, MessageEncoder messageEncoder,
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
Clock clock, ForumFactory forumFactory) {
ForumSharingValidator validator =
new ForumSharingValidator(clientHelper, metadataEncoder, clock);
messageQueueManager.registerMessageValidator(
ForumSharingManager.CLIENT_ID, validator);
new ForumSharingValidator(messageEncoder, clientHelper,
metadataEncoder, clock, forumFactory);
validationManager
.registerMessageValidator(ForumSharingManager.CLIENT_ID,
validator);
return validator;
}
@@ -83,14 +87,14 @@ public class SharingModule {
@Singleton
ForumSharingManager provideForumSharingManager(
LifecycleManager lifecycleManager, ContactManager contactManager,
MessageQueueManager messageQueueManager,
ValidationManager validationManager,
ConversationManager conversationManager, ForumManager forumManager,
ForumSharingManagerImpl forumSharingManager) {
lifecycleManager.registerClient(forumSharingManager);
contactManager.registerAddContactHook(forumSharingManager);
contactManager.registerRemoveContactHook(forumSharingManager);
messageQueueManager.registerIncomingMessageHook(
validationManager.registerIncomingMessageHook(
ForumSharingManager.CLIENT_ID, forumSharingManager);
conversationManager.registerConversationClient(forumSharingManager);
forumManager.registerRemoveForumHook(forumSharingManager);
@@ -98,4 +102,37 @@ public class SharingModule {
return forumSharingManager;
}
@Provides
MessageEncoder provideMessageEncoder(MessageEncoderImpl messageEncoder) {
return messageEncoder;
}
@Provides
MessageParser<Forum> provideForumMessageParser(
ForumMessageParserImpl forumMessageParser) {
return forumMessageParser;
}
@Provides
SessionEncoder provideSessionEncoder(SessionEncoderImpl sessionEncoder) {
return sessionEncoder;
}
@Provides
SessionParser provideSessionParser(SessionParserImpl sessionParser) {
return sessionParser;
}
@Provides
ProtocolEngine<Forum> provideForumProtocolEngine(
ForumProtocolEngineImpl forumProtocolEngine) {
return forumProtocolEngine;
}
@Provides
InvitationFactory<Forum> provideForumInvitationFactory(
ForumInvitationFactoryImpl forumInvitationFactory) {
return forumInvitationFactory;
}
}

View File

@@ -18,6 +18,7 @@ import static org.briarproject.briar.api.sharing.SharingConstants.SHAREABLE_ID;
import static org.briarproject.briar.api.sharing.SharingConstants.STATE;
import static org.briarproject.briar.api.sharing.SharingConstants.STORAGE_ID;
@Deprecated
@NotThreadSafe
@NotNullByDefault
abstract class SharingSessionState {

View File

@@ -0,0 +1,101 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.client.BdfMessageContext;
import org.briarproject.bramble.api.client.BdfMessageValidator;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import java.util.Collections;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
import static org.briarproject.briar.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH;
import static org.briarproject.briar.sharing.MessageType.INVITE;
@Immutable
@NotNullByDefault
abstract class SharingValidator extends BdfMessageValidator {
private final MessageEncoder messageEncoder;
SharingValidator(MessageEncoder messageEncoder, ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
super(clientHelper, metadataEncoder, clock);
this.messageEncoder = messageEncoder;
}
@Override
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException {
MessageType type = MessageType.fromValue(body.getLong(0).intValue());
switch (type) {
case INVITE:
return validateInviteMessage(m, body);
case ACCEPT:
case DECLINE:
case LEAVE:
case ABORT:
return validateNonInviteMessage(type, m, body);
default:
throw new FormatException();
}
}
private BdfMessageContext validateInviteMessage(Message m, BdfList body)
throws FormatException {
checkSize(body, 4);
byte[] previousMessageId = body.getOptionalRaw(1);
checkLength(previousMessageId, UniqueId.LENGTH);
BdfList descriptor = body.getList(2);
GroupId shareableId = validateDescriptor(descriptor);
String msg = body.getOptionalString(3);
checkLength(msg, 1, MAX_INVITATION_MESSAGE_LENGTH);
BdfDictionary meta = messageEncoder
.encodeMetadata(INVITE, shareableId, m.getTimestamp(), false,
false, false, false);
if (previousMessageId == null) {
return new BdfMessageContext(meta);
} else {
MessageId dependency = new MessageId(previousMessageId);
return new BdfMessageContext(meta,
Collections.singletonList(dependency));
}
}
protected abstract GroupId validateDescriptor(BdfList descriptor)
throws FormatException;
private BdfMessageContext validateNonInviteMessage(MessageType type,
Message m, BdfList body) throws FormatException {
checkSize(body, 3);
byte[] shareableId = body.getRaw(1);
checkLength(shareableId, UniqueId.LENGTH);
byte[] previousMessageId = body.getOptionalRaw(2);
checkLength(previousMessageId, UniqueId.LENGTH);
BdfDictionary meta = messageEncoder
.encodeMetadata(type, new GroupId(shareableId),
m.getTimestamp(), false, false, false, false);
if (previousMessageId == null) {
return new BdfMessageContext(meta);
} else {
MessageId dependency = new MessageId(previousMessageId);
return new BdfMessageContext(meta,
Collections.singletonList(dependency));
}
}
}

View File

@@ -0,0 +1,34 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
enum State {
START(0), LOCAL_INVITED(1), REMOTE_INVITED(2), SHARING(3), LOCAL_LEFT(4),
REMOTE_LEFT(5), REMOTE_HANGING(6), ERROR(7);
private final int value;
State(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public boolean canInvite() {
return this == START || this == REMOTE_LEFT;
}
static State fromValue(int value) throws FormatException {
for (State s : values()) if (s.value == value) return s;
throw new FormatException();
}
}

View File

@@ -45,7 +45,8 @@ public class ForumManagerTest
forum0 = forumManager0.addForum("Test Forum");
groupId0 = forum0.getId();
// share forum
forumSharingManager0.sendInvitation(groupId0, contactId1From0, null);
forumSharingManager0.sendInvitation(groupId0, contactId1From0, null,
clock.currentTimeMillis());
sync0To1(1, true);
forumSharingManager1.respondToInvitation(forum0, contact0From1, true);
sync1To0(1, true);
@@ -189,7 +190,8 @@ public class ForumManagerTest
// share a second forum
Forum forum1 = forumManager0.addForum("Test Forum1");
GroupId g1 = forum1.getId();
forumSharingManager0.sendInvitation(g1, contactId1From0, null);
forumSharingManager0.sendInvitation(g1, contactId1From0, null,
clock.currentTimeMillis());
sync0To1(1, true);
forumSharingManager1.respondToInvitation(forum1, contact0From1, true);
sync1To0(1, true);

View File

@@ -12,6 +12,7 @@ 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.api.sharing.InvitationMessage;
import org.briarproject.briar.api.sharing.InvitationResponse;
import org.briarproject.briar.test.BriarIntegrationTest;
import org.briarproject.briar.test.BriarIntegrationTestComponent;
import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent;
@@ -97,10 +98,10 @@ public class GroupInvitationIntegrationTest
GroupInvitationRequest request =
(GroupInvitationRequest) messages.iterator().next();
assertEquals(msg, request.getMessage());
assertEquals(author0, request.getCreator());
assertEquals(author0, request.getShareable().getCreator());
assertEquals(timestamp, request.getTimestamp());
assertEquals(contactId0From1, request.getContactId());
assertEquals(privateGroup0.getName(), request.getGroupName());
assertEquals(privateGroup0.getName(), request.getShareable().getName());
assertFalse(request.isLocal());
assertFalse(request.isRead());
}
@@ -123,7 +124,7 @@ public class GroupInvitationIntegrationTest
for (InvitationMessage m : messages) {
if (m instanceof GroupInvitationResponse) {
foundResponse = true;
GroupInvitationResponse response = (GroupInvitationResponse) m;
InvitationResponse response = (GroupInvitationResponse) m;
assertEquals(contactId0From1, response.getContactId());
assertTrue(response.isLocal());
assertFalse(response.wasAccepted());
@@ -140,7 +141,7 @@ public class GroupInvitationIntegrationTest
for (InvitationMessage m : messages) {
if (m instanceof GroupInvitationResponse) {
foundResponse = true;
GroupInvitationResponse response = (GroupInvitationResponse) m;
InvitationResponse response = (GroupInvitationResponse) m;
assertEquals(contactId0From1, response.getContactId());
assertFalse(response.isLocal());
assertFalse(response.wasAccepted());
@@ -172,7 +173,7 @@ public class GroupInvitationIntegrationTest
for (InvitationMessage m : messages) {
if (m instanceof GroupInvitationResponse) {
foundResponse = true;
GroupInvitationResponse response = (GroupInvitationResponse) m;
InvitationResponse response = (GroupInvitationResponse) m;
assertTrue(response.wasAccepted());
}
}
@@ -187,7 +188,7 @@ public class GroupInvitationIntegrationTest
for (InvitationMessage m : messages) {
if (m instanceof GroupInvitationResponse) {
foundResponse = true;
GroupInvitationResponse response = (GroupInvitationResponse) m;
InvitationResponse response = (GroupInvitationResponse) m;
assertTrue(response.wasAccepted());
}
}

View File

@@ -90,8 +90,6 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
private final GroupInvitationManagerImpl groupInvitationManager;
private final Group localGroup =
new Group(new GroupId(getRandomId()), CLIENT_ID, getRandomBytes(5));
private final Transaction txn = new Transaction(null, false);
private final ContactId contactId = new ContactId(0);
private final Author author =
@@ -141,8 +139,6 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
will(returnValue(inviteeEngine));
oneOf(engineFactory).createPeerEngine();
will(returnValue(peerEngine));
oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID);
will(returnValue(localGroup));
}});
MetadataParser metadataParser = context.mock(MetadataParser.class);
MessageTracker messageTracker = context.mock(MessageTracker.class);
@@ -156,7 +152,6 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
@Test
public void testCreateLocalState() throws Exception {
context.checking(new Expectations() {{
oneOf(db).addGroup(txn, localGroup);
oneOf(db).getContacts(txn);
will(returnValue(Collections.singletonList(contact)));
}});
@@ -651,6 +646,9 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
new InviteMessage(message.getId(), contactGroup.getId(),
privateGroup.getId(), time1, "name", author,
new byte[0], null, new byte[0]);
final PrivateGroup pg =
new PrivateGroup(privateGroup, invite.getGroupName(),
invite.getCreator(), invite.getSalt());
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
@@ -668,12 +666,11 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
oneOf(messageParser).parseMetadata(meta);
will(returnValue(messageMetadata1));
oneOf(db).getMessageStatus(txn, contactId, message.getId());
oneOf(clientHelper).getMessage(txn, message.getId());
will(returnValue(message));
oneOf(clientHelper).toList(message);
will(returnValue(body));
oneOf(messageParser).parseInviteMessage(message, body);
oneOf(messageParser).getInviteMessage(txn, message.getId());
will(returnValue(invite));
oneOf(privateGroupFactory).createPrivateGroup(invite.getGroupName(),
invite.getCreator(), invite.getSalt());
will(returnValue(pg));
oneOf(db).containsGroup(txn, privateGroup.getId());
will(returnValue(true));
// second message
@@ -742,21 +739,13 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
contactGroup.getId(), query);
will(returnValue(results));
// message 1
oneOf(clientHelper).getMessage(txn, message.getId());
will(returnValue(message));
oneOf(clientHelper).toList(message);
will(returnValue(body));
oneOf(messageParser).parseInviteMessage(message, body);
oneOf(messageParser).getInviteMessage(txn, message.getId());
will(returnValue(inviteMessage1));
oneOf(privateGroupFactory).createPrivateGroup(groupName, author,
salt);
will(returnValue(pg));
// message 2
oneOf(clientHelper).getMessage(txn, messageId2);
will(returnValue(message2));
oneOf(clientHelper).toList(message2);
will(returnValue(body2));
oneOf(messageParser).parseInviteMessage(message2, body2);
oneOf(messageParser).getInviteMessage(txn, messageId2);
will(returnValue(inviteMessage2));
oneOf(privateGroupFactory).createPrivateGroup(groupName, author,
salt);

View File

@@ -144,11 +144,7 @@ public class InviteeProtocolEngineTest extends AbstractProtocolEngineTest {
expectSendJoinMessage(properJoinMessage, true);
context.checking(new Expectations() {{
oneOf(messageTracker).trackOutgoingMessage(txn, message);
oneOf(clientHelper).getMessage(txn, lastRemoteMessageId);
will(returnValue(inviteMsg));
oneOf(clientHelper).toList(inviteMsg);
will(returnValue(inviteList));
oneOf(messageParser).parseInviteMessage(inviteMsg, inviteList);
oneOf(messageParser).getInviteMessage(txn, lastRemoteMessageId);
will(returnValue(inviteMessage));
oneOf(privateGroupFactory)
.createPrivateGroup(inviteMessage.getGroupName(),

View File

@@ -107,7 +107,8 @@ public class BlogSharingIntegrationTest
// create invitation
blogSharingManager0
.sendInvitation(blog1.getId(), contactId1From0, "Hi!");
.sendInvitation(blog1.getId(), contactId1From0, "Hi!",
clock.currentTimeMillis());
// sync invitation
sync0To1(1, false);
@@ -122,7 +123,8 @@ public class BlogSharingIntegrationTest
// send invitation
blogSharingManager0
.sendInvitation(blog2.getId(), contactId1From0, "Hi!");
.sendInvitation(blog2.getId(), contactId1From0, "Hi!",
clock.currentTimeMillis());
// invitee has own blog and that of the sharer
assertEquals(2, blogManager1.getBlogs().size());
@@ -194,7 +196,8 @@ public class BlogSharingIntegrationTest
// send invitation
blogSharingManager0
.sendInvitation(blog2.getId(), contactId1From0, null);
.sendInvitation(blog2.getId(), contactId1From0, null,
clock.currentTimeMillis());
// sync first request message
sync0To1(1, true);
@@ -251,7 +254,8 @@ public class BlogSharingIntegrationTest
// send invitation
blogSharingManager0
.sendInvitation(blog2.getId(), contactId1From0, "Hi!");
.sendInvitation(blog2.getId(), contactId1From0, "Hi!",
clock.currentTimeMillis());
// sync first request message
sync0To1(1, true);
@@ -313,7 +317,8 @@ public class BlogSharingIntegrationTest
// sharer sends invitation for 2's blog to 1
blogSharingManager0
.sendInvitation(blog2.getId(), contactId1From0, "Hi!");
.sendInvitation(blog2.getId(), contactId1From0, "Hi!",
clock.currentTimeMillis());
// sync first request message
sync0To1(1, true);
@@ -349,7 +354,8 @@ public class BlogSharingIntegrationTest
// send invitation
blogSharingManager0
.sendInvitation(blog2.getId(), contactId1From0, "Hi!");
.sendInvitation(blog2.getId(), contactId1From0, "Hi!",
clock.currentTimeMillis());
// sync first request message
sync0To1(1, true);
@@ -399,7 +405,8 @@ public class BlogSharingIntegrationTest
// sharer sends invitation for 2's blog to 1
blogSharingManager0
.sendInvitation(blog2.getId(), contactId1From0, "Hi!");
.sendInvitation(blog2.getId(), contactId1From0, "Hi!",
clock.currentTimeMillis());
// sync first request message
sync0To1(1, true);

View File

@@ -2,19 +2,14 @@ package org.briarproject.briar.sharing;
import net.jodah.concurrentunit.Waiter;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.test.TestDatabaseModule;
import org.briarproject.briar.api.client.MessageQueueManager;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.forum.Forum;
import org.briarproject.briar.api.forum.ForumInvitationRequest;
import org.briarproject.briar.api.forum.ForumInvitationResponse;
@@ -39,11 +34,7 @@ import java.util.Collection;
import java.util.List;
import static junit.framework.Assert.assertNotNull;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomString;
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT_LENGTH;
import static org.briarproject.briar.api.forum.ForumSharingManager.CLIENT_ID;
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -115,7 +106,8 @@ public class ForumSharingIntegrationTest
// send invitation
forumSharingManager0
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
.sendInvitation(forum0.getId(), contactId1From0, "Hi!",
clock.currentTimeMillis());
// sync first request message
sync0To1(1, true);
@@ -171,7 +163,8 @@ public class ForumSharingIntegrationTest
// send invitation
forumSharingManager0
.sendInvitation(forum0.getId(), contactId1From0, null);
.sendInvitation(forum0.getId(), contactId1From0, null,
clock.currentTimeMillis());
// sync first request message
sync0To1(1, true);
@@ -227,7 +220,8 @@ public class ForumSharingIntegrationTest
// send invitation
forumSharingManager0
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
.sendInvitation(forum0.getId(), contactId1From0, "Hi!",
clock.currentTimeMillis());
// sync first request message
sync0To1(1, true);
@@ -245,9 +239,8 @@ public class ForumSharingIntegrationTest
assertTrue(forumManager1.getForums().contains(forum0));
// sharer shares forum with invitee
Contact c1 = contactManager0.getContact(contactId1From0);
assertTrue(forumSharingManager0.getSharedWith(forum0.getId())
.contains(c1));
.contains(contact1From0));
// invitee gets forum shared by sharer
Contact contact0 = contactManager1.getContact(contactId1From0);
assertTrue(forumSharingManager1.getSharedWith(forum0.getId())
@@ -265,14 +258,16 @@ public class ForumSharingIntegrationTest
// sharer no longer shares forum with invitee
assertFalse(forumSharingManager0.getSharedWith(forum0.getId())
.contains(c1));
.contains(contact1From0));
// invitee no longer gets forum shared by sharer
assertFalse(forumSharingManager1.getSharedWith(forum0.getId())
.contains(contact0));
// forum can be shared again
assertTrue(forumSharingManager0.canBeShared(forum0.getId(), c1));
Contact c0 = contactManager1.getContact(contactId0From1);
assertTrue(forumSharingManager1.canBeShared(forum0.getId(), c0));
// forum can be shared again by sharer
assertTrue(forumSharingManager0
.canBeShared(forum0.getId(), contact1From0));
// invitee that left can not share again
assertFalse(forumSharingManager1
.canBeShared(forum0.getId(), contact0From1));
}
@Test
@@ -282,7 +277,8 @@ public class ForumSharingIntegrationTest
// send invitation
forumSharingManager0
.sendInvitation(forum0.getId(), contactId1From0, null);
.sendInvitation(forum0.getId(), contactId1From0, null,
clock.currentTimeMillis());
// sync first request message
sync0To1(1, true);
@@ -336,7 +332,8 @@ public class ForumSharingIntegrationTest
// send invitation
forumSharingManager0
.sendInvitation(forum0.getId(), contactId1From0, null);
.sendInvitation(forum0.getId(), contactId1From0, null,
clock.currentTimeMillis());
// sharer un-subscribes from forum
forumManager0.removeForum(forum0);
@@ -360,7 +357,8 @@ public class ForumSharingIntegrationTest
// send invitation
forumSharingManager0
.sendInvitation(forum0.getId(), contactId1From0, null);
.sendInvitation(forum0.getId(), contactId1From0, null,
clock.currentTimeMillis());
// sharer un-subscribes from forum
forumManager0.removeForum(forum0);
@@ -375,73 +373,15 @@ public class ForumSharingIntegrationTest
assertEquals(1, forumManager1.getForums().size());
}
@Test
public void testSessionIdReuse() throws Exception {
// initialize and let invitee accept all requests
listenToEvents(true);
// send invitation
forumSharingManager0
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
// sync first request message
sync0To1(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// sync response back
sync1To0(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
// forum was added successfully
assertEquals(1, forumManager1.getForums().size());
// reset event received state
listener1.requestReceived = false;
// get SessionId from invitation
List<InvitationMessage> list = new ArrayList<InvitationMessage>(
forumSharingManager1
.getInvitationMessages(contactId0From1));
assertEquals(2, list.size());
InvitationMessage msg = list.get(0);
SessionId sessionId = msg.getSessionId();
assertEquals(sessionId, list.get(1).getSessionId());
// get all sorts of stuff needed to send a message
MessageQueueManager queue = c0.getMessageQueueManager();
Contact c1 = contactManager0.getContact(contactId1From0);
Group group = contactGroupFactory.createContactGroup(CLIENT_ID, c1);
long time = clock.currentTimeMillis();
BdfList bodyList =
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId.getBytes(),
getRandomString(42), getRandomBytes(FORUM_SALT_LENGTH));
byte[] body = clientHelper.toByteArray(bodyList);
// add the message to the queue
Transaction txn = db0.startTransaction(false);
try {
queue.sendMessage(txn, group, time, body, new Metadata());
db0.commitTransaction(txn);
} finally {
db0.endTransaction(txn);
}
// actually send the message
sync0To1(1, false);
// make sure there was no new request received
assertFalse(listener1.requestReceived);
}
@Test
@Test(expected = ProtocolStateException.class)
public void testSharingSameForumWithEachOther() throws Exception {
// initialize and let invitee accept all requests
listenToEvents(true);
// send invitation
forumSharingManager0
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
.sendInvitation(forum0.getId(), contactId1From0, "Hi!",
clock.currentTimeMillis());
// sync first request message
sync0To1(1, true);
@@ -455,24 +395,12 @@ public class ForumSharingIntegrationTest
// forum was added successfully
assertEquals(1, forumManager1.getForums().size());
assertEquals(2,
forumSharingManager0.getInvitationMessages(contactId1From0)
.size());
// invitee now shares same forum back
forumSharingManager1.sendInvitation(forum0.getId(),
contactId0From1,
"I am re-sharing this forum with you.");
// sync re-share invitation
sync1To0(1, false);
// make sure that no new request was received
assertFalse(listener0.requestReceived);
assertEquals(2,
forumSharingManager0.getInvitationMessages(contactId1From0)
.size());
assertEquals(0, forumSharingManager0.getInvitations().size());
"I am re-sharing this forum with you.",
clock.currentTimeMillis());
}
@Test
@@ -482,62 +410,48 @@ public class ForumSharingIntegrationTest
// invitee adds the same forum
Transaction txn = db1.startTransaction(false);
db1.addGroup(txn, forum0.getGroup());
forumManager1.addForum(txn, forum0);
db1.commitTransaction(txn);
db1.endTransaction(txn);
// send invitation
forumSharingManager0
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
.sendInvitation(forum0.getId(), contactId1From0, "Hi!",
clock.currentTimeMillis());
// invitee now shares same forum back
forumSharingManager1.sendInvitation(forum0.getId(),
contactId0From1, "I am re-sharing this forum with you.");
contactId0From1, "I am re-sharing this forum with you.",
clock.currentTimeMillis());
// find out who should be Alice, because of random keys
Bytes key0 = new Bytes(author0.getPublicKey());
Bytes key1 = new Bytes(author1.getPublicKey());
// prevent automatic responses
respond = false;
// only now sync first request message
boolean alice = key1.compareTo(key0) < 0;
if (alice) {
sync0To1(1, false);
assertFalse(listener1.requestReceived);
} else {
sync0To1(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
}
sync0To1(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// sync second invitation
alice = key0.compareTo(key1) < 0;
if (alice) {
sync1To0(1, false);
assertFalse(listener0.requestReceived);
// sync second invitation which counts as accept
sync1To0(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.requestReceived);
// sharer did not receive request, but response to own request
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
// both peers should share the forum with each other now
assertTrue(forumSharingManager0.getSharedWith(forum0.getId())
.contains(contact1From0));
assertTrue(forumSharingManager1.getSharedWith(forum0.getId())
.contains(contact0From1));
assertEquals(2, forumSharingManager0
.getInvitationMessages(contactId1From0).size());
assertEquals(3, forumSharingManager1
.getInvitationMessages(contactId0From1).size());
} else {
sync1To0(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.requestReceived);
// and both have each other's invitations (and no response)
assertEquals(2, forumSharingManager0
.getInvitationMessages(contactId1From0).size());
assertEquals(2, forumSharingManager1
.getInvitationMessages(contactId0From1).size());
// send response from sharer to invitee and make sure it arrived
sync0To1(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.responseReceived);
assertEquals(3, forumSharingManager0
.getInvitationMessages(contactId1From0).size());
assertEquals(2, forumSharingManager1
.getInvitationMessages(contactId0From1).size());
}
// there are no more open invitations
assertTrue(forumSharingManager0.getInvitations().isEmpty());
assertTrue(forumSharingManager1.getInvitations().isEmpty());
}
@Test
@@ -547,7 +461,8 @@ public class ForumSharingIntegrationTest
// send invitation
forumSharingManager0
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
.sendInvitation(forum0.getId(), contactId1From0, "Hi!",
clock.currentTimeMillis());
// sync first request message
sync0To1(1, true);
@@ -564,18 +479,12 @@ public class ForumSharingIntegrationTest
assertEquals(1,
forumSharingManager0.getSharedWith(forum0.getId()).size());
// remember SessionId from invitation
List<InvitationMessage> list = new ArrayList<InvitationMessage>(
forumSharingManager1
.getInvitationMessages(contactId0From1));
assertEquals(2, list.size());
InvitationMessage msg = list.get(0);
SessionId sessionId = msg.getSessionId();
assertEquals(sessionId, list.get(1).getSessionId());
// contacts now remove each other
removeAllContacts();
// invitee still has forum
assertEquals(1, forumManager1.getForums().size());
// make sure sharer does share the forum with nobody now
assertEquals(0,
forumSharingManager0.getSharedWith(forum0.getId()).size());
@@ -584,35 +493,30 @@ public class ForumSharingIntegrationTest
addDefaultContacts();
addContacts1And2();
// get all sorts of stuff needed to send a message
MessageQueueManager queue = c0.getMessageQueueManager();
Contact c1 = contactManager0.getContact(contactId1From0);
Group group = contactGroupFactory.createContactGroup(CLIENT_ID, c1);
long time = clock.currentTimeMillis();
// forum can be shared with contacts again
assertTrue(forumSharingManager0
.canBeShared(forum0.getId(), contact1From0));
assertTrue(forumSharingManager0
.canBeShared(forum0.getId(), contact2From0));
// send invitation
forumSharingManager0
.sendInvitation(forum0.getId(), contactId1From0, "Hi!",
clock.currentTimeMillis());
// construct a new message re-using the old SessionId
BdfList bodyList = BdfList.of(SHARE_MSG_TYPE_INVITATION,
sessionId.getBytes(),
getRandomString(42),
getRandomBytes(FORUM_SALT_LENGTH)
);
byte[] body = clientHelper.toByteArray(bodyList);
// add the message to the queue
Transaction txn = db0.startTransaction(false);
try {
queue.sendMessage(txn, group, time, body, new Metadata());
db0.commitTransaction(txn);
} finally {
db0.endTransaction(txn);
}
// actually send the message
// sync first request message
sync0To1(1, true);
eventWaiter.await(TIMEOUT, 1);
// make sure the new request was received with the same sessionId
// as proof that the state got deleted along with contacts
assertTrue(listener1.requestReceived);
// sync response back
sync1To0(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
// forum is still there
assertEquals(1, forumManager1.getForums().size());
assertEquals(1,
forumSharingManager0.getSharedWith(forum0.getId()).size());
}
@Test
@@ -633,14 +537,16 @@ public class ForumSharingIntegrationTest
// send invitation
forumSharingManager0
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
.sendInvitation(forum0.getId(), contactId1From0, "Hi!",
clock.currentTimeMillis());
// sync first request message
sync0To1(1, true);
// second sharer sends invitation for same forum
assertTrue(contactId1From2 != null);
forumSharingManager2
.sendInvitation(forum0.getId(), contactId1From2, null);
.sendInvitation(forum0.getId(), contactId1From2, null,
clock.currentTimeMillis());
// sync second request message
sync2To1(1, true);
@@ -650,13 +556,6 @@ public class ForumSharingIntegrationTest
assertEquals(1, forums.size());
assertEquals(2, forums.iterator().next().getNewSharers().size());
assertEquals(forum0, forums.iterator().next().getShareable());
assertEquals(2,
forumSharingManager1.getSharedWith(forum0.getId()).size());
// make sure both sharers actually share the forum
Collection<Contact> contacts =
forumSharingManager1.getSharedWith(forum0.getId());
assertEquals(2, contacts.size());
// answer second request
assertNotNull(contactId2From1);
@@ -675,6 +574,11 @@ public class ForumSharingIntegrationTest
sync1To0(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
// make sure both sharers actually share the forum
Collection<Contact> contacts =
forumSharingManager1.getSharedWith(forum0.getId());
assertEquals(2, contacts.size());
}
@Test
@@ -684,7 +588,8 @@ public class ForumSharingIntegrationTest
// send invitation
forumSharingManager0
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
.sendInvitation(forum0.getId(), contactId1From0, "Hi!",
clock.currentTimeMillis());
// sync first request message
sync0To1(1, true);
@@ -745,7 +650,8 @@ public class ForumSharingIntegrationTest
// send invitation again
forumSharingManager0
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
.sendInvitation(forum0.getId(), contactId1From0, "Hi!",
clock.currentTimeMillis());
// sync first request message
sync0To1(1, true);
@@ -799,8 +705,10 @@ public class ForumSharingIntegrationTest
requestReceived = true;
Forum f = event.getShareable();
try {
Contact c = contactManager0.getContact(contactId1From0);
forumSharingManager0.respondToInvitation(f, c, true);
if (respond) {
Contact c = contactManager0.getContact(contactId1From0);
forumSharingManager0.respondToInvitation(f, c, true);
}
} catch (DbException ex) {
eventWaiter.rethrow(ex);
} finally {
@@ -814,7 +722,6 @@ public class ForumSharingIntegrationTest
private class InviteeListener implements EventListener {
private volatile boolean requestReceived = false;
private volatile boolean responseReceived = false;
private final boolean accept, answer;
@@ -836,13 +743,13 @@ public class ForumSharingIntegrationTest
if (!answer) return;
Forum f = event.getShareable();
try {
eventWaiter.assertEquals(1,
forumSharingManager1.getInvitations().size());
SharingInvitationItem invitation =
forumSharingManager1.getInvitations().iterator()
.next();
eventWaiter.assertEquals(f, invitation.getShareable());
if (respond) {
eventWaiter.assertEquals(1,
forumSharingManager1.getInvitations().size());
SharingInvitationItem invitation =
forumSharingManager1.getInvitations().iterator()
.next();
eventWaiter.assertEquals(f, invitation.getShareable());
Contact c =
contactManager1
.getContact(event.getContactId());
@@ -859,7 +766,6 @@ public class ForumSharingIntegrationTest
ForumInvitationResponseReceivedEvent event =
(ForumInvitationResponseReceivedEvent) e;
eventWaiter.assertEquals(contactId0From1, event.getContactId());
responseReceived = true;
eventWaiter.resume();
}
}

View File

@@ -4,340 +4,314 @@ import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.client.BdfMessageContext;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.test.ValidatorTestCase;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.forum.Forum;
import org.briarproject.briar.api.forum.ForumFactory;
import org.jmock.Expectations;
import org.junit.Test;
import java.util.Collection;
import javax.annotation.Nullable;
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_NAME;
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT_LENGTH;
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
import static org.briarproject.briar.api.sharing.SharingConstants.INVITATION_MSG;
import static org.briarproject.briar.api.sharing.SharingConstants.LOCAL;
import static org.briarproject.briar.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH;
import static org.briarproject.briar.api.sharing.SharingConstants.SESSION_ID;
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
import static org.briarproject.briar.api.sharing.SharingConstants.TIME;
import static org.briarproject.briar.api.sharing.SharingConstants.TYPE;
import static org.briarproject.briar.sharing.MessageType.ABORT;
import static org.briarproject.briar.sharing.MessageType.ACCEPT;
import static org.briarproject.briar.sharing.MessageType.DECLINE;
import static org.briarproject.briar.sharing.MessageType.INVITE;
import static org.briarproject.briar.sharing.MessageType.LEAVE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class ForumSharingValidatorTest extends ValidatorTestCase {
private final SessionId sessionId = new SessionId(TestUtils.getRandomId());
private final MessageEncoder messageEncoder =
context.mock(MessageEncoder.class);
private final ForumFactory forumFactory = context.mock(ForumFactory.class);
private final ForumSharingValidator v =
new ForumSharingValidator(messageEncoder, clientHelper,
metadataEncoder, clock, forumFactory);
private final MessageId previousMsgId = new MessageId(getRandomId());
private final String forumName =
TestUtils.getRandomString(MAX_FORUM_NAME_LENGTH);
private final byte[] salt = TestUtils.getRandomBytes(FORUM_SALT_LENGTH);
private final Forum forum = new Forum(group, forumName, salt);
private final BdfList descriptor = BdfList.of(forumName, salt);
private final String content =
TestUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH);
private final BdfDictionary meta =
BdfDictionary.of(new BdfEntry("meta", "data"));
@Test
public void testAcceptsInvitationWithContent() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
expectCreateForum(forumName);
expectEncodeMetadata(INVITE);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
salt, content));
assertExpectedContextForInvitation(messageContext, forumName, content);
BdfList.of(INVITE.getValue(), previousMsgId, descriptor,
content));
assertExpectedContext(messageContext, previousMsgId);
}
@Test
public void testAcceptsInvitationWithoutContent() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
public void testAcceptsInvitationWithNullContent() throws Exception {
expectCreateForum(forumName);
expectEncodeMetadata(INVITE);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
salt));
assertExpectedContextForInvitation(messageContext, forumName, null);
BdfList.of(INVITE.getValue(), previousMsgId, descriptor, null));
assertExpectedContext(messageContext, previousMsgId);
}
@Test
public void testAcceptsInvitationWithNullPreviousMsgId() throws Exception {
expectCreateForum(forumName);
expectEncodeMetadata(INVITE);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(INVITE.getValue(), null, descriptor, null));
assertExpectedContext(messageContext, null);
}
@Test
public void testAcceptsAccept() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
expectEncodeMetadata(ACCEPT);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_ACCEPT, sessionId));
assertExpectedContext(messageContext, SHARE_MSG_TYPE_ACCEPT);
BdfList.of(ACCEPT.getValue(), groupId, previousMsgId));
assertExpectedContext(messageContext, previousMsgId);
}
@Test
public void testAcceptsDecline() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
expectEncodeMetadata(DECLINE);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_DECLINE, sessionId));
assertExpectedContext(messageContext, SHARE_MSG_TYPE_DECLINE);
BdfList.of(DECLINE.getValue(), groupId, previousMsgId));
assertExpectedContext(messageContext, previousMsgId);
}
@Test
public void testAcceptsLeave() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
expectEncodeMetadata(LEAVE);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_LEAVE, sessionId));
assertExpectedContext(messageContext, SHARE_MSG_TYPE_LEAVE);
BdfList.of(LEAVE.getValue(), groupId, previousMsgId));
assertExpectedContext(messageContext, previousMsgId);
}
@Test
public void testAcceptsAbort() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
expectEncodeMetadata(ABORT);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_ABORT, sessionId));
assertExpectedContext(messageContext, SHARE_MSG_TYPE_ABORT);
BdfList.of(ABORT.getValue(), groupId, previousMsgId));
assertExpectedContext(messageContext, previousMsgId);
}
@Test(expected = FormatException.class)
public void testRejectsNullMessageType() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group, BdfList.of(null, sessionId));
v.validateMessage(message, group,
BdfList.of(null, groupId, previousMsgId));
}
@Test(expected = FormatException.class)
public void testRejectsNonLongMessageType() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group, BdfList.of("", sessionId));
v.validateMessage(message, group,
BdfList.of("", groupId, previousMsgId));
}
@Test(expected = FormatException.class)
public void testRejectsInvalidMessageType() throws Exception {
int invalidMessageType = SHARE_MSG_TYPE_ABORT + 1;
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
int invalidMessageType = ABORT.getValue() + 1;
v.validateMessage(message, group,
BdfList.of(invalidMessageType, sessionId));
BdfList.of(invalidMessageType, groupId, previousMsgId));
}
@Test(expected = FormatException.class)
public void testRejectsNullSessionId() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_ABORT, null));
BdfList.of(ABORT.getValue(), null, previousMsgId));
}
@Test(expected = FormatException.class)
public void testRejectsNonRawSessionId() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_ABORT, 123));
v.validateMessage(message, group, BdfList.of(ABORT.getValue(), 123));
}
@Test(expected = FormatException.class)
public void testRejectsTooShortSessionId() throws Exception {
byte[] invalidSessionId = TestUtils.getRandomBytes(UniqueId.LENGTH - 1);
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
byte[] invalidGroupId = TestUtils.getRandomBytes(UniqueId.LENGTH - 1);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_ABORT, invalidSessionId));
BdfList.of(ABORT.getValue(), invalidGroupId, previousMsgId));
}
@Test(expected = FormatException.class)
public void testRejectsTooLongSessionId() throws Exception {
byte[] invalidSessionId = TestUtils.getRandomBytes(UniqueId.LENGTH + 1);
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
byte[] invalidGroupId = TestUtils.getRandomBytes(UniqueId.LENGTH + 1);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_ABORT, invalidSessionId));
BdfList.of(ABORT.getValue(), invalidGroupId, previousMsgId));
}
@Test(expected = FormatException.class)
public void testRejectsTooShortBodyForAbort() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group, BdfList.of(SHARE_MSG_TYPE_ABORT));
v.validateMessage(message, group,
BdfList.of(ABORT.getValue(), groupId));
}
@Test(expected = FormatException.class)
public void testRejectsTooLongBodyForAbort() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_ABORT, sessionId, 123));
BdfList.of(ABORT.getValue(), groupId, previousMsgId, 123));
}
@Test(expected = FormatException.class)
public void testRejectsTooShortBodyForInvitation() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName));
BdfList.of(INVITE.getValue(), previousMsgId, descriptor));
}
@Test(expected = FormatException.class)
public void testRejectsTooLongBodyForInvitation() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
salt, content, 123));
BdfList.of(INVITE.getValue(), previousMsgId, descriptor, null,
123));
}
@Test(expected = FormatException.class)
public void testRejectsNullForumName() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
BdfList invalidDescriptor = BdfList.of(null, salt);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, null,
salt, content));
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
null));
}
@Test(expected = FormatException.class)
public void testRejectsNonStringForumName() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
BdfList invalidDescriptor = BdfList.of(123, salt);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, 123,
salt, content));
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
null));
}
@Test(expected = FormatException.class)
public void testRejectsTooShortForumName() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
BdfList invalidDescriptor = BdfList.of("", salt);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, "",
salt, content));
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
null));
}
@Test
public void testAcceptsMinLengthForumName() throws Exception {
String shortForumName = TestUtils.getRandomString(1);
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
BdfList validDescriptor = BdfList.of(shortForumName, salt);
expectCreateForum(shortForumName);
expectEncodeMetadata(INVITE);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, shortForumName,
salt, content));
assertExpectedContextForInvitation(messageContext, shortForumName,
content);
BdfList.of(INVITE.getValue(), previousMsgId, validDescriptor,
null));
assertExpectedContext(messageContext, previousMsgId);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongForumName() throws Exception {
String invalidForumName =
TestUtils.getRandomString(MAX_FORUM_NAME_LENGTH + 1);
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
BdfList invalidDescriptor = BdfList.of(invalidForumName, salt);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId,
invalidForumName, salt, content));
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
null));
}
@Test(expected = FormatException.class)
public void testRejectsNullSalt() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
BdfList invalidDescriptor = BdfList.of(forumName, null);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
null, content));
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
null));
}
@Test(expected = FormatException.class)
public void testRejectsNonRawSalt() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
BdfList invalidDescriptor = BdfList.of(forumName, 123);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
123, content));
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
null));
}
@Test(expected = FormatException.class)
public void testRejectsTooShortSalt() throws Exception {
byte[] invalidSalt = TestUtils.getRandomBytes(FORUM_SALT_LENGTH - 1);
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
BdfList invalidDescriptor = BdfList.of(forumName, invalidSalt);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
invalidSalt, content));
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
null));
}
@Test(expected = FormatException.class)
public void testRejectsTooLongSalt() throws Exception {
byte[] invalidSalt = TestUtils.getRandomBytes(FORUM_SALT_LENGTH + 1);
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
BdfList invalidDescriptor = BdfList.of(forumName, invalidSalt);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
invalidSalt, content));
}
@Test(expected = FormatException.class)
public void testRejectsNullContent() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
salt, null));
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
null));
}
@Test(expected = FormatException.class)
public void testRejectsNonStringContent() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
expectCreateForum(forumName);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
salt, 123));
BdfList.of(INVITE.getValue(), previousMsgId, descriptor,
123));
}
@Test
public void testAcceptsMinLengthContent() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
expectCreateForum(forumName);
expectEncodeMetadata(INVITE);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
salt, ""));
assertExpectedContextForInvitation(messageContext, forumName, "");
BdfList.of(INVITE.getValue(), previousMsgId, descriptor, "1"));
assertExpectedContext(messageContext, previousMsgId);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongContent() throws Exception {
String invalidContent =
TestUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH + 1);
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
expectCreateForum(forumName);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
salt, invalidContent));
BdfList.of(INVITE.getValue(), previousMsgId, descriptor,
invalidContent));
}
private void assertExpectedContextForInvitation(
BdfMessageContext messageContext, String forumName,
@Nullable String content) throws FormatException {
BdfDictionary meta = messageContext.getDictionary();
if (content == null) {
assertEquals(6, meta.size());
} else {
assertEquals(7, meta.size());
assertEquals(content, meta.getString(INVITATION_MSG));
}
assertEquals(forumName, meta.getString(FORUM_NAME));
assertEquals(salt, meta.getRaw(FORUM_SALT));
assertEquals(SHARE_MSG_TYPE_INVITATION, meta.getLong(TYPE).intValue());
assertEquals(sessionId.getBytes(), meta.getRaw(SESSION_ID));
assertFalse(meta.getBoolean(LOCAL));
assertEquals(timestamp, meta.getLong(TIME).longValue());
assertEquals(0, messageContext.getDependencies().size());
private void expectCreateForum(final String name) {
context.checking(new Expectations() {{
oneOf(forumFactory).createForum(name, salt);
will(returnValue(forum));
}});
}
private void expectEncodeMetadata(final MessageType type) {
context.checking(new Expectations() {{
oneOf(messageEncoder)
.encodeMetadata(type, groupId, timestamp, false, false,
false, false);
will(returnValue(meta));
}});
}
private void assertExpectedContext(BdfMessageContext messageContext,
int type) throws FormatException {
BdfDictionary meta = messageContext.getDictionary();
assertEquals(4, meta.size());
assertEquals(type, meta.getLong(TYPE).intValue());
assertEquals(sessionId.getBytes(), meta.getRaw(SESSION_ID));
assertFalse(meta.getBoolean(LOCAL));
assertEquals(timestamp, meta.getLong(TIME).longValue());
assertEquals(0, messageContext.getDependencies().size());
@Nullable MessageId previousMsgId) throws FormatException {
Collection<MessageId> dependencies = messageContext.getDependencies();
if (previousMsgId == null) {
assertTrue(dependencies.isEmpty());
} else {
assertEquals(1, dependencies.size());
assertTrue(dependencies.contains(previousMsgId));
}
assertEquals(meta, messageContext.getDictionary());
}
}