mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-15 20:29:52 +01:00
Forum Sharing Client backend
This commit replaces the old ForumSharingManagerImpl with a new one which is based on state machines and the ProtocolEngine. There is a SharerEngine and a InviteeEngine that take care of state transitions, messages, events and trigger actions to be carried out by the ForumSharingManagerImpl. This is all very similar to the Introduction Client. The general sharing paradigm has been changed from sharing as a state to sharing as an action. Now the UI can allow users to invite contacts to forums. The contacts can accept or decline the invitiation. Also, the Forum Sharing Manger is notified when users leave a forum. Closes #322
This commit is contained in:
@@ -1,48 +0,0 @@
|
||||
package org.briarproject.forum;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
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 static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
|
||||
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
|
||||
|
||||
class ForumListValidator extends BdfMessageValidator {
|
||||
|
||||
ForumListValidator(ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
super(clientHelper, metadataEncoder, clock);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BdfDictionary validateMessage(Message m, Group g,
|
||||
BdfList body) throws FormatException {
|
||||
// Version, forum list
|
||||
checkSize(body, 2);
|
||||
// Version
|
||||
long version = body.getLong(0);
|
||||
if (version < 0) throw new FormatException();
|
||||
// Forum list
|
||||
BdfList forumList = body.getList(1);
|
||||
for (int i = 0; i < forumList.size(); i++) {
|
||||
BdfList forum = forumList.getList(i);
|
||||
// Name, salt
|
||||
checkSize(forum, 2);
|
||||
String name = forum.getString(0);
|
||||
checkLength(name, 1, MAX_FORUM_NAME_LENGTH);
|
||||
byte[] salt = forum.getRaw(1);
|
||||
checkLength(salt, FORUM_SALT_LENGTH);
|
||||
}
|
||||
// Return the metadata
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put("version", version);
|
||||
meta.put("local", false);
|
||||
return meta;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.forum;
|
||||
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.clients.MessageQueueManager;
|
||||
import org.briarproject.api.contact.ContactManager;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
@@ -26,11 +27,11 @@ import dagger.Provides;
|
||||
public class ForumModule {
|
||||
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
ForumListValidator forumListValidator;
|
||||
@Inject
|
||||
ForumPostValidator forumPostValidator;
|
||||
@Inject
|
||||
ForumSharingValidator forumSharingValidator;
|
||||
@Inject
|
||||
ForumSharingManager forumSharingManager;
|
||||
}
|
||||
|
||||
@@ -63,13 +64,15 @@ public class ForumModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ForumListValidator provideForumListValidator(
|
||||
ValidationManager validationManager, ClientHelper clientHelper,
|
||||
ForumSharingValidator provideSharingValidator(
|
||||
MessageQueueManager messageQueueManager, ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
ForumListValidator validator = new ForumListValidator(clientHelper,
|
||||
|
||||
ForumSharingValidator validator = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
validationManager.registerMessageValidator(
|
||||
messageQueueManager.registerMessageValidator(
|
||||
ForumSharingManagerImpl.CLIENT_ID, validator);
|
||||
|
||||
return validator;
|
||||
}
|
||||
|
||||
@@ -78,15 +81,17 @@ public class ForumModule {
|
||||
ForumSharingManager provideForumSharingManager(
|
||||
LifecycleManager lifecycleManager,
|
||||
ContactManager contactManager,
|
||||
ValidationManager validationManager,
|
||||
MessageQueueManager messageQueueManager,
|
||||
ForumManager forumManager,
|
||||
ForumSharingManagerImpl forumSharingManager) {
|
||||
|
||||
lifecycleManager.registerClient(forumSharingManager);
|
||||
contactManager.registerAddContactHook(forumSharingManager);
|
||||
contactManager.registerRemoveContactHook(forumSharingManager);
|
||||
validationManager.registerIncomingMessageHook(
|
||||
messageQueueManager.registerIncomingMessageHook(
|
||||
ForumSharingManagerImpl.CLIENT_ID, forumSharingManager);
|
||||
forumManager.registerRemoveForumHook(forumSharingManager);
|
||||
|
||||
return forumSharingManager;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,81 @@
|
||||
package org.briarproject.forum;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
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 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.GROUP_ID;
|
||||
import static org.briarproject.api.forum.ForumConstants.INVITATION_MSG;
|
||||
import static org.briarproject.api.forum.ForumConstants.LOCAL;
|
||||
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
|
||||
import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
|
||||
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
|
||||
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
|
||||
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
|
||||
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
|
||||
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
|
||||
import static org.briarproject.api.forum.ForumConstants.TIME;
|
||||
import static org.briarproject.api.forum.ForumConstants.TYPE;
|
||||
import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||
|
||||
class ForumSharingValidator extends BdfMessageValidator {
|
||||
|
||||
ForumSharingValidator(ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
super(clientHelper, metadataEncoder, clock);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BdfDictionary 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(GROUP_ID, m.getGroupId());
|
||||
d.put(LOCAL, false);
|
||||
d.put(TIME, m.getTimestamp());
|
||||
return d;
|
||||
}
|
||||
}
|
||||
265
briar-core/src/org/briarproject/forum/InviteeEngine.java
Normal file
265
briar-core/src/org/briarproject/forum/InviteeEngine.java
Normal file
@@ -0,0 +1,265 @@
|
||||
package org.briarproject.forum;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ProtocolEngine;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfEntry;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.ForumInvitationReceivedEvent;
|
||||
import org.briarproject.api.forum.Forum;
|
||||
import org.briarproject.api.forum.InviteeAction;
|
||||
import org.briarproject.api.forum.InviteeProtocolState;
|
||||
|
||||
import java.util.Arrays;
|
||||
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.forum.ForumConstants.CONTACT_ID;
|
||||
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.GROUP_ID;
|
||||
import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
|
||||
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
|
||||
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
|
||||
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
|
||||
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
|
||||
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
|
||||
import static org.briarproject.api.forum.ForumConstants.STATE;
|
||||
import static org.briarproject.api.forum.ForumConstants.TASK;
|
||||
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US;
|
||||
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_SHARED_FORUM;
|
||||
import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US;
|
||||
import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_WITH_US;
|
||||
import static org.briarproject.api.forum.ForumConstants.TYPE;
|
||||
import static org.briarproject.api.forum.InviteeAction.LOCAL_ABORT;
|
||||
import static org.briarproject.api.forum.InviteeAction.LOCAL_ACCEPT;
|
||||
import static org.briarproject.api.forum.InviteeAction.LOCAL_DECLINE;
|
||||
import static org.briarproject.api.forum.InviteeAction.LOCAL_LEAVE;
|
||||
import static org.briarproject.api.forum.InviteeAction.REMOTE_INVITATION;
|
||||
import static org.briarproject.api.forum.InviteeAction.REMOTE_LEAVE;
|
||||
import static org.briarproject.api.forum.InviteeProtocolState.ERROR;
|
||||
import static org.briarproject.api.forum.InviteeProtocolState.FINISHED;
|
||||
import static org.briarproject.api.forum.InviteeProtocolState.LEFT;
|
||||
|
||||
public class InviteeEngine
|
||||
implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(SharerEngine.class.getName());
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
|
||||
BdfDictionary localState, BdfDictionary localAction) {
|
||||
|
||||
try {
|
||||
InviteeProtocolState currentState =
|
||||
getState(localState.getLong(STATE));
|
||||
long type = localAction.getLong(TYPE);
|
||||
InviteeAction action = InviteeAction.getLocal(type);
|
||||
InviteeProtocolState nextState = currentState.next(action);
|
||||
localState.put(STATE, nextState.getValue());
|
||||
|
||||
if (action == LOCAL_ABORT && currentState != ERROR) {
|
||||
return abortSession(currentState, localState);
|
||||
}
|
||||
|
||||
if (nextState == ERROR) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Error: Invalid action in state " +
|
||||
currentState.name());
|
||||
}
|
||||
return noUpdate(localState, true);
|
||||
}
|
||||
List<BdfDictionary> messages;
|
||||
List<Event> events = Collections.emptyList();
|
||||
|
||||
if (action == LOCAL_ACCEPT || action == LOCAL_DECLINE) {
|
||||
BdfDictionary msg = BdfDictionary.of(
|
||||
new BdfEntry(SESSION_ID, localState.getRaw(SESSION_ID)),
|
||||
new BdfEntry(GROUP_ID, localState.getRaw(GROUP_ID))
|
||||
);
|
||||
if (action == LOCAL_ACCEPT) {
|
||||
localState.put(TASK, TASK_ADD_SHARED_FORUM);
|
||||
msg.put(TYPE, SHARE_MSG_TYPE_ACCEPT);
|
||||
} else {
|
||||
localState.put(TASK,
|
||||
TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US);
|
||||
msg.put(TYPE, SHARE_MSG_TYPE_DECLINE);
|
||||
}
|
||||
messages = Collections.singletonList(msg);
|
||||
logLocalAction(currentState, localState, msg);
|
||||
}
|
||||
else if (action == LOCAL_LEAVE) {
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, SHARE_MSG_TYPE_LEAVE);
|
||||
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
|
||||
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
|
||||
messages = Collections.singletonList(msg);
|
||||
logLocalAction(currentState, localState, msg);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unknown Local Action");
|
||||
}
|
||||
return new StateUpdate<BdfDictionary, BdfDictionary>(false,
|
||||
false, localState, messages, events);
|
||||
} catch (FormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
|
||||
BdfDictionary localState, BdfDictionary msg) {
|
||||
|
||||
try {
|
||||
InviteeProtocolState currentState =
|
||||
getState(localState.getLong(STATE));
|
||||
long type = msg.getLong(TYPE);
|
||||
InviteeAction action = InviteeAction.getRemote(type);
|
||||
InviteeProtocolState nextState = currentState.next(action);
|
||||
localState.put(STATE, nextState.getValue());
|
||||
|
||||
logMessageReceived(currentState, nextState, type, msg);
|
||||
|
||||
if (nextState == ERROR) {
|
||||
if (currentState != ERROR) {
|
||||
return abortSession(currentState, localState);
|
||||
} else {
|
||||
return noUpdate(localState, true);
|
||||
}
|
||||
}
|
||||
|
||||
List<BdfDictionary> messages = Collections.emptyList();
|
||||
List<Event> events = Collections.emptyList();
|
||||
boolean deleteMsg = false;
|
||||
|
||||
if (currentState == 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 == REMOTE_LEAVE && currentState == FINISHED) {
|
||||
localState.put(TASK, TASK_UNSHARE_FORUM_SHARED_WITH_US);
|
||||
}
|
||||
else if (currentState == 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 == REMOTE_LEAVE) {
|
||||
localState.put(TASK, TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US);
|
||||
}
|
||||
// we have just received our invitation
|
||||
else if (action == REMOTE_INVITATION) {
|
||||
localState.put(TASK, TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US);
|
||||
// TODO how to get the proper group here?
|
||||
Forum forum = new Forum(null, localState.getString(FORUM_NAME),
|
||||
localState.getRaw(FORUM_SALT));
|
||||
ContactId contactId = new ContactId(
|
||||
localState.getLong(CONTACT_ID).intValue());
|
||||
Event event = new ForumInvitationReceivedEvent(forum, contactId);
|
||||
events = Collections.singletonList(event);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Bad state");
|
||||
}
|
||||
return new StateUpdate<BdfDictionary, BdfDictionary>(deleteMsg,
|
||||
false, localState, messages, events);
|
||||
} catch (FormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void logLocalAction(InviteeProtocolState state,
|
||||
BdfDictionary localState, BdfDictionary msg) {
|
||||
|
||||
if (!LOG.isLoggable(INFO)) return;
|
||||
|
||||
String a = "response";
|
||||
if (msg.getLong(TYPE, -1L) == SHARE_MSG_TYPE_LEAVE) a = "leave";
|
||||
|
||||
try {
|
||||
LOG.info("Sending " + a + " in state " + state.name() +
|
||||
" with session ID " +
|
||||
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
|
||||
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
|
||||
"Moving on to state " +
|
||||
getState(localState.getLong(STATE)).name()
|
||||
);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void logMessageReceived(InviteeProtocolState currentState,
|
||||
InviteeProtocolState nextState, long type, BdfDictionary msg) {
|
||||
if (!LOG.isLoggable(INFO)) return;
|
||||
|
||||
try {
|
||||
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 " +
|
||||
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
|
||||
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
|
||||
"Moving on to state " + nextState.name()
|
||||
);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered(
|
||||
BdfDictionary localState, BdfDictionary delivered) {
|
||||
try {
|
||||
return noUpdate(localState, false);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private InviteeProtocolState getState(Long state) {
|
||||
return InviteeProtocolState.fromValue(state.intValue());
|
||||
}
|
||||
|
||||
private StateUpdate<BdfDictionary, BdfDictionary> abortSession(
|
||||
InviteeProtocolState currentState, BdfDictionary localState)
|
||||
throws FormatException {
|
||||
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Aborting protocol session " +
|
||||
Arrays.hashCode(localState.getRaw(SESSION_ID)) +
|
||||
" in state " + currentState.name());
|
||||
}
|
||||
|
||||
localState.put(STATE, ERROR.getValue());
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, SHARE_MSG_TYPE_ABORT);
|
||||
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
|
||||
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
|
||||
List<BdfDictionary> messages = Collections.singletonList(msg);
|
||||
|
||||
List<Event> events = Collections.emptyList();
|
||||
|
||||
return new StateUpdate<BdfDictionary, BdfDictionary>(false, false,
|
||||
localState, messages, events);
|
||||
}
|
||||
|
||||
private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
|
||||
BdfDictionary localState, boolean delete) throws FormatException {
|
||||
|
||||
return new StateUpdate<BdfDictionary, BdfDictionary>(delete, false,
|
||||
localState, Collections.<BdfDictionary>emptyList(),
|
||||
Collections.<Event>emptyList());
|
||||
}
|
||||
}
|
||||
264
briar-core/src/org/briarproject/forum/SharerEngine.java
Normal file
264
briar-core/src/org/briarproject/forum/SharerEngine.java
Normal file
@@ -0,0 +1,264 @@
|
||||
package org.briarproject.forum;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ProtocolEngine;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.ForumInvitationResponseReceivedEvent;
|
||||
import org.briarproject.api.forum.SharerAction;
|
||||
import org.briarproject.api.forum.SharerProtocolState;
|
||||
|
||||
import java.util.Arrays;
|
||||
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.forum.ForumConstants.CONTACT_ID;
|
||||
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.GROUP_ID;
|
||||
import static org.briarproject.api.forum.ForumConstants.INVITATION_MSG;
|
||||
import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
|
||||
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
|
||||
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
|
||||
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
|
||||
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
|
||||
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
|
||||
import static org.briarproject.api.forum.ForumConstants.STATE;
|
||||
import static org.briarproject.api.forum.ForumConstants.TASK;
|
||||
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US;
|
||||
import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US;
|
||||
import static org.briarproject.api.forum.ForumConstants.TASK_SHARE_FORUM;
|
||||
import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_BY_US;
|
||||
import static org.briarproject.api.forum.ForumConstants.TYPE;
|
||||
import static org.briarproject.api.forum.SharerAction.LOCAL_ABORT;
|
||||
import static org.briarproject.api.forum.SharerAction.LOCAL_INVITATION;
|
||||
import static org.briarproject.api.forum.SharerAction.LOCAL_LEAVE;
|
||||
import static org.briarproject.api.forum.SharerAction.REMOTE_ACCEPT;
|
||||
import static org.briarproject.api.forum.SharerAction.REMOTE_DECLINE;
|
||||
import static org.briarproject.api.forum.SharerAction.REMOTE_LEAVE;
|
||||
import static org.briarproject.api.forum.SharerProtocolState.ERROR;
|
||||
import static org.briarproject.api.forum.SharerProtocolState.FINISHED;
|
||||
import static org.briarproject.api.forum.SharerProtocolState.LEFT;
|
||||
|
||||
public class SharerEngine
|
||||
implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(SharerEngine.class.getName());
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
|
||||
BdfDictionary localState, BdfDictionary localAction) {
|
||||
|
||||
try {
|
||||
SharerProtocolState currentState =
|
||||
getState(localState.getLong(STATE));
|
||||
long type = localAction.getLong(TYPE);
|
||||
SharerAction action = SharerAction.getLocal(type);
|
||||
SharerProtocolState nextState = currentState.next(action);
|
||||
localState.put(STATE, nextState.getValue());
|
||||
|
||||
if (action == LOCAL_ABORT && currentState != ERROR) {
|
||||
return abortSession(currentState, localState);
|
||||
}
|
||||
|
||||
if (nextState == ERROR) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Error: Invalid action in state " +
|
||||
currentState.name());
|
||||
}
|
||||
return noUpdate(localState, true);
|
||||
}
|
||||
List<BdfDictionary> messages;
|
||||
List<Event> events = Collections.emptyList();
|
||||
|
||||
if (action == LOCAL_INVITATION) {
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, SHARE_MSG_TYPE_INVITATION);
|
||||
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
|
||||
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
|
||||
msg.put(FORUM_NAME, localState.getString(FORUM_NAME));
|
||||
msg.put(FORUM_SALT, localState.getRaw(FORUM_SALT));
|
||||
if (localAction.containsKey(INVITATION_MSG)) {
|
||||
msg.put(INVITATION_MSG,
|
||||
localAction.getString(INVITATION_MSG));
|
||||
}
|
||||
messages = Collections.singletonList(msg);
|
||||
logLocalAction(currentState, localState, msg);
|
||||
|
||||
// remember that we offered to share this forum
|
||||
localState.put(TASK, TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US);
|
||||
}
|
||||
else if (action == LOCAL_LEAVE) {
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, SHARE_MSG_TYPE_LEAVE);
|
||||
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
|
||||
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
|
||||
messages = Collections.singletonList(msg);
|
||||
logLocalAction(currentState, localState, msg);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unknown Local Action");
|
||||
}
|
||||
return new StateUpdate<BdfDictionary, BdfDictionary>(false,
|
||||
false, localState, messages, events);
|
||||
} catch (FormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
|
||||
BdfDictionary localState, BdfDictionary msg) {
|
||||
|
||||
try {
|
||||
SharerProtocolState currentState =
|
||||
getState(localState.getLong(STATE));
|
||||
long type = msg.getLong(TYPE);
|
||||
SharerAction action = SharerAction.getRemote(type);
|
||||
SharerProtocolState nextState = currentState.next(action);
|
||||
localState.put(STATE, nextState.getValue());
|
||||
|
||||
logMessageReceived(currentState, nextState, type, msg);
|
||||
|
||||
if (nextState == ERROR) {
|
||||
if (currentState != ERROR) {
|
||||
return abortSession(currentState, localState);
|
||||
} else {
|
||||
return noUpdate(localState, true);
|
||||
}
|
||||
}
|
||||
List<BdfDictionary> messages = Collections.emptyList();
|
||||
List<Event> events = Collections.emptyList();
|
||||
boolean deleteMsg = false;
|
||||
|
||||
if (currentState == LEFT) {
|
||||
// ignore and delete messages coming in while in that state
|
||||
deleteMsg = true;
|
||||
}
|
||||
else if (action == REMOTE_LEAVE) {
|
||||
localState.put(TASK, TASK_UNSHARE_FORUM_SHARED_BY_US);
|
||||
}
|
||||
else if (currentState == 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 == REMOTE_ACCEPT || action == REMOTE_DECLINE) {
|
||||
if (action == REMOTE_ACCEPT) {
|
||||
localState.put(TASK, TASK_SHARE_FORUM);
|
||||
} else {
|
||||
// this ensures that the forum can be shared again
|
||||
localState.put(TASK,
|
||||
TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US);
|
||||
}
|
||||
String name = localState.getString(FORUM_NAME);
|
||||
ContactId c = new ContactId(
|
||||
localState.getLong(CONTACT_ID).intValue());
|
||||
Event event = new ForumInvitationResponseReceivedEvent(name, c);
|
||||
events = Collections.singletonList(event);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Bad state");
|
||||
}
|
||||
return new StateUpdate<BdfDictionary, BdfDictionary>(deleteMsg,
|
||||
false, localState, messages, events);
|
||||
} catch (FormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void logLocalAction(SharerProtocolState state,
|
||||
BdfDictionary localState, BdfDictionary msg) {
|
||||
|
||||
if (!LOG.isLoggable(INFO)) return;
|
||||
|
||||
String a = "invitation";
|
||||
if (msg.getLong(TYPE, -1L) == SHARE_MSG_TYPE_LEAVE) a = "leave";
|
||||
|
||||
try {
|
||||
LOG.info("Sending " + a + " in state " + state.name() +
|
||||
" with session ID " +
|
||||
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
|
||||
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
|
||||
"Moving on to state " +
|
||||
getState(localState.getLong(STATE)).name()
|
||||
);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void logMessageReceived(SharerProtocolState currentState,
|
||||
SharerProtocolState nextState, long type, BdfDictionary msg) {
|
||||
if (!LOG.isLoggable(INFO)) return;
|
||||
|
||||
try {
|
||||
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 " +
|
||||
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
|
||||
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
|
||||
"Moving on to state " + nextState.name()
|
||||
);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered(
|
||||
BdfDictionary localState, BdfDictionary delivered) {
|
||||
try {
|
||||
return noUpdate(localState, false);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private SharerProtocolState getState(Long state) {
|
||||
return SharerProtocolState.fromValue(state.intValue());
|
||||
}
|
||||
|
||||
private StateUpdate<BdfDictionary, BdfDictionary> abortSession(
|
||||
SharerProtocolState currentState, BdfDictionary localState)
|
||||
throws FormatException {
|
||||
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Aborting protocol session " +
|
||||
Arrays.hashCode(localState.getRaw(SESSION_ID)) +
|
||||
" in state " + currentState.name());
|
||||
}
|
||||
|
||||
localState.put(STATE, ERROR.getValue());
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, SHARE_MSG_TYPE_ABORT);
|
||||
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
|
||||
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
|
||||
List<BdfDictionary> messages = Collections.singletonList(msg);
|
||||
|
||||
List<Event> events = Collections.emptyList();
|
||||
|
||||
return new StateUpdate<BdfDictionary, BdfDictionary>(false, false,
|
||||
localState, messages, events);
|
||||
}
|
||||
|
||||
private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
|
||||
BdfDictionary localState, boolean delete) throws FormatException {
|
||||
|
||||
return new StateUpdate<BdfDictionary, BdfDictionary>(delete, false,
|
||||
localState, Collections.<BdfDictionary>emptyList(),
|
||||
Collections.<Event>emptyList());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user