mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-14 19:59:05 +01:00
Merge branch '322-forum-sharing-client' into 'master'
Forum Sharing Client Backend This MR 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 invitation. Also, the Forum Sharing Manager is notified when users leave a forum. Please note that you will need the UI to actually test this. It is coming up soon in another MR. Closes #322 See merge request !170
This commit is contained in:
@@ -24,7 +24,7 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook,
|
||||
protected final MetadataParser metadataParser;
|
||||
|
||||
protected BdfIncomingMessageHook(ClientHelper clientHelper,
|
||||
MetadataParser metadataParser, Clock clock) {
|
||||
MetadataParser metadataParser) {
|
||||
this.clientHelper = clientHelper;
|
||||
this.metadataParser = metadataParser;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -141,15 +141,21 @@ class ForumManagerImpl implements ForumManager {
|
||||
|
||||
@Override
|
||||
public Forum getForum(GroupId g) throws DbException {
|
||||
Forum forum;
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
Group group;
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
group = db.getGroup(txn, g);
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
forum = getForum(txn, g);
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
return forum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Forum getForum(Transaction txn, GroupId g) throws DbException {
|
||||
try {
|
||||
Group group = db.getGroup(txn, g);
|
||||
return parseForum(group);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.introduction;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.Client;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
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;
|
||||
@@ -20,14 +21,12 @@ import org.briarproject.api.introduction.IntroductionManager;
|
||||
import org.briarproject.api.introduction.IntroductionMessage;
|
||||
import org.briarproject.api.introduction.IntroductionRequest;
|
||||
import org.briarproject.api.introduction.IntroductionResponse;
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
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;
|
||||
|
||||
@@ -93,12 +92,11 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
|
||||
|
||||
@Inject
|
||||
IntroductionManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||
MetadataParser metadataParser, Clock clock,
|
||||
IntroducerManager introducerManager,
|
||||
MetadataParser metadataParser, IntroducerManager introducerManager,
|
||||
IntroduceeManager introduceeManager,
|
||||
IntroductionGroupFactory introductionGroupFactory) {
|
||||
|
||||
super(clientHelper, metadataParser, clock);
|
||||
super(clientHelper, metadataParser);
|
||||
this.db = db;
|
||||
this.introducerManager = introducerManager;
|
||||
this.introduceeManager = introduceeManager;
|
||||
|
||||
Reference in New Issue
Block a user