Factor out generic sharing code from ForumSharingManger

This commit is contained in:
str4d
2016-06-16 04:26:49 +00:00
parent 5df2776dc2
commit 9ae64124d3
49 changed files with 1723 additions and 1130 deletions

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View 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());
}
}

View 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;
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View 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());
}
}

View 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;
}
}
}

View File

@@ -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);
}

View 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);
}
}

View 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;
}
}

View 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;
}
}