mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-16 12:49:55 +01:00
Factor out generic sharing code from ForumSharingManger
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
|
||||
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
|
||||
|
||||
public class ForumInviteeSessionState extends InviteeSessionState {
|
||||
|
||||
private final String forumName;
|
||||
private final byte[] forumSalt;
|
||||
|
||||
public ForumInviteeSessionState(SessionId sessionId, MessageId storageId,
|
||||
GroupId groupId, State state, ContactId contactId, GroupId forumId,
|
||||
String forumName, byte[] forumSalt) {
|
||||
super(sessionId, storageId, groupId, state, contactId, forumId);
|
||||
|
||||
this.forumName = forumName;
|
||||
this.forumSalt = forumSalt;
|
||||
}
|
||||
|
||||
public BdfDictionary toBdfDictionary() {
|
||||
BdfDictionary d = super.toBdfDictionary();
|
||||
d.put(FORUM_NAME, getForumName());
|
||||
d.put(FORUM_SALT, getForumSalt());
|
||||
return d;
|
||||
}
|
||||
|
||||
public String getForumName() {
|
||||
return forumName;
|
||||
}
|
||||
|
||||
public byte[] getForumSalt() {
|
||||
return forumSalt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
|
||||
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
|
||||
|
||||
public class ForumSharerSessionState extends SharerSessionState {
|
||||
|
||||
private final String forumName;
|
||||
private final byte[] forumSalt;
|
||||
|
||||
public ForumSharerSessionState(SessionId sessionId, MessageId storageId,
|
||||
GroupId groupId, State state, ContactId contactId, GroupId forumId,
|
||||
String forumName, byte[] forumSalt) {
|
||||
super(sessionId, storageId, groupId, state, contactId, forumId);
|
||||
|
||||
this.forumName = forumName;
|
||||
this.forumSalt = forumSalt;
|
||||
}
|
||||
|
||||
public BdfDictionary toBdfDictionary() {
|
||||
BdfDictionary d = super.toBdfDictionary();
|
||||
d.put(FORUM_NAME, getForumName());
|
||||
d.put(FORUM_SALT, getForumSalt());
|
||||
return d;
|
||||
}
|
||||
|
||||
public String getForumName() {
|
||||
return forumName;
|
||||
}
|
||||
|
||||
public byte[] getForumSalt() {
|
||||
return forumSalt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.clients.MessageQueueManager;
|
||||
import org.briarproject.api.clients.PrivateGroupFactory;
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.data.MetadataParser;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.Transaction;
|
||||
import org.briarproject.api.event.ForumInvitationReceivedEvent;
|
||||
import org.briarproject.api.event.ForumInvitationResponseReceivedEvent;
|
||||
import org.briarproject.api.forum.Forum;
|
||||
import org.briarproject.api.forum.ForumFactory;
|
||||
import org.briarproject.api.forum.ForumInvitationMessage;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
import org.briarproject.api.forum.ForumSharingManager;
|
||||
import org.briarproject.api.forum.ForumSharingMessage.ForumInvitation;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
|
||||
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
|
||||
|
||||
class ForumSharingManagerImpl extends
|
||||
SharingManagerImpl<Forum, ForumInvitation, ForumInvitationMessage, ForumInviteeSessionState, ForumSharerSessionState, ForumInvitationReceivedEvent, ForumInvitationResponseReceivedEvent>
|
||||
implements ForumSharingManager, ForumManager.RemoveForumHook {
|
||||
|
||||
static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
|
||||
"cd11a5d04dccd9e2931d6fc3df456313"
|
||||
+ "63bb3e9d9d0e9405fccdb051f41f5449"));
|
||||
|
||||
private final ForumManager forumManager;
|
||||
|
||||
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,
|
||||
PrivateGroupFactory privateGroupFactory,
|
||||
SecureRandom random) {
|
||||
super(db, messageQueueManager, clientHelper, metadataParser,
|
||||
metadataEncoder, random, privateGroupFactory, clock);
|
||||
this.forumManager = forumManager;
|
||||
|
||||
sFactory = new SFactory(forumFactory, forumManager);
|
||||
iFactory = new IFactory();
|
||||
isFactory = new ISFactory();
|
||||
ssFactory = new SSFactory();
|
||||
irFactory = new IRFactory(sFactory);
|
||||
irrFactory = new IRRFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientId getClientId() {
|
||||
return CLIENT_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClientId getShareableClientId() {
|
||||
return forumManager.getClientId();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ForumInvitationMessage createInvitationMessage(MessageId id,
|
||||
ForumInvitation msg, ContactId contactId, boolean available,
|
||||
long time, boolean local, boolean sent, boolean seen,
|
||||
boolean read) {
|
||||
return new ForumInvitationMessage(id, msg.getSessionId(), contactId,
|
||||
msg.getForumName(), msg.getMessage(), available, 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, ForumInvitationReceivedEvent> getIRFactory() {
|
||||
return irFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InvitationResponseReceivedEventFactory<ForumSharerSessionState, ForumInvitationResponseReceivedEvent> getIRRFactory() {
|
||||
return irrFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removingForum(Transaction txn, Forum f) throws DbException {
|
||||
removingShareable(txn, f);
|
||||
}
|
||||
|
||||
static class SFactory implements
|
||||
ShareableFactory<Forum, ForumInvitation, ForumInviteeSessionState, ForumSharerSessionState> {
|
||||
|
||||
private final ForumFactory forumFactory;
|
||||
private final ForumManager forumManager;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
return new ForumInvitation(localState.getGroupId(),
|
||||
localState.getSessionId(), localState.getForumName(),
|
||||
localState.getForumSalt(), localState.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
return new ForumInviteeSessionState(sessionId, storageId,
|
||||
groupId, state, contactId, forumId, forumName, forumSalt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForumInviteeSessionState build(SessionId sessionId,
|
||||
MessageId storageId, GroupId groupId,
|
||||
InviteeSessionState.State state, ContactId contactId,
|
||||
Forum forum) {
|
||||
return new ForumInviteeSessionState(sessionId, storageId,
|
||||
groupId, state, contactId, forum.getId(), forum.getName(),
|
||||
forum.getSalt());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
return new ForumSharerSessionState(sessionId, storageId,
|
||||
groupId, state, contactId, forumId, forumName, forumSalt);
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
}
|
||||
|
||||
static class IRFactory implements
|
||||
InvitationReceivedEventFactory<ForumInviteeSessionState, ForumInvitationReceivedEvent> {
|
||||
|
||||
private final SFactory sFactory;
|
||||
|
||||
IRFactory(SFactory sFactory) {
|
||||
this.sFactory = sFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForumInvitationReceivedEvent build(
|
||||
ForumInviteeSessionState localState) {
|
||||
Forum forum = sFactory.parse(localState);
|
||||
ContactId contactId = localState.getContactId();
|
||||
return new ForumInvitationReceivedEvent(forum, contactId);
|
||||
}
|
||||
}
|
||||
|
||||
static class IRRFactory implements
|
||||
InvitationResponseReceivedEventFactory<ForumSharerSessionState, ForumInvitationResponseReceivedEvent> {
|
||||
@Override
|
||||
public ForumInvitationResponseReceivedEvent build(
|
||||
ForumSharerSessionState localState) {
|
||||
String name = localState.getForumName();
|
||||
ContactId c = localState.getContactId();
|
||||
return new ForumInvitationResponseReceivedEvent(name, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
import org.briarproject.api.clients.BdfMessageContext;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.clients.BdfMessageValidator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
|
||||
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
|
||||
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
|
||||
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
|
||||
import static org.briarproject.api.sharing.SharingConstants.INVITATION_MSG;
|
||||
import static org.briarproject.api.sharing.SharingConstants.LOCAL;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SESSION_ID;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TIME;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TYPE;
|
||||
import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||
|
||||
class ForumSharingValidator extends BdfMessageValidator {
|
||||
|
||||
@Inject
|
||||
ForumSharingValidator(ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
super(clientHelper, metadataEncoder, clock);
|
||||
}
|
||||
|
||||
@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_MESSAGE_BODY_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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.sharing.SharingMessage;
|
||||
|
||||
public interface InvitationFactory<I extends SharingMessage.Invitation, SS extends SharerSessionState> extends
|
||||
org.briarproject.api.sharing.InvitationFactory<I> {
|
||||
|
||||
I build(SS localState);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.event.InvitationReceivedEvent;
|
||||
|
||||
public interface InvitationReceivedEventFactory<IS extends InviteeSessionState, IR extends InvitationReceivedEvent> {
|
||||
|
||||
IR build(IS localState);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.event.InvitationResponseReceivedEvent;
|
||||
|
||||
public interface InvitationResponseReceivedEventFactory<SS extends SharerSessionState, IRR extends InvitationResponseReceivedEvent> {
|
||||
|
||||
IRR build(SS localState);
|
||||
}
|
||||
222
briar-core/src/org/briarproject/sharing/InviteeEngine.java
Normal file
222
briar-core/src/org/briarproject/sharing/InviteeEngine.java
Normal file
@@ -0,0 +1,222 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ProtocolEngine;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.InvitationReceivedEvent;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TASK_ADD_SHAREABLE_TO_LIST_SHARED_WITH_US;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TASK_ADD_SHARED_SHAREABLE;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TASK_UNSHARE_SHAREABLE_SHARED_WITH_US;
|
||||
import static org.briarproject.api.sharing.SharingMessage.BaseMessage;
|
||||
import static org.briarproject.api.sharing.SharingMessage.SimpleMessage;
|
||||
|
||||
public class InviteeEngine<IS extends InviteeSessionState, IR extends InvitationReceivedEvent>
|
||||
implements ProtocolEngine<InviteeSessionState.Action, IS, BaseMessage> {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(InviteeEngine.class.getName());
|
||||
|
||||
private final InvitationReceivedEventFactory<IS, IR> invitationReceivedEventFactory;
|
||||
|
||||
InviteeEngine(InvitationReceivedEventFactory<IS, IR> invitationReceivedEventFactory) {
|
||||
this.invitationReceivedEventFactory = invitationReceivedEventFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<IS, BaseMessage> onLocalAction(
|
||||
IS localState, InviteeSessionState.Action action) {
|
||||
|
||||
try {
|
||||
InviteeSessionState.State currentState = localState.getState();
|
||||
InviteeSessionState.State nextState = currentState.next(action);
|
||||
localState.setState(nextState);
|
||||
|
||||
if (action == InviteeSessionState.Action.LOCAL_ABORT && currentState != InviteeSessionState.State.ERROR) {
|
||||
return abortSession(currentState, localState);
|
||||
}
|
||||
|
||||
if (nextState == InviteeSessionState.State.ERROR) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Error: Invalid action in state " +
|
||||
currentState.name());
|
||||
}
|
||||
return noUpdate(localState, true);
|
||||
}
|
||||
List<BaseMessage> messages;
|
||||
List<Event> events = Collections.emptyList();
|
||||
|
||||
if (action == InviteeSessionState.Action.LOCAL_ACCEPT || action == InviteeSessionState.Action.LOCAL_DECLINE) {
|
||||
BaseMessage msg;
|
||||
if (action == InviteeSessionState.Action.LOCAL_ACCEPT) {
|
||||
localState.setTask(TASK_ADD_SHARED_SHAREABLE);
|
||||
msg = new SimpleMessage(SHARE_MSG_TYPE_ACCEPT,
|
||||
localState.getGroupId(), localState.getSessionId());
|
||||
} else {
|
||||
localState.setTask(
|
||||
TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US);
|
||||
msg = new SimpleMessage(SHARE_MSG_TYPE_DECLINE,
|
||||
localState.getGroupId(), localState.getSessionId());
|
||||
}
|
||||
messages = Collections.singletonList(msg);
|
||||
logLocalAction(currentState, localState, msg);
|
||||
}
|
||||
else if (action == InviteeSessionState.Action.LOCAL_LEAVE) {
|
||||
BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_LEAVE,
|
||||
localState.getGroupId(), localState.getSessionId());
|
||||
messages = Collections.singletonList(msg);
|
||||
logLocalAction(currentState, localState, msg);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unknown Local Action");
|
||||
}
|
||||
return new StateUpdate<IS, BaseMessage>(false,
|
||||
false, localState, messages, events);
|
||||
} catch (FormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<IS, BaseMessage> onMessageReceived(
|
||||
IS localState, BaseMessage msg) {
|
||||
|
||||
try {
|
||||
InviteeSessionState.State currentState = localState.getState();
|
||||
InviteeSessionState.Action action = InviteeSessionState.Action.getRemote(msg.getType());
|
||||
InviteeSessionState.State nextState = currentState.next(action);
|
||||
localState.setState(nextState);
|
||||
|
||||
logMessageReceived(currentState, nextState, msg.getType(), msg);
|
||||
|
||||
if (nextState == InviteeSessionState.State.ERROR) {
|
||||
if (currentState != InviteeSessionState.State.ERROR) {
|
||||
return abortSession(currentState, localState);
|
||||
} else {
|
||||
return noUpdate(localState, true);
|
||||
}
|
||||
}
|
||||
|
||||
List<BaseMessage> messages = Collections.emptyList();
|
||||
List<Event> events = Collections.emptyList();
|
||||
boolean deleteMsg = false;
|
||||
|
||||
if (currentState == InviteeSessionState.State.LEFT) {
|
||||
// ignore and delete messages coming in while in that state
|
||||
deleteMsg = true;
|
||||
}
|
||||
// the sharer left the forum she had shared with us
|
||||
else if (action == InviteeSessionState.Action.REMOTE_LEAVE && currentState == InviteeSessionState.State.FINISHED) {
|
||||
localState.setTask(TASK_UNSHARE_SHAREABLE_SHARED_WITH_US);
|
||||
}
|
||||
else if (currentState == InviteeSessionState.State.FINISHED) {
|
||||
// ignore and delete messages coming in while in that state
|
||||
// note that LEAVE is possible, but was handled above
|
||||
deleteMsg = true;
|
||||
}
|
||||
// the sharer left the forum before we couldn't even respond
|
||||
else if (action == InviteeSessionState.Action.REMOTE_LEAVE) {
|
||||
localState.setTask(TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US);
|
||||
}
|
||||
// we have just received our invitation
|
||||
else if (action == InviteeSessionState.Action.REMOTE_INVITATION) {
|
||||
localState.setTask(TASK_ADD_SHAREABLE_TO_LIST_SHARED_WITH_US);
|
||||
Event event = invitationReceivedEventFactory.build(localState);
|
||||
events = Collections.singletonList(event);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Bad state");
|
||||
}
|
||||
return new StateUpdate<IS, BaseMessage>(deleteMsg,
|
||||
false, localState, messages, events);
|
||||
} catch (FormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void logLocalAction(InviteeSessionState.State state,
|
||||
InviteeSessionState localState, BaseMessage msg) {
|
||||
|
||||
if (!LOG.isLoggable(INFO)) return;
|
||||
|
||||
String a = "response";
|
||||
if (msg.getType() == SHARE_MSG_TYPE_LEAVE) a = "leave";
|
||||
|
||||
LOG.info("Sending " + a + " in state " + state.name() +
|
||||
" with session ID " +
|
||||
msg.getSessionId().hashCode() + " in group " +
|
||||
msg.getGroupId().hashCode() + ". " +
|
||||
"Moving on to state " + localState.getState().name()
|
||||
);
|
||||
}
|
||||
|
||||
private void logMessageReceived(InviteeSessionState.State currentState, InviteeSessionState.State nextState,
|
||||
long type, BaseMessage msg) {
|
||||
|
||||
if (!LOG.isLoggable(INFO)) return;
|
||||
|
||||
String t = "unknown";
|
||||
if (type == SHARE_MSG_TYPE_INVITATION) t = "INVITE";
|
||||
else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE";
|
||||
else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT";
|
||||
|
||||
LOG.info("Received " + t + " in state " + currentState.name() +
|
||||
" with session ID " +
|
||||
msg.getSessionId().hashCode() + " in group " +
|
||||
msg.getGroupId().hashCode() + ". " +
|
||||
"Moving on to state " + nextState.name()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<IS, BaseMessage> onMessageDelivered(
|
||||
IS localState, BaseMessage delivered) {
|
||||
try {
|
||||
return noUpdate(localState, false);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private StateUpdate<IS, BaseMessage> abortSession(
|
||||
InviteeSessionState.State currentState, IS localState)
|
||||
throws FormatException {
|
||||
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Aborting protocol session " +
|
||||
localState.getSessionId().hashCode() +
|
||||
" in state " + currentState.name());
|
||||
}
|
||||
localState.setState(InviteeSessionState.State.ERROR);
|
||||
BaseMessage msg =
|
||||
new SimpleMessage(SHARE_MSG_TYPE_ABORT, localState.getGroupId(),
|
||||
localState.getSessionId());
|
||||
List<BaseMessage> messages = Collections.singletonList(msg);
|
||||
|
||||
List<Event> events = Collections.emptyList();
|
||||
|
||||
return new StateUpdate<IS, BaseMessage>(false, false,
|
||||
localState, messages, events);
|
||||
}
|
||||
|
||||
private StateUpdate<IS, BaseMessage> noUpdate(
|
||||
IS localState, boolean delete) throws FormatException {
|
||||
|
||||
return new StateUpdate<IS, BaseMessage>(delete, false,
|
||||
localState, Collections.<BaseMessage>emptyList(),
|
||||
Collections.<Event>emptyList());
|
||||
}
|
||||
}
|
||||
119
briar-core/src/org/briarproject/sharing/InviteeSessionState.java
Normal file
119
briar-core/src/org/briarproject/sharing/InviteeSessionState.java
Normal file
@@ -0,0 +1,119 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import static org.briarproject.api.sharing.SharingConstants.IS_SHARER;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
|
||||
import static org.briarproject.api.sharing.SharingConstants.STATE;
|
||||
import static org.briarproject.sharing.InviteeSessionState.Action.LOCAL_ACCEPT;
|
||||
import static org.briarproject.sharing.InviteeSessionState.Action.LOCAL_DECLINE;
|
||||
import static org.briarproject.sharing.InviteeSessionState.Action.LOCAL_LEAVE;
|
||||
import static org.briarproject.sharing.InviteeSessionState.Action.REMOTE_INVITATION;
|
||||
import static org.briarproject.sharing.InviteeSessionState.Action.REMOTE_LEAVE;
|
||||
|
||||
// This class is not thread-safe
|
||||
public abstract class InviteeSessionState extends SharingSessionState {
|
||||
|
||||
private State state;
|
||||
|
||||
public InviteeSessionState(SessionId sessionId, MessageId storageId,
|
||||
GroupId groupId, State state, ContactId contactId,
|
||||
GroupId shareableId) {
|
||||
|
||||
super(sessionId, storageId, groupId, contactId, shareableId);
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public BdfDictionary toBdfDictionary() {
|
||||
BdfDictionary d = super.toBdfDictionary();
|
||||
d.put(STATE, getState().getValue());
|
||||
d.put(IS_SHARER, false);
|
||||
return d;
|
||||
}
|
||||
|
||||
public void setState(State state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public enum State {
|
||||
ERROR(0),
|
||||
AWAIT_INVITATION(1) {
|
||||
@Override
|
||||
public State next(Action a) {
|
||||
if (a == REMOTE_INVITATION) return AWAIT_LOCAL_RESPONSE;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
AWAIT_LOCAL_RESPONSE(2) {
|
||||
@Override
|
||||
public State next(Action a) {
|
||||
if (a == LOCAL_ACCEPT || a == LOCAL_DECLINE) return FINISHED;
|
||||
if (a == REMOTE_LEAVE) return LEFT;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
FINISHED(3) {
|
||||
@Override
|
||||
public State next(Action a) {
|
||||
if (a == LOCAL_LEAVE || a == REMOTE_LEAVE) return LEFT;
|
||||
return FINISHED;
|
||||
}
|
||||
},
|
||||
LEFT(4) {
|
||||
@Override
|
||||
public State next(Action a) {
|
||||
if (a == LOCAL_LEAVE) return ERROR;
|
||||
return LEFT;
|
||||
}
|
||||
};
|
||||
|
||||
private final int value;
|
||||
|
||||
State(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static State fromValue(int value) {
|
||||
for (State s : values()) {
|
||||
if (s.value == value) return s;
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public State next(Action a) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
LOCAL_ACCEPT,
|
||||
LOCAL_DECLINE,
|
||||
LOCAL_LEAVE,
|
||||
LOCAL_ABORT,
|
||||
REMOTE_INVITATION,
|
||||
REMOTE_LEAVE,
|
||||
REMOTE_ABORT;
|
||||
|
||||
public static Action getRemote(long type) {
|
||||
if (type == SHARE_MSG_TYPE_INVITATION) return REMOTE_INVITATION;
|
||||
if (type == SHARE_MSG_TYPE_LEAVE) return REMOTE_LEAVE;
|
||||
if (type == SHARE_MSG_TYPE_ABORT) return REMOTE_ABORT;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.sharing.Shareable;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
public interface InviteeSessionStateFactory<S extends Shareable, IS extends InviteeSessionState> {
|
||||
|
||||
IS build(SessionId sessionId, MessageId storageId, GroupId groupId,
|
||||
InviteeSessionState.State state, ContactId contactId,
|
||||
GroupId shareableId, BdfDictionary d) throws FormatException;
|
||||
|
||||
IS build(SessionId sessionId, MessageId storageId, GroupId groupId,
|
||||
InviteeSessionState.State state, ContactId contactId, S shareable);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.Transaction;
|
||||
import org.briarproject.api.sharing.Shareable;
|
||||
import org.briarproject.api.sharing.SharingMessage;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
interface ShareableFactory<S extends Shareable, I extends SharingMessage.Invitation, IS extends InviteeSessionState, SS extends SharerSessionState> {
|
||||
|
||||
BdfList encode(S sh);
|
||||
|
||||
S get(Transaction txn, GroupId groupId) throws DbException;
|
||||
|
||||
S parse(BdfList shareable) throws FormatException;
|
||||
|
||||
S parse(I msg);
|
||||
|
||||
S parse(IS state);
|
||||
|
||||
S parse(SS state);
|
||||
}
|
||||
225
briar-core/src/org/briarproject/sharing/SharerEngine.java
Normal file
225
briar-core/src/org/briarproject/sharing/SharerEngine.java
Normal file
@@ -0,0 +1,225 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ProtocolEngine;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.InvitationResponseReceivedEvent;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TASK_ADD_SHAREABLE_TO_LIST_TO_BE_SHARED_BY_US;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TASK_REMOVE_SHAREABLE_FROM_LIST_TO_BE_SHARED_BY_US;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TASK_SHARE_SHAREABLE;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TASK_UNSHARE_SHAREABLE_SHARED_BY_US;
|
||||
import static org.briarproject.api.sharing.SharingMessage.BaseMessage;
|
||||
import static org.briarproject.api.sharing.SharingMessage.Invitation;
|
||||
import static org.briarproject.api.sharing.SharingMessage.SimpleMessage;
|
||||
|
||||
public class SharerEngine<I extends Invitation, SS extends SharerSessionState, IRR extends InvitationResponseReceivedEvent>
|
||||
implements ProtocolEngine<SharerSessionState.Action, SS, BaseMessage> {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(SharerEngine.class.getName());
|
||||
|
||||
private final InvitationFactory<I, SS> invitationFactory;
|
||||
private final InvitationResponseReceivedEventFactory<SS, IRR>
|
||||
invitationResponseReceivedEventFactory;
|
||||
|
||||
SharerEngine(InvitationFactory<I, SS> invitationFactory,
|
||||
InvitationResponseReceivedEventFactory<SS, IRR> invitationResponseReceivedEventFactory) {
|
||||
this.invitationFactory = invitationFactory;
|
||||
this.invitationResponseReceivedEventFactory =
|
||||
invitationResponseReceivedEventFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<SS, BaseMessage> onLocalAction(
|
||||
SS localState, SharerSessionState.Action action) {
|
||||
|
||||
try {
|
||||
SharerSessionState.State currentState = localState.getState();
|
||||
SharerSessionState.State nextState = currentState.next(action);
|
||||
localState.setState(nextState);
|
||||
|
||||
if (action == SharerSessionState.Action.LOCAL_ABORT &&
|
||||
currentState != SharerSessionState.State.ERROR) {
|
||||
return abortSession(currentState, localState);
|
||||
}
|
||||
|
||||
if (nextState == SharerSessionState.State.ERROR) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Error: Invalid action in state " +
|
||||
currentState.name());
|
||||
}
|
||||
return noUpdate(localState, true);
|
||||
}
|
||||
List<BaseMessage> messages;
|
||||
List<Event> events = Collections.emptyList();
|
||||
|
||||
if (action == SharerSessionState.Action.LOCAL_INVITATION) {
|
||||
BaseMessage msg = invitationFactory.build(localState);
|
||||
messages = Collections.singletonList(msg);
|
||||
logLocalAction(currentState, nextState, msg);
|
||||
|
||||
// remember that we offered to share this forum
|
||||
localState
|
||||
.setTask(TASK_ADD_SHAREABLE_TO_LIST_TO_BE_SHARED_BY_US);
|
||||
} else if (action == SharerSessionState.Action.LOCAL_LEAVE) {
|
||||
BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_LEAVE,
|
||||
localState.getGroupId(), localState.getSessionId());
|
||||
messages = Collections.singletonList(msg);
|
||||
logLocalAction(currentState, nextState, msg);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown Local Action");
|
||||
}
|
||||
return new StateUpdate<SS, BaseMessage>(false,
|
||||
false, localState, messages, events);
|
||||
} catch (FormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<SS, BaseMessage> onMessageReceived(
|
||||
SS localState, BaseMessage msg) {
|
||||
|
||||
try {
|
||||
SharerSessionState.State currentState = localState.getState();
|
||||
SharerSessionState.Action action =
|
||||
SharerSessionState.Action.getRemote(msg.getType());
|
||||
SharerSessionState.State nextState = currentState.next(action);
|
||||
localState.setState(nextState);
|
||||
|
||||
logMessageReceived(currentState, nextState, msg.getType(), msg);
|
||||
|
||||
if (nextState == SharerSessionState.State.ERROR) {
|
||||
if (currentState != SharerSessionState.State.ERROR) {
|
||||
return abortSession(currentState, localState);
|
||||
} else {
|
||||
return noUpdate(localState, true);
|
||||
}
|
||||
}
|
||||
List<BaseMessage> messages = Collections.emptyList();
|
||||
List<Event> events = Collections.emptyList();
|
||||
boolean deleteMsg = false;
|
||||
|
||||
if (currentState == SharerSessionState.State.LEFT) {
|
||||
// ignore and delete messages coming in while in that state
|
||||
deleteMsg = true;
|
||||
} else if (action == SharerSessionState.Action.REMOTE_LEAVE) {
|
||||
localState.setTask(TASK_UNSHARE_SHAREABLE_SHARED_BY_US);
|
||||
} else if (currentState == SharerSessionState.State.FINISHED) {
|
||||
// ignore and delete messages coming in while in that state
|
||||
// note that LEAVE is possible, but was handled above
|
||||
deleteMsg = true;
|
||||
}
|
||||
// we have sent our invitation and just got a response
|
||||
else if (action == SharerSessionState.Action.REMOTE_ACCEPT ||
|
||||
action == SharerSessionState.Action.REMOTE_DECLINE) {
|
||||
if (action == SharerSessionState.Action.REMOTE_ACCEPT) {
|
||||
localState.setTask(TASK_SHARE_SHAREABLE);
|
||||
} else {
|
||||
// this ensures that the forum can be shared again
|
||||
localState.setTask(
|
||||
TASK_REMOVE_SHAREABLE_FROM_LIST_TO_BE_SHARED_BY_US);
|
||||
}
|
||||
Event event = invitationResponseReceivedEventFactory
|
||||
.build(localState);
|
||||
events = Collections.singletonList(event);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Bad state");
|
||||
}
|
||||
return new StateUpdate<SS, BaseMessage>(deleteMsg,
|
||||
false, localState, messages, events);
|
||||
} catch (FormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void logLocalAction(SharerSessionState.State currentState,
|
||||
SharerSessionState.State nextState,
|
||||
BaseMessage msg) {
|
||||
|
||||
if (!LOG.isLoggable(INFO)) return;
|
||||
|
||||
String a = "invitation";
|
||||
if (msg.getType() == SHARE_MSG_TYPE_LEAVE) a = "leave";
|
||||
|
||||
LOG.info("Sending " + a + " in state " + currentState.name() +
|
||||
" with session ID " +
|
||||
msg.getSessionId().hashCode() + " in group " +
|
||||
msg.getGroupId().hashCode() + ". " +
|
||||
"Moving on to state " + nextState.name()
|
||||
);
|
||||
}
|
||||
|
||||
private void logMessageReceived(SharerSessionState.State currentState,
|
||||
SharerSessionState.State nextState,
|
||||
long type, BaseMessage msg) {
|
||||
|
||||
if (!LOG.isLoggable(INFO)) return;
|
||||
|
||||
String t = "unknown";
|
||||
if (type == SHARE_MSG_TYPE_ACCEPT) t = "ACCEPT";
|
||||
else if (type == SHARE_MSG_TYPE_DECLINE) t = "DECLINE";
|
||||
else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE";
|
||||
else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT";
|
||||
|
||||
LOG.info("Received " + t + " in state " + currentState.name() +
|
||||
" with session ID " +
|
||||
msg.getSessionId().hashCode() + " in group " +
|
||||
msg.getGroupId().hashCode() + ". " +
|
||||
"Moving on to state " + nextState.name()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<SS, BaseMessage> onMessageDelivered(
|
||||
SS localState, BaseMessage delivered) {
|
||||
try {
|
||||
return noUpdate(localState, false);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private StateUpdate<SS, BaseMessage> abortSession(
|
||||
SharerSessionState.State currentState, SS localState)
|
||||
throws FormatException {
|
||||
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Aborting protocol session " +
|
||||
localState.getSessionId().hashCode() +
|
||||
" in state " + currentState.name());
|
||||
}
|
||||
|
||||
localState.setState(SharerSessionState.State.ERROR);
|
||||
BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_ABORT,
|
||||
localState.getGroupId(), localState.getSessionId());
|
||||
List<BaseMessage> messages = Collections.singletonList(msg);
|
||||
|
||||
List<Event> events = Collections.emptyList();
|
||||
|
||||
return new StateUpdate<SS, BaseMessage>(false, false,
|
||||
localState, messages, events);
|
||||
}
|
||||
|
||||
private StateUpdate<SS, BaseMessage> noUpdate(
|
||||
SS localState, boolean delete)
|
||||
throws FormatException {
|
||||
|
||||
return new StateUpdate<SS, BaseMessage>(delete, false,
|
||||
localState, Collections.<BaseMessage>emptyList(),
|
||||
Collections.<Event>emptyList());
|
||||
}
|
||||
|
||||
}
|
||||
130
briar-core/src/org/briarproject/sharing/SharerSessionState.java
Normal file
130
briar-core/src/org/briarproject/sharing/SharerSessionState.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import static org.briarproject.api.sharing.SharingConstants.IS_SHARER;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
|
||||
import static org.briarproject.api.sharing.SharingConstants.STATE;
|
||||
import static org.briarproject.sharing.SharerSessionState.Action.LOCAL_INVITATION;
|
||||
import static org.briarproject.sharing.SharerSessionState.Action.LOCAL_LEAVE;
|
||||
import static org.briarproject.sharing.SharerSessionState.Action.REMOTE_ACCEPT;
|
||||
import static org.briarproject.sharing.SharerSessionState.Action.REMOTE_DECLINE;
|
||||
import static org.briarproject.sharing.SharerSessionState.Action.REMOTE_LEAVE;
|
||||
|
||||
// This class is not thread-safe
|
||||
public abstract class SharerSessionState extends SharingSessionState {
|
||||
|
||||
private State state;
|
||||
private String msg = null;
|
||||
|
||||
public SharerSessionState(SessionId sessionId, MessageId storageId,
|
||||
GroupId groupId, State state, ContactId contactId,
|
||||
GroupId shareableId) {
|
||||
|
||||
super(sessionId, storageId, groupId, contactId, shareableId);
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public BdfDictionary toBdfDictionary() {
|
||||
BdfDictionary d = super.toBdfDictionary();
|
||||
d.put(STATE, getState().getValue());
|
||||
d.put(IS_SHARER, true);
|
||||
return d;
|
||||
}
|
||||
|
||||
public void setState(State state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setMessage(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return this.msg;
|
||||
}
|
||||
|
||||
public enum State {
|
||||
ERROR(0),
|
||||
PREPARE_INVITATION(1) {
|
||||
@Override
|
||||
public State next(Action a) {
|
||||
if (a == LOCAL_INVITATION) return AWAIT_RESPONSE;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
AWAIT_RESPONSE(2) {
|
||||
@Override
|
||||
public State next(Action a) {
|
||||
if (a == REMOTE_ACCEPT || a == REMOTE_DECLINE) return FINISHED;
|
||||
if (a == LOCAL_LEAVE) return LEFT;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
FINISHED(3) {
|
||||
@Override
|
||||
public State next(Action a) {
|
||||
if (a == LOCAL_LEAVE || a == REMOTE_LEAVE) return LEFT;
|
||||
return FINISHED;
|
||||
}
|
||||
},
|
||||
LEFT(4) {
|
||||
@Override
|
||||
public State next(Action a) {
|
||||
if (a == LOCAL_LEAVE) return ERROR;
|
||||
return LEFT;
|
||||
}
|
||||
};
|
||||
|
||||
private final int value;
|
||||
|
||||
State(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static State fromValue(int value) {
|
||||
for (State s : values()) {
|
||||
if (s.value == value) return s;
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public State next(Action a) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
LOCAL_INVITATION,
|
||||
LOCAL_LEAVE,
|
||||
LOCAL_ABORT,
|
||||
REMOTE_ACCEPT,
|
||||
REMOTE_DECLINE,
|
||||
REMOTE_LEAVE,
|
||||
REMOTE_ABORT;
|
||||
|
||||
public static Action getRemote(long type) {
|
||||
if (type == SHARE_MSG_TYPE_ACCEPT) return REMOTE_ACCEPT;
|
||||
if (type == SHARE_MSG_TYPE_DECLINE) return REMOTE_DECLINE;
|
||||
if (type == SHARE_MSG_TYPE_LEAVE) return REMOTE_LEAVE;
|
||||
if (type == SHARE_MSG_TYPE_ABORT) return REMOTE_ABORT;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.sharing.Shareable;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
public interface SharerSessionStateFactory<S extends Shareable, SS extends SharerSessionState> {
|
||||
|
||||
SS build(SessionId sessionId, MessageId storageId, GroupId groupId,
|
||||
SharerSessionState.State state, ContactId contactId,
|
||||
GroupId shareableId, BdfDictionary d) throws FormatException;
|
||||
|
||||
SS build(SessionId sessionId, MessageId storageId, GroupId groupId,
|
||||
SharerSessionState.State state, ContactId contactId, S shareable);
|
||||
}
|
||||
942
briar-core/src/org/briarproject/sharing/SharingManagerImpl.java
Normal file
942
briar-core/src/org/briarproject/sharing/SharingManagerImpl.java
Normal file
@@ -0,0 +1,942 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.Bytes;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.Client;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.clients.MessageQueueManager;
|
||||
import org.briarproject.api.clients.PrivateGroupFactory;
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.contact.ContactManager.AddContactHook;
|
||||
import org.briarproject.api.contact.ContactManager.RemoveContactHook;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfEntry;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.data.MetadataParser;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.db.NoSuchMessageException;
|
||||
import org.briarproject.api.db.Transaction;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.InvitationReceivedEvent;
|
||||
import org.briarproject.api.event.InvitationResponseReceivedEvent;
|
||||
import org.briarproject.api.identity.LocalAuthor;
|
||||
import org.briarproject.api.sharing.InvitationMessage;
|
||||
import org.briarproject.api.sharing.Shareable;
|
||||
import org.briarproject.api.sharing.SharingManager;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.MessageStatus;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.clients.BdfIncomingMessageHook;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.api.clients.ProtocolEngine.StateUpdate;
|
||||
import static org.briarproject.api.sharing.SharingConstants.CONTACT_ID;
|
||||
import static org.briarproject.api.sharing.SharingConstants.IS_SHARER;
|
||||
import static org.briarproject.api.sharing.SharingConstants.LOCAL;
|
||||
import static org.briarproject.api.sharing.SharingConstants.READ;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SESSION_ID;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHAREABLE_ID;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARED_BY_US;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARED_WITH_US;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHARING_SALT_LENGTH;
|
||||
import static org.briarproject.api.sharing.SharingConstants.STATE;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TASK_ADD_SHAREABLE_TO_LIST_SHARED_WITH_US;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TASK_ADD_SHAREABLE_TO_LIST_TO_BE_SHARED_BY_US;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TASK_ADD_SHARED_SHAREABLE;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TASK_REMOVE_SHAREABLE_FROM_LIST_TO_BE_SHARED_BY_US;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TASK_SHARE_SHAREABLE;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TASK_UNSHARE_SHAREABLE_SHARED_BY_US;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TASK_UNSHARE_SHAREABLE_SHARED_WITH_US;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TIME;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TO_BE_SHARED_BY_US;
|
||||
import static org.briarproject.api.sharing.SharingConstants.TYPE;
|
||||
import static org.briarproject.api.sharing.SharingMessage.BaseMessage;
|
||||
import static org.briarproject.api.sharing.SharingMessage.Invitation;
|
||||
|
||||
abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM extends InvitationMessage, IS extends InviteeSessionState, SS extends SharerSessionState, IR extends InvitationReceivedEvent, IRR extends InvitationResponseReceivedEvent>
|
||||
extends BdfIncomingMessageHook
|
||||
implements SharingManager<S, IM>, Client, AddContactHook,
|
||||
RemoveContactHook {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(SharingManagerImpl.class.getName());
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final MessageQueueManager messageQueueManager;
|
||||
private final MetadataEncoder metadataEncoder;
|
||||
private final SecureRandom random;
|
||||
private final PrivateGroupFactory privateGroupFactory;
|
||||
private final Clock clock;
|
||||
private final Group localGroup;
|
||||
|
||||
SharingManagerImpl(DatabaseComponent db,
|
||||
MessageQueueManager messageQueueManager, ClientHelper clientHelper,
|
||||
MetadataParser metadataParser, MetadataEncoder metadataEncoder,
|
||||
SecureRandom random, PrivateGroupFactory privateGroupFactory,
|
||||
Clock clock) {
|
||||
|
||||
super(clientHelper, metadataParser);
|
||||
this.db = db;
|
||||
this.messageQueueManager = messageQueueManager;
|
||||
this.metadataEncoder = metadataEncoder;
|
||||
this.random = random;
|
||||
this.privateGroupFactory = privateGroupFactory;
|
||||
this.clock = clock;
|
||||
localGroup = privateGroupFactory.createLocalGroup(getClientId());
|
||||
}
|
||||
|
||||
public abstract ClientId getClientId();
|
||||
|
||||
protected abstract ClientId getShareableClientId();
|
||||
|
||||
protected abstract IM createInvitationMessage(MessageId id, I msg,
|
||||
ContactId contactId, boolean available, long time, boolean local,
|
||||
boolean sent, boolean seen, boolean read);
|
||||
|
||||
protected abstract ShareableFactory<S, I, IS, SS> getSFactory();
|
||||
|
||||
protected abstract InvitationFactory<I, SS> getIFactory();
|
||||
|
||||
protected abstract InviteeSessionStateFactory<S, IS> getISFactory();
|
||||
|
||||
protected abstract SharerSessionStateFactory<S, SS> getSSFactory();
|
||||
|
||||
protected abstract InvitationReceivedEventFactory<IS, IR> getIRFactory();
|
||||
|
||||
protected abstract InvitationResponseReceivedEventFactory<SS, IRR> getIRRFactory();
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addingContact(Transaction txn, Contact c) throws DbException {
|
||||
try {
|
||||
// Create a group to share with the contact
|
||||
Group g = getContactGroup(c);
|
||||
// Return if we've already set things up for this contact
|
||||
if (db.containsGroup(txn, g.getId())) return;
|
||||
// Store the group and share it with the contact
|
||||
db.addGroup(txn, g);
|
||||
db.setVisibleToContact(txn, c.getId(), g.getId(), true);
|
||||
// Attach the contact ID to the group
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(CONTACT_ID, c.getId().getInt());
|
||||
meta.put(TO_BE_SHARED_BY_US, new BdfList());
|
||||
meta.put(SHARED_BY_US, new BdfList());
|
||||
meta.put(SHARED_WITH_US, new BdfList());
|
||||
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removingContact(Transaction txn, Contact c) throws DbException {
|
||||
// query for this contact c
|
||||
BdfDictionary query = BdfDictionary.of(
|
||||
new BdfEntry(CONTACT_ID, c.getId().getInt())
|
||||
);
|
||||
|
||||
// clean up session states with that contact from localGroup
|
||||
try {
|
||||
Map<MessageId, BdfDictionary> map = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, localGroup.getId(),
|
||||
query);
|
||||
for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
|
||||
deleteMessage(txn, entry.getKey());
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
|
||||
// remove the contact group (all messages will be removed with it)
|
||||
db.removeGroup(txn, getContactGroup(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void incomingMessage(Transaction txn, Message m, BdfList body,
|
||||
BdfDictionary d) throws DbException, FormatException {
|
||||
|
||||
BaseMessage msg = BaseMessage.from(getIFactory(), m.getGroupId(), d);
|
||||
SessionId sessionId = msg.getSessionId();
|
||||
|
||||
if (msg.getType() == SHARE_MSG_TYPE_INVITATION) {
|
||||
// we are an invitee who just received a new invitation
|
||||
boolean stateExists = true;
|
||||
try {
|
||||
// check if we have a session with that ID already
|
||||
getSessionState(txn, sessionId, false);
|
||||
} catch (FormatException e) {
|
||||
// this is what we would expect under normal circumstances
|
||||
stateExists = false;
|
||||
}
|
||||
try {
|
||||
// check if we already have a state with that sessionId
|
||||
if (stateExists) throw new FormatException();
|
||||
|
||||
// check if shareable can be shared
|
||||
I invitation = (I) msg;
|
||||
S f = getSFactory().parse(invitation);
|
||||
ContactId contactId = getContactId(txn, m.getGroupId());
|
||||
Contact contact = db.getContact(txn, contactId);
|
||||
if (!canBeShared(txn, f.getId(), contact))
|
||||
checkForRaceCondition(txn, f, contact);
|
||||
|
||||
// initialize state and process invitation
|
||||
IS state = initializeInviteeState(txn, contactId, invitation);
|
||||
InviteeEngine<IS, IR> engine =
|
||||
new InviteeEngine<IS, IR>(getIRFactory());
|
||||
processInviteeStateUpdate(txn, m.getId(),
|
||||
engine.onMessageReceived(state, msg));
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
deleteMessage(txn, m.getId());
|
||||
}
|
||||
} else if (msg.getType() == SHARE_MSG_TYPE_ACCEPT ||
|
||||
msg.getType() == SHARE_MSG_TYPE_DECLINE) {
|
||||
// we are a sharer who just received a response
|
||||
SS state = getSessionStateForSharer(txn, sessionId);
|
||||
SharerEngine<I, SS, IRR> engine =
|
||||
new SharerEngine<I, SS, IRR>(getIFactory(),
|
||||
getIRRFactory());
|
||||
processSharerStateUpdate(txn, m.getId(),
|
||||
engine.onMessageReceived(state, msg));
|
||||
} else if (msg.getType() == SHARE_MSG_TYPE_LEAVE ||
|
||||
msg.getType() == SHARE_MSG_TYPE_ABORT) {
|
||||
// we don't know who we are, so figure it out
|
||||
SharingSessionState s = getSessionState(txn, sessionId, true);
|
||||
if (s instanceof SharerSessionState) {
|
||||
// we are a sharer and the invitee wants to leave or abort
|
||||
SS state = (SS) s;
|
||||
SharerEngine<I, SS, IRR> engine =
|
||||
new SharerEngine<I, SS, IRR>(getIFactory(),
|
||||
getIRRFactory());
|
||||
processSharerStateUpdate(txn, m.getId(),
|
||||
engine.onMessageReceived(state, msg));
|
||||
} else {
|
||||
// we are an invitee and the sharer wants to leave or abort
|
||||
IS state = (IS) s;
|
||||
InviteeEngine<IS, IR> engine =
|
||||
new InviteeEngine<IS, IR>(getIRFactory());
|
||||
processInviteeStateUpdate(txn, m.getId(),
|
||||
engine.onMessageReceived(state, msg));
|
||||
}
|
||||
} else {
|
||||
// message has passed validator, so that should never happen
|
||||
throw new RuntimeException("Illegal Sharing Message");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendInvitation(GroupId groupId, ContactId contactId,
|
||||
String msg) throws DbException {
|
||||
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
// initialize local state for sharer
|
||||
S f = getSFactory().get(txn, groupId);
|
||||
SS localState = initializeSharerState(txn, f, contactId);
|
||||
|
||||
// add invitation message to local state to be available for engine
|
||||
if (!StringUtils.isNullOrEmpty(msg)) {
|
||||
localState.setMessage(msg);
|
||||
}
|
||||
|
||||
// start engine and process its state update
|
||||
SharerEngine<I, SS, IRR> engine =
|
||||
new SharerEngine<I, SS, IRR>(getIFactory(),
|
||||
getIRRFactory());
|
||||
processSharerStateUpdate(txn, null,
|
||||
engine.onLocalAction(localState,
|
||||
SharerSessionState.Action.LOCAL_INVITATION));
|
||||
|
||||
txn.setComplete();
|
||||
} catch (FormatException e) {
|
||||
throw new DbException();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void respondToInvitation(S f, Contact c, boolean accept)
|
||||
throws DbException {
|
||||
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
// find session state based on shareable
|
||||
IS localState = getSessionStateForResponse(txn, f, c);
|
||||
|
||||
// define action
|
||||
InviteeSessionState.Action localAction;
|
||||
if (accept) {
|
||||
localAction = InviteeSessionState.Action.LOCAL_ACCEPT;
|
||||
} else {
|
||||
localAction = InviteeSessionState.Action.LOCAL_DECLINE;
|
||||
}
|
||||
|
||||
// start engine and process its state update
|
||||
InviteeEngine<IS, IR> engine =
|
||||
new InviteeEngine<IS, IR>(getIRFactory());
|
||||
processInviteeStateUpdate(txn, null,
|
||||
engine.onLocalAction(localState, localAction));
|
||||
|
||||
txn.setComplete();
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<IM> getInvitationMessages(ContactId contactId)
|
||||
throws DbException {
|
||||
|
||||
// query for all invitations
|
||||
BdfDictionary query = BdfDictionary.of(
|
||||
new BdfEntry(TYPE, SHARE_MSG_TYPE_INVITATION)
|
||||
);
|
||||
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
Contact contact = db.getContact(txn, contactId);
|
||||
Group group = getContactGroup(contact);
|
||||
|
||||
Collection<IM> list = new ArrayList<IM>();
|
||||
Map<MessageId, BdfDictionary> map = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, group.getId(), query);
|
||||
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
|
||||
BdfDictionary d = m.getValue();
|
||||
try {
|
||||
I msg = getIFactory().build(group.getId(), d);
|
||||
MessageStatus status =
|
||||
db.getMessageStatus(txn, contactId, m.getKey());
|
||||
long time = d.getLong(TIME);
|
||||
boolean local = d.getBoolean(LOCAL);
|
||||
boolean read = d.getBoolean(READ, false);
|
||||
boolean available = false;
|
||||
if (!local) {
|
||||
// figure out whether the shareable is still available
|
||||
SharingSessionState s =
|
||||
getSessionState(txn, msg.getSessionId(), true);
|
||||
if (!(s instanceof InviteeSessionState))
|
||||
continue;
|
||||
available = ((InviteeSessionState) s).getState() ==
|
||||
InviteeSessionState.State.AWAIT_LOCAL_RESPONSE;
|
||||
}
|
||||
IM im = createInvitationMessage(m.getKey(), msg, contactId,
|
||||
available, time, local, status.isSent(),
|
||||
status.isSeen(), read);
|
||||
list.add(im);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
txn.setComplete();
|
||||
return list;
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<S> getAvailable() throws DbException {
|
||||
try {
|
||||
Set<S> available = new HashSet<S>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
// Get any shareables we subscribe to
|
||||
Set<Group> subscribed = new HashSet<Group>(db.getGroups(txn,
|
||||
getShareableClientId()));
|
||||
// Get all shareables shared by contacts
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
Group g = getContactGroup(c);
|
||||
List<S> shareables =
|
||||
getShareableList(txn, g.getId(), SHARED_WITH_US);
|
||||
for (S f : shareables) {
|
||||
if (!subscribed.contains(f.getGroup()))
|
||||
available.add(f);
|
||||
}
|
||||
}
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
return Collections.unmodifiableSet(available);
|
||||
} catch (IOException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Contact> getSharedBy(GroupId g) throws DbException {
|
||||
try {
|
||||
List<Contact> subscribers = new ArrayList<Contact>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
GroupId contactGroup = getContactGroup(c).getId();
|
||||
if (listContains(txn, contactGroup, g, SHARED_WITH_US))
|
||||
subscribers.add(c);
|
||||
}
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
return Collections.unmodifiableList(subscribers);
|
||||
} catch (IOException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Contact> getSharedWith(GroupId g) throws DbException {
|
||||
try {
|
||||
List<Contact> shared = new ArrayList<Contact>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
GroupId contactGroup = getContactGroup(c).getId();
|
||||
if (listContains(txn, contactGroup, g, SHARED_BY_US))
|
||||
shared.add(c);
|
||||
}
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
return Collections.unmodifiableList(shared);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeShared(GroupId g, Contact c) throws DbException {
|
||||
boolean canBeShared;
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
canBeShared = canBeShared(txn, g, c);
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
return canBeShared;
|
||||
}
|
||||
|
||||
private boolean canBeShared(Transaction txn, GroupId g, Contact c)
|
||||
throws DbException {
|
||||
|
||||
try {
|
||||
GroupId contactGroup = getContactGroup(c).getId();
|
||||
return !listContains(txn, contactGroup, g, SHARED_BY_US) &&
|
||||
!listContains(txn, contactGroup, g, SHARED_WITH_US) &&
|
||||
!listContains(txn, contactGroup, g, TO_BE_SHARED_BY_US);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void removingShareable(Transaction txn, S f) throws DbException {
|
||||
try {
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
GroupId g = getContactGroup(c).getId();
|
||||
if (removeFromList(txn, g, TO_BE_SHARED_BY_US, f)) {
|
||||
leaveShareable(txn, c.getId(), f);
|
||||
}
|
||||
if (removeFromList(txn, g, SHARED_BY_US, f)) {
|
||||
leaveShareable(txn, c.getId(), f);
|
||||
}
|
||||
if (removeFromList(txn, g, SHARED_WITH_US, f)) {
|
||||
leaveShareable(txn, c.getId(), f);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForRaceCondition(Transaction txn, S f, Contact c)
|
||||
throws FormatException, DbException {
|
||||
|
||||
GroupId contactGroup = getContactGroup(c).getId();
|
||||
if (!listContains(txn, contactGroup, f.getId(), TO_BE_SHARED_BY_US))
|
||||
// no race-condition, this invitation is invalid
|
||||
throw new FormatException();
|
||||
|
||||
// we have an invitation race condition
|
||||
LocalAuthor author = db.getLocalAuthor(txn, c.getLocalAuthorId());
|
||||
Bytes ourKey = new Bytes(author.getPublicKey());
|
||||
Bytes theirKey = new Bytes(c.getAuthor().getPublicKey());
|
||||
|
||||
// determine which invitation takes precedence
|
||||
boolean alice = ourKey.compareTo(theirKey) < 0;
|
||||
|
||||
if (alice) {
|
||||
// our own invitation takes precedence, so just delete Bob's
|
||||
LOG.info(
|
||||
"Invitation race-condition: We are Alice deleting Bob's invitation.");
|
||||
throw new FormatException();
|
||||
} else {
|
||||
// we are Bob, so we need to "take back" our own invitation
|
||||
LOG.info(
|
||||
"Invitation race-condition: We are Bob taking back our invitation.");
|
||||
SharingSessionState state =
|
||||
getSessionStateForLeaving(txn, f, c.getId());
|
||||
if (state instanceof SharerSessionState) {
|
||||
//SharerEngine engine = new SharerEngine();
|
||||
//processSharerStateUpdate(txn, null,
|
||||
// engine.onLocalAction((SharerSessionState) state,
|
||||
// Action.LOCAL_LEAVE));
|
||||
|
||||
// simply remove from list instead of involving engine
|
||||
removeFromList(txn, contactGroup, TO_BE_SHARED_BY_US, f);
|
||||
// TODO here we could also remove the old session state
|
||||
// and invitation message
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private SS initializeSharerState(Transaction txn, S f,
|
||||
ContactId contactId) throws FormatException, DbException {
|
||||
|
||||
Contact c = db.getContact(txn, contactId);
|
||||
Group group = getContactGroup(c);
|
||||
|
||||
// create local message to keep engine state
|
||||
long now = clock.currentTimeMillis();
|
||||
Bytes salt = new Bytes(new byte[SHARING_SALT_LENGTH]);
|
||||
random.nextBytes(salt.getBytes());
|
||||
Message m = clientHelper.createMessage(localGroup.getId(), now,
|
||||
BdfList.of(salt));
|
||||
SessionId sessionId = new SessionId(m.getId().getBytes());
|
||||
|
||||
SS s = getSSFactory().build(sessionId, sessionId,
|
||||
group.getId(), SharerSessionState.State.PREPARE_INVITATION,
|
||||
contactId, f);
|
||||
|
||||
// save local state to database
|
||||
BdfDictionary d = s.toBdfDictionary();
|
||||
clientHelper.addLocalMessage(txn, m, getClientId(), d, false);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
private IS initializeInviteeState(Transaction txn,
|
||||
ContactId contactId, I msg)
|
||||
throws FormatException, DbException {
|
||||
|
||||
Contact c = db.getContact(txn, contactId);
|
||||
Group group = getContactGroup(c);
|
||||
S f = getSFactory().parse(msg);
|
||||
|
||||
// create local message to keep engine state
|
||||
long now = clock.currentTimeMillis();
|
||||
Bytes mSalt = new Bytes(new byte[SHARING_SALT_LENGTH]);
|
||||
random.nextBytes(mSalt.getBytes());
|
||||
Message m = clientHelper.createMessage(localGroup.getId(), now,
|
||||
BdfList.of(mSalt));
|
||||
|
||||
IS s = getISFactory().build(msg.getSessionId(),
|
||||
m.getId(), group.getId(),
|
||||
InviteeSessionState.State.AWAIT_INVITATION, contactId, f);
|
||||
|
||||
// save local state to database
|
||||
BdfDictionary d = s.toBdfDictionary();
|
||||
clientHelper.addLocalMessage(txn, m, getClientId(), d, false);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
private SharingSessionState getSessionState(Transaction txn,
|
||||
SessionId sessionId, boolean warn)
|
||||
throws DbException, FormatException {
|
||||
|
||||
try {
|
||||
return getSessionStateForSharer(txn, sessionId);
|
||||
} catch (NoSuchMessageException e) {
|
||||
// State not found directly, so query for state for invitee
|
||||
BdfDictionary query = BdfDictionary.of(
|
||||
new BdfEntry(SESSION_ID, sessionId)
|
||||
);
|
||||
|
||||
Map<MessageId, BdfDictionary> map = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, localGroup.getId(),
|
||||
query);
|
||||
|
||||
if (map.size() > 1 && LOG.isLoggable(WARNING)) {
|
||||
LOG.warning(
|
||||
"More than one session state found for message with session ID " +
|
||||
Arrays.hashCode(sessionId.getBytes()));
|
||||
}
|
||||
if (map.isEmpty()) {
|
||||
if (warn && LOG.isLoggable(WARNING)) {
|
||||
LOG.warning(
|
||||
"No session state found for message with session ID " +
|
||||
Arrays.hashCode(sessionId.getBytes()));
|
||||
}
|
||||
throw new FormatException();
|
||||
}
|
||||
return SharingSessionState
|
||||
.fromBdfDictionary(getISFactory(), getSSFactory(),
|
||||
map.values().iterator().next());
|
||||
}
|
||||
}
|
||||
|
||||
private SS getSessionStateForSharer(Transaction txn,
|
||||
SessionId sessionId)
|
||||
throws DbException, FormatException {
|
||||
|
||||
// we should be able to get the sharer state directly from sessionId
|
||||
BdfDictionary d =
|
||||
clientHelper.getMessageMetadataAsDictionary(txn, sessionId);
|
||||
|
||||
if (!d.getBoolean(IS_SHARER)) throw new FormatException();
|
||||
|
||||
return (SS) SharingSessionState
|
||||
.fromBdfDictionary(getISFactory(), getSSFactory(), d);
|
||||
}
|
||||
|
||||
private IS getSessionStateForResponse(Transaction txn,
|
||||
S f, Contact c) throws DbException, FormatException {
|
||||
|
||||
// query for invitee states for that shareable in state await response
|
||||
BdfDictionary query = BdfDictionary.of(
|
||||
new BdfEntry(IS_SHARER, false),
|
||||
new BdfEntry(CONTACT_ID, c.getId().getInt()),
|
||||
new BdfEntry(SHAREABLE_ID, f.getId()),
|
||||
new BdfEntry(STATE,
|
||||
InviteeSessionState.State.AWAIT_LOCAL_RESPONSE
|
||||
.getValue())
|
||||
);
|
||||
|
||||
Map<MessageId, BdfDictionary> map = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, localGroup.getId(), query);
|
||||
|
||||
if (map.size() > 1 && LOG.isLoggable(WARNING)) {
|
||||
LOG.warning(
|
||||
"More than one session state found for shareable with ID " +
|
||||
Arrays.hashCode(f.getId().getBytes()) +
|
||||
" in state AWAIT_LOCAL_RESPONSE for contact " +
|
||||
c.getAuthor().getName());
|
||||
}
|
||||
if (map.isEmpty()) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning(
|
||||
"No session state found for shareable with ID " +
|
||||
Arrays.hashCode(f.getId().getBytes()) +
|
||||
" in state AWAIT_LOCAL_RESPONSE");
|
||||
}
|
||||
throw new DbException();
|
||||
}
|
||||
return (IS) SharingSessionState
|
||||
.fromBdfDictionary(getISFactory(), getSSFactory(),
|
||||
map.values().iterator().next());
|
||||
}
|
||||
|
||||
private SharingSessionState getSessionStateForLeaving(Transaction txn,
|
||||
S f, ContactId c) throws DbException, FormatException {
|
||||
|
||||
BdfDictionary query = BdfDictionary.of(
|
||||
new BdfEntry(CONTACT_ID, c.getInt()),
|
||||
new BdfEntry(SHAREABLE_ID, f.getId())
|
||||
);
|
||||
Map<MessageId, BdfDictionary> map = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, localGroup.getId(), query);
|
||||
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
|
||||
BdfDictionary d = m.getValue();
|
||||
try {
|
||||
SharingSessionState s = SharingSessionState
|
||||
.fromBdfDictionary(getISFactory(), getSSFactory(), d);
|
||||
|
||||
// check that a shareable get be left in current session
|
||||
if (s instanceof SharerSessionState) {
|
||||
SharerSessionState state = (SharerSessionState) s;
|
||||
SharerSessionState.State nextState =
|
||||
state.getState()
|
||||
.next(SharerSessionState.Action.LOCAL_LEAVE);
|
||||
if (nextState != SharerSessionState.State.ERROR) {
|
||||
return state;
|
||||
}
|
||||
} else {
|
||||
InviteeSessionState state = (InviteeSessionState) s;
|
||||
InviteeSessionState.State nextState = state.getState()
|
||||
.next(InviteeSessionState.Action.LOCAL_LEAVE);
|
||||
if (nextState != InviteeSessionState.State.ERROR) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
throw new FormatException();
|
||||
}
|
||||
|
||||
private void processStateUpdate(Transaction txn, MessageId messageId,
|
||||
StateUpdate<SharingSessionState, BaseMessage> result, S f)
|
||||
throws DbException, FormatException {
|
||||
|
||||
// perform actions based on new local state
|
||||
performTasks(txn, result.localState, f);
|
||||
|
||||
// save new local state
|
||||
MessageId storageId = result.localState.getStorageId();
|
||||
clientHelper.mergeMessageMetadata(txn, storageId,
|
||||
result.localState.toBdfDictionary());
|
||||
|
||||
// send messages
|
||||
for (BaseMessage msg : result.toSend) {
|
||||
sendMessage(txn, msg);
|
||||
}
|
||||
|
||||
// broadcast events
|
||||
for (Event event : result.toBroadcast) {
|
||||
txn.attach(event);
|
||||
}
|
||||
|
||||
// delete message
|
||||
if (result.deleteMessage && messageId != null) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Deleting message with id " + messageId.hashCode());
|
||||
}
|
||||
db.deleteMessage(txn, messageId);
|
||||
db.deleteMessageMetadata(txn, messageId);
|
||||
}
|
||||
}
|
||||
|
||||
private void processSharerStateUpdate(Transaction txn, MessageId messageId,
|
||||
StateUpdate<SS, BaseMessage> result)
|
||||
throws DbException, FormatException {
|
||||
|
||||
StateUpdate<SharingSessionState, BaseMessage> r =
|
||||
new StateUpdate<SharingSessionState, BaseMessage>(
|
||||
result.deleteMessage, result.deleteState,
|
||||
result.localState, result.toSend, result.toBroadcast);
|
||||
|
||||
// get shareable for later
|
||||
S f = getSFactory().parse(result.localState);
|
||||
|
||||
processStateUpdate(txn, messageId, r, f);
|
||||
}
|
||||
|
||||
private void processInviteeStateUpdate(Transaction txn, MessageId messageId,
|
||||
StateUpdate<IS, BaseMessage> result)
|
||||
throws DbException, FormatException {
|
||||
|
||||
StateUpdate<SharingSessionState, BaseMessage> r =
|
||||
new StateUpdate<SharingSessionState, BaseMessage>(
|
||||
result.deleteMessage, result.deleteState,
|
||||
result.localState, result.toSend, result.toBroadcast);
|
||||
|
||||
// get shareable for later
|
||||
S f = getSFactory().parse(result.localState);
|
||||
|
||||
processStateUpdate(txn, messageId, r, f);
|
||||
}
|
||||
|
||||
private void performTasks(Transaction txn, SharingSessionState localState,
|
||||
S f) throws FormatException, DbException {
|
||||
|
||||
if (localState.getTask() == -1) return;
|
||||
|
||||
// remember task and remove it from localState
|
||||
long task = localState.getTask();
|
||||
localState.setTask(-1);
|
||||
|
||||
// get group ID for later
|
||||
GroupId groupId = localState.getGroupId();
|
||||
// get contact ID for later
|
||||
ContactId contactId = localState.getContactId();
|
||||
|
||||
// perform tasks
|
||||
if (task == TASK_ADD_SHAREABLE_TO_LIST_SHARED_WITH_US) {
|
||||
addToList(txn, groupId, SHARED_WITH_US, f);
|
||||
} else if (task == TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US) {
|
||||
removeFromList(txn, groupId, SHARED_WITH_US, f);
|
||||
} else if (task == TASK_ADD_SHARED_SHAREABLE) {
|
||||
db.addGroup(txn, f.getGroup());
|
||||
db.setVisibleToContact(txn, contactId, f.getId(), true);
|
||||
} else if (task == TASK_ADD_SHAREABLE_TO_LIST_TO_BE_SHARED_BY_US) {
|
||||
addToList(txn, groupId, TO_BE_SHARED_BY_US, f);
|
||||
} else if (task == TASK_REMOVE_SHAREABLE_FROM_LIST_TO_BE_SHARED_BY_US) {
|
||||
removeFromList(txn, groupId, TO_BE_SHARED_BY_US, f);
|
||||
} else if (task == TASK_SHARE_SHAREABLE) {
|
||||
db.setVisibleToContact(txn, contactId, f.getId(), true);
|
||||
removeFromList(txn, groupId, TO_BE_SHARED_BY_US, f);
|
||||
addToList(txn, groupId, SHARED_BY_US, f);
|
||||
} else if (task == TASK_UNSHARE_SHAREABLE_SHARED_BY_US) {
|
||||
db.setVisibleToContact(txn, contactId, f.getId(), false);
|
||||
removeFromList(txn, groupId, SHARED_BY_US, f);
|
||||
} else if (task == TASK_UNSHARE_SHAREABLE_SHARED_WITH_US) {
|
||||
db.setVisibleToContact(txn, contactId, f.getId(), false);
|
||||
removeFromList(txn, groupId, SHARED_WITH_US, f);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessage(Transaction txn, BaseMessage m)
|
||||
throws FormatException, DbException {
|
||||
|
||||
byte[] body = clientHelper.toByteArray(m.toBdfList());
|
||||
Group group = db.getGroup(txn, m.getGroupId());
|
||||
long timestamp = clock.currentTimeMillis();
|
||||
|
||||
// add message itself as metadata
|
||||
BdfDictionary d = m.toBdfDictionary();
|
||||
d.put(LOCAL, true);
|
||||
d.put(TIME, timestamp);
|
||||
Metadata meta = metadataEncoder.encode(d);
|
||||
|
||||
messageQueueManager
|
||||
.sendMessage(txn, group, timestamp, body, meta);
|
||||
}
|
||||
|
||||
private Group getContactGroup(Contact c) {
|
||||
return privateGroupFactory.createPrivateGroup(getClientId(), c);
|
||||
}
|
||||
|
||||
private ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
||||
throws DbException, FormatException {
|
||||
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn,
|
||||
contactGroupId);
|
||||
return new ContactId(meta.getLong(CONTACT_ID).intValue());
|
||||
}
|
||||
|
||||
private void leaveShareable(Transaction txn, ContactId c, S f)
|
||||
throws DbException, FormatException {
|
||||
|
||||
SharingSessionState state = getSessionStateForLeaving(txn, f, c);
|
||||
if (state instanceof SharerSessionState) {
|
||||
SharerSessionState.Action action =
|
||||
SharerSessionState.Action.LOCAL_LEAVE;
|
||||
SharerEngine<I, SS, IRR> engine =
|
||||
new SharerEngine<I, SS, IRR>(getIFactory(),
|
||||
getIRRFactory());
|
||||
processSharerStateUpdate(txn, null,
|
||||
engine.onLocalAction((SS) state, action));
|
||||
} else {
|
||||
InviteeSessionState.Action action =
|
||||
InviteeSessionState.Action.LOCAL_LEAVE;
|
||||
InviteeEngine<IS, IR> engine =
|
||||
new InviteeEngine<IS, IR>(getIRFactory());
|
||||
processInviteeStateUpdate(txn, null,
|
||||
engine.onLocalAction((IS) state, action));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean listContains(Transaction txn, GroupId contactGroup,
|
||||
GroupId shareable, String key) throws DbException, FormatException {
|
||||
|
||||
List<S> list = getShareableList(txn, contactGroup, key);
|
||||
for (S f : list) {
|
||||
if (f.getId().equals(shareable)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean addToList(Transaction txn, GroupId groupId, String key,
|
||||
S f) throws DbException, FormatException {
|
||||
|
||||
List<S> shareables = getShareableList(txn, groupId, key);
|
||||
if (shareables.contains(f)) return false;
|
||||
shareables.add(f);
|
||||
storeShareableList(txn, groupId, key, shareables);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean removeFromList(Transaction txn, GroupId groupId, String key,
|
||||
S f) throws DbException, FormatException {
|
||||
|
||||
List<S> shareables = getShareableList(txn, groupId, key);
|
||||
if (shareables.remove(f)) {
|
||||
storeShareableList(txn, groupId, key, shareables);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<S> getShareableList(Transaction txn, GroupId groupId,
|
||||
String key) throws DbException, FormatException {
|
||||
|
||||
BdfDictionary metadata =
|
||||
clientHelper.getGroupMetadataAsDictionary(txn, groupId);
|
||||
BdfList list = metadata.getList(key);
|
||||
|
||||
return parseShareableList(list);
|
||||
}
|
||||
|
||||
private void storeShareableList(Transaction txn, GroupId groupId,
|
||||
String key,
|
||||
List<S> shareables) throws DbException, FormatException {
|
||||
|
||||
BdfList list = encodeShareableList(shareables);
|
||||
BdfDictionary metadata = BdfDictionary.of(
|
||||
new BdfEntry(key, list)
|
||||
);
|
||||
clientHelper.mergeGroupMetadata(txn, groupId, metadata);
|
||||
}
|
||||
|
||||
private BdfList encodeShareableList(List<S> shareables) {
|
||||
BdfList shareableList = new BdfList();
|
||||
for (S f : shareables)
|
||||
shareableList.add(getSFactory().encode(f));
|
||||
return shareableList;
|
||||
}
|
||||
|
||||
private List<S> parseShareableList(BdfList list) throws FormatException {
|
||||
List<S> shareables = new ArrayList<S>(list.size());
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
BdfList shareable = list.getList(i);
|
||||
shareables.add(getSFactory().parse(shareable));
|
||||
}
|
||||
return shareables;
|
||||
}
|
||||
|
||||
private void deleteMessage(Transaction txn, MessageId messageId)
|
||||
throws DbException {
|
||||
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Deleting message with ID: " + messageId.hashCode());
|
||||
|
||||
db.deleteMessage(txn, messageId);
|
||||
db.deleteMessageMetadata(txn, messageId);
|
||||
}
|
||||
|
||||
}
|
||||
62
briar-core/src/org/briarproject/sharing/SharingModule.java
Normal file
62
briar-core/src/org/briarproject/sharing/SharingModule.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.clients.MessageQueueManager;
|
||||
import org.briarproject.api.contact.ContactManager;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
import org.briarproject.api.forum.ForumSharingManager;
|
||||
import org.briarproject.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.api.system.Clock;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class SharingModule {
|
||||
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
ForumSharingValidator forumSharingValidator;
|
||||
@Inject
|
||||
ForumSharingManager forumSharingManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ForumSharingValidator provideForumSharingValidator(
|
||||
MessageQueueManager messageQueueManager, ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
|
||||
ForumSharingValidator
|
||||
validator = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
messageQueueManager.registerMessageValidator(
|
||||
ForumSharingManagerImpl.CLIENT_ID, validator);
|
||||
|
||||
return validator;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ForumSharingManager provideForumSharingManager(
|
||||
LifecycleManager lifecycleManager,
|
||||
ContactManager contactManager,
|
||||
MessageQueueManager messageQueueManager,
|
||||
ForumManager forumManager,
|
||||
ForumSharingManagerImpl forumSharingManager) {
|
||||
|
||||
lifecycleManager.registerClient(forumSharingManager);
|
||||
contactManager.registerAddContactHook(forumSharingManager);
|
||||
contactManager.registerRemoveContactHook(forumSharingManager);
|
||||
messageQueueManager.registerIncomingMessageHook(
|
||||
ForumSharingManagerImpl.CLIENT_ID, forumSharingManager);
|
||||
forumManager.registerRemoveForumHook(forumSharingManager);
|
||||
|
||||
return forumSharingManager;
|
||||
}
|
||||
|
||||
}
|
||||
102
briar-core/src/org/briarproject/sharing/SharingSessionState.java
Normal file
102
briar-core/src/org/briarproject/sharing/SharingSessionState.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import static org.briarproject.api.sharing.SharingConstants.CONTACT_ID;
|
||||
import static org.briarproject.api.sharing.SharingConstants.GROUP_ID;
|
||||
import static org.briarproject.api.sharing.SharingConstants.IS_SHARER;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SESSION_ID;
|
||||
import static org.briarproject.api.sharing.SharingConstants.SHAREABLE_ID;
|
||||
import static org.briarproject.api.sharing.SharingConstants.STATE;
|
||||
import static org.briarproject.api.sharing.SharingConstants.STORAGE_ID;
|
||||
|
||||
// This class is not thread-safe
|
||||
public abstract class SharingSessionState {
|
||||
|
||||
private final SessionId sessionId;
|
||||
private final MessageId storageId;
|
||||
private final GroupId groupId;
|
||||
private final ContactId contactId;
|
||||
private final GroupId shareableId;
|
||||
private int task = -1; // TODO get rid of task, see #376
|
||||
|
||||
public SharingSessionState(SessionId sessionId, MessageId storageId,
|
||||
GroupId groupId, ContactId contactId, GroupId shareableId) {
|
||||
|
||||
this.sessionId = sessionId;
|
||||
this.storageId = storageId;
|
||||
this.groupId = groupId;
|
||||
this.contactId = contactId;
|
||||
this.shareableId = shareableId;
|
||||
}
|
||||
|
||||
public static SharingSessionState fromBdfDictionary(
|
||||
InviteeSessionStateFactory isFactory,
|
||||
SharerSessionStateFactory ssFactory, BdfDictionary d)
|
||||
throws FormatException {
|
||||
|
||||
SessionId sessionId = new SessionId(d.getRaw(SESSION_ID));
|
||||
MessageId messageId = new MessageId(d.getRaw(STORAGE_ID));
|
||||
GroupId groupId = new GroupId(d.getRaw(GROUP_ID));
|
||||
ContactId contactId = new ContactId(d.getLong(CONTACT_ID).intValue());
|
||||
GroupId forumId = new GroupId(d.getRaw(SHAREABLE_ID));
|
||||
|
||||
int intState = d.getLong(STATE).intValue();
|
||||
if (d.getBoolean(IS_SHARER)) {
|
||||
SharerSessionState.State state =
|
||||
SharerSessionState.State.fromValue(intState);
|
||||
return ssFactory.build(sessionId, messageId, groupId, state,
|
||||
contactId, forumId, d);
|
||||
} else {
|
||||
InviteeSessionState.State state =
|
||||
InviteeSessionState.State.fromValue(intState);
|
||||
return isFactory.build(sessionId, messageId, groupId, state,
|
||||
contactId, forumId, d);
|
||||
}
|
||||
}
|
||||
|
||||
public BdfDictionary toBdfDictionary() {
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put(SESSION_ID, getSessionId());
|
||||
d.put(STORAGE_ID, getStorageId());
|
||||
d.put(GROUP_ID, getGroupId());
|
||||
d.put(CONTACT_ID, getContactId().getInt());
|
||||
d.put(SHAREABLE_ID, getShareableId());
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
public SessionId getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public MessageId getStorageId() {
|
||||
return storageId;
|
||||
}
|
||||
|
||||
public GroupId getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public GroupId getShareableId() {
|
||||
return shareableId;
|
||||
}
|
||||
|
||||
public void setTask(int task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
public int getTask() {
|
||||
return task;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user