Removed forums for beta release.

This commit is contained in:
akwizgran
2016-05-13 12:32:09 +01:00
parent b818d6846c
commit 62eda5cd91
86 changed files with 10 additions and 7594 deletions

View File

@@ -3,7 +3,6 @@ package org.briarproject;
import org.briarproject.contact.ContactModule;
import org.briarproject.crypto.CryptoModule;
import org.briarproject.db.DatabaseExecutorModule;
import org.briarproject.forum.ForumModule;
import org.briarproject.introduction.IntroductionModule;
import org.briarproject.lifecycle.LifecycleModule;
import org.briarproject.messaging.MessagingModule;
@@ -21,8 +20,6 @@ public interface CoreEagerSingletons {
void inject(DatabaseExecutorModule.EagerSingletons init);
void inject(ForumModule.EagerSingletons init);
void inject(IntroductionModule.EagerSingletons init);
void inject(LifecycleModule.EagerSingletons init);

View File

@@ -7,7 +7,6 @@ import org.briarproject.data.DataModule;
import org.briarproject.db.DatabaseExecutorModule;
import org.briarproject.db.DatabaseModule;
import org.briarproject.event.EventModule;
import org.briarproject.forum.ForumModule;
import org.briarproject.identity.IdentityModule;
import org.briarproject.introduction.IntroductionModule;
import org.briarproject.invitation.InvitationModule;
@@ -33,7 +32,6 @@ import dagger.Module;
DatabaseModule.class,
DatabaseExecutorModule.class,
EventModule.class,
ForumModule.class,
IdentityModule.class,
IntroductionModule.class,
InvitationModule.class,
@@ -55,7 +53,6 @@ public class CoreModule {
c.inject(new ContactModule.EagerSingletons());
c.inject(new CryptoModule.EagerSingletons());
c.inject(new DatabaseExecutorModule.EagerSingletons());
c.inject(new ForumModule.EagerSingletons());
c.inject(new LifecycleModule.EagerSingletons());
c.inject(new MessagingModule.EagerSingletons());
c.inject(new PluginsModule.EagerSingletons());

View File

@@ -1,271 +0,0 @@
package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumPost;
import org.briarproject.api.forum.ForumPostHeader;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupFactory;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject;
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.identity.Author.Status.ANONYMOUS;
import static org.briarproject.api.identity.Author.Status.UNKNOWN;
import static org.briarproject.api.identity.Author.Status.VERIFIED;
class ForumManagerImpl implements ForumManager {
static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
"859a7be50dca035b64bd6902fb797097"
+ "795af837abbf8c16d750b3c2ccc186ea"));
private final DatabaseComponent db;
private final ClientHelper clientHelper;
private final GroupFactory groupFactory;
private final SecureRandom random;
private final List<RemoveForumHook> removeHooks;
@Inject
ForumManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
GroupFactory groupFactory, SecureRandom random) {
this.db = db;
this.clientHelper = clientHelper;
this.groupFactory = groupFactory;
this.random = random;
removeHooks = new CopyOnWriteArrayList<RemoveForumHook>();
}
@Override
public ClientId getClientId() {
return CLIENT_ID;
}
@Override
public Forum createForum(String name) {
int length = StringUtils.toUtf8(name).length;
if (length == 0) throw new IllegalArgumentException();
if (length > MAX_FORUM_NAME_LENGTH)
throw new IllegalArgumentException();
byte[] salt = new byte[FORUM_SALT_LENGTH];
random.nextBytes(salt);
return createForum(name, salt);
}
@Override
public Forum createForum(String name, byte[] salt) {
try {
BdfList forum = BdfList.of(name, salt);
byte[] descriptor = clientHelper.toByteArray(forum);
Group g = groupFactory.createGroup(getClientId(), descriptor);
return new Forum(g, name, salt);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
@Override
public void addForum(Forum f) throws DbException {
Transaction txn = db.startTransaction(false);
try {
db.addGroup(txn, f.getGroup());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
}
@Override
public void removeForum(Forum f) throws DbException {
Transaction txn = db.startTransaction(false);
try {
for (RemoveForumHook hook : removeHooks)
hook.removingForum(txn, f);
db.removeGroup(txn, f.getGroup());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
}
@Override
public void addLocalPost(ForumPost p) throws DbException {
try {
BdfDictionary meta = new BdfDictionary();
meta.put("timestamp", p.getMessage().getTimestamp());
if (p.getParent() != null) meta.put("parent", p.getParent());
if (p.getAuthor() != null) {
Author a = p.getAuthor();
BdfDictionary authorMeta = new BdfDictionary();
authorMeta.put("id", a.getId());
authorMeta.put("name", a.getName());
authorMeta.put("publicKey", a.getPublicKey());
meta.put("author", authorMeta);
}
meta.put("contentType", p.getContentType());
meta.put("local", true);
meta.put("read", true);
clientHelper.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
@Override
public Forum getForum(GroupId g) throws DbException {
Forum forum;
Transaction txn = db.startTransaction(true);
try {
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);
}
}
@Override
public Collection<Forum> getForums() throws DbException {
try {
Collection<Group> groups;
Transaction txn = db.startTransaction(true);
try {
groups = db.getGroups(txn, CLIENT_ID);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
List<Forum> forums = new ArrayList<Forum>();
for (Group g : groups) forums.add(parseForum(g));
return Collections.unmodifiableList(forums);
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override
public byte[] getPostBody(MessageId m) throws DbException {
try {
// Parent ID, author, content type, forum post body, signature
BdfList message = clientHelper.getMessageAsList(m);
return message.getRaw(3);
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override
public Collection<ForumPostHeader> getPostHeaders(GroupId g)
throws DbException {
Set<AuthorId> localAuthorIds = new HashSet<AuthorId>();
Set<AuthorId> contactAuthorIds = new HashSet<AuthorId>();
Map<MessageId, BdfDictionary> metadata;
Transaction txn = db.startTransaction(true);
try {
// Load the IDs of the user's identities
for (LocalAuthor a : db.getLocalAuthors(txn))
localAuthorIds.add(a.getId());
// Load the IDs of contacts' identities
for (Contact c : db.getContacts(txn))
contactAuthorIds.add(c.getAuthor().getId());
// Load the metadata
metadata = clientHelper.getMessageMetadataAsDictionary(txn, g);
txn.setComplete();
} catch (FormatException e) {
throw new DbException(e);
} finally {
db.endTransaction(txn);
}
// Parse the metadata
Collection<ForumPostHeader> headers = new ArrayList<ForumPostHeader>();
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
try {
BdfDictionary meta = entry.getValue();
long timestamp = meta.getLong("timestamp");
Author author = null;
Author.Status authorStatus = ANONYMOUS;
BdfDictionary d1 = meta.getDictionary("author", null);
if (d1 != null) {
AuthorId authorId = new AuthorId(d1.getRaw("id"));
String name = d1.getString("name");
byte[] publicKey = d1.getRaw("publicKey");
author = new Author(authorId, name, publicKey);
if (localAuthorIds.contains(authorId))
authorStatus = VERIFIED;
else if (contactAuthorIds.contains(authorId))
authorStatus = VERIFIED;
else authorStatus = UNKNOWN;
}
String contentType = meta.getString("contentType");
boolean read = meta.getBoolean("read");
headers.add(new ForumPostHeader(entry.getKey(), timestamp,
author, authorStatus, contentType, read));
} catch (FormatException e) {
throw new DbException(e);
}
}
return headers;
}
@Override
public void setReadFlag(MessageId m, boolean read) throws DbException {
try {
BdfDictionary meta = new BdfDictionary();
meta.put("read", read);
clientHelper.mergeMessageMetadata(m, meta);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
@Override
public void registerRemoveForumHook(RemoveForumHook hook) {
removeHooks.add(hook);
}
private Forum parseForum(Group g) throws FormatException {
byte[] descriptor = g.getDescriptor();
// Name, salt
BdfList forum = clientHelper.toList(descriptor, 0, descriptor.length);
return new Forum(g, forum.getString(0), forum.getRaw(1));
}
}

View File

@@ -1,97 +0,0 @@
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;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumPostFactory;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.sync.GroupFactory;
import org.briarproject.api.sync.ValidationManager;
import org.briarproject.api.system.Clock;
import java.security.SecureRandom;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class ForumModule {
public static class EagerSingletons {
@Inject
ForumPostValidator forumPostValidator;
@Inject
ForumSharingValidator forumSharingValidator;
@Inject
ForumSharingManager forumSharingManager;
}
@Provides
@Singleton
ForumManager provideForumManager(DatabaseComponent db,
ClientHelper clientHelper,
GroupFactory groupFactory, SecureRandom random) {
return new ForumManagerImpl(db, clientHelper, groupFactory, random);
}
@Provides
ForumPostFactory provideForumPostFactory(CryptoComponent crypto,
ClientHelper clientHelper) {
return new ForumPostFactoryImpl(crypto, clientHelper);
}
@Provides
@Singleton
ForumPostValidator provideForumPostValidator(
ValidationManager validationManager, CryptoComponent crypto,
AuthorFactory authorFactory, ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
ForumPostValidator validator = new ForumPostValidator(crypto,
authorFactory, clientHelper, metadataEncoder, clock);
validationManager.registerMessageValidator(
ForumManagerImpl.CLIENT_ID, validator);
return validator;
}
@Provides
@Singleton
ForumSharingValidator provideSharingValidator(
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

@@ -1,76 +0,0 @@
package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.PrivateKey;
import org.briarproject.api.crypto.Signature;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.forum.ForumPost;
import org.briarproject.api.forum.ForumPostFactory;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils;
import java.security.GeneralSecurityException;
import javax.inject.Inject;
import static org.briarproject.api.forum.ForumConstants.MAX_CONTENT_TYPE_LENGTH;
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
class ForumPostFactoryImpl implements ForumPostFactory {
private final CryptoComponent crypto;
private final ClientHelper clientHelper;
@Inject
ForumPostFactoryImpl(CryptoComponent crypto, ClientHelper clientHelper) {
this.crypto = crypto;
this.clientHelper = clientHelper;
}
@Override
public ForumPost createAnonymousPost(GroupId groupId, long timestamp,
MessageId parent, String contentType, byte[] body)
throws FormatException {
// Validate the arguments
if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
throw new IllegalArgumentException();
if (body.length > MAX_FORUM_POST_BODY_LENGTH)
throw new IllegalArgumentException();
// Serialise the message
BdfList message = BdfList.of(parent, null, contentType, body, null);
Message m = clientHelper.createMessage(groupId, timestamp, message);
return new ForumPost(m, parent, null, contentType);
}
@Override
public ForumPost createPseudonymousPost(GroupId groupId, long timestamp,
MessageId parent, Author author, String contentType, byte[] body,
PrivateKey privateKey) throws FormatException,
GeneralSecurityException {
// Validate the arguments
if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
throw new IllegalArgumentException();
if (body.length > MAX_FORUM_POST_BODY_LENGTH)
throw new IllegalArgumentException();
// Serialise the data to be signed
BdfList authorList = BdfList.of(author.getName(),
author.getPublicKey());
BdfList signed = BdfList.of(groupId, timestamp, parent, authorList,
contentType, body);
// Generate the signature
Signature signature = crypto.getSignature();
signature.initSign(privateKey);
signature.update(clientHelper.toByteArray(signed));
byte[] sig = signature.sign();
// Serialise the signed message
BdfList message = BdfList.of(parent, authorList, contentType, body,
sig);
Message m = clientHelper.createMessage(groupId, timestamp, message);
return new ForumPost(m, parent, author, contentType);
}
}

View File

@@ -1,116 +0,0 @@
package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.UniqueId;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyParser;
import org.briarproject.api.crypto.PublicKey;
import org.briarproject.api.crypto.Signature;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.Message;
import org.briarproject.api.system.Clock;
import org.briarproject.clients.BdfMessageValidator;
import java.security.GeneralSecurityException;
import static org.briarproject.api.forum.ForumConstants.MAX_CONTENT_TYPE_LENGTH;
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
class ForumPostValidator extends BdfMessageValidator {
private final CryptoComponent crypto;
private final AuthorFactory authorFactory;
ForumPostValidator(CryptoComponent crypto, AuthorFactory authorFactory,
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
Clock clock) {
super(clientHelper, metadataEncoder, clock);
this.crypto = crypto;
this.authorFactory = authorFactory;
}
@Override
protected BdfDictionary validateMessage(Message m, Group g,
BdfList body) throws FormatException {
// Parent ID, author, content type, forum post body, signature
checkSize(body, 5);
// Parent ID is optional
byte[] parent = body.getOptionalRaw(0);
checkLength(parent, UniqueId.LENGTH);
// Author is optional
Author author = null;
BdfList authorList = body.getOptionalList(1);
if (authorList != null) {
// Name, public key
checkSize(authorList, 2);
String name = authorList.getString(0);
checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
byte[] publicKey = authorList.getRaw(1);
checkLength(publicKey, 0, MAX_PUBLIC_KEY_LENGTH);
author = authorFactory.createAuthor(name, publicKey);
}
// Content type
String contentType = body.getString(2);
checkLength(contentType, 0, MAX_CONTENT_TYPE_LENGTH);
// Forum post body
byte[] forumPostBody = body.getRaw(3);
checkLength(forumPostBody, 0, MAX_FORUM_POST_BODY_LENGTH);
// Signature is optional
byte[] sig = body.getOptionalRaw(4);
checkLength(sig, 0, MAX_SIGNATURE_LENGTH);
// If there's an author there must be a signature and vice versa
if (author != null && sig == null) {
LOG.info("Author without signature");
return null;
}
if (author == null && sig != null) {
LOG.info("Signature without author");
return null;
}
// Verify the signature, if any
if (author != null) {
try {
// Parse the public key
KeyParser keyParser = crypto.getSignatureKeyParser();
PublicKey key = keyParser.parsePublicKey(author.getPublicKey());
// Serialise the data to be signed
BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), parent,
authorList, contentType, forumPostBody);
// Verify the signature
Signature signature = crypto.getSignature();
signature.initVerify(key);
signature.update(clientHelper.toByteArray(signed));
if (!signature.verify(sig)) {
LOG.info("Invalid signature");
return null;
}
} catch (GeneralSecurityException e) {
LOG.info("Invalid public key");
return null;
}
}
// Return the metadata
BdfDictionary meta = new BdfDictionary();
meta.put("timestamp", m.getTimestamp());
if (parent != null) meta.put("parent", parent);
if (author != null) {
BdfDictionary authorMeta = new BdfDictionary();
authorMeta.put("id", author.getId());
authorMeta.put("name", author.getName());
authorMeta.put("publicKey", author.getPublicKey());
meta.put("author", authorMeta);
}
meta.put("contentType", contentType);
meta.put("read", false);
return meta;
}
}

View File

@@ -1,878 +0,0 @@
package org.briarproject.forum;
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.forum.Forum;
import org.briarproject.api.forum.ForumInvitationMessage;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.forum.InviteeAction;
import org.briarproject.api.forum.InviteeProtocolState;
import org.briarproject.api.forum.SharerAction;
import org.briarproject.api.forum.SharerProtocolState;
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 javax.inject.Inject;
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.forum.ForumConstants.CONTACT_ID;
import static org.briarproject.api.forum.ForumConstants.FORUM_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.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.IS_SHARER;
import static org.briarproject.api.forum.ForumConstants.LOCAL;
import static org.briarproject.api.forum.ForumConstants.READ;
import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
import static org.briarproject.api.forum.ForumConstants.SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.SHARED_WITH_US;
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.STORAGE_ID;
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_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_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.TASK_UNSHARE_FORUM_SHARED_WITH_US;
import static org.briarproject.api.forum.ForumConstants.TIME;
import static org.briarproject.api.forum.ForumConstants.TO_BE_SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.TYPE;
import static org.briarproject.api.forum.ForumManager.RemoveForumHook;
import static org.briarproject.api.forum.InviteeProtocolState.AWAIT_INVITATION;
import static org.briarproject.api.forum.InviteeProtocolState.AWAIT_LOCAL_RESPONSE;
import static org.briarproject.api.forum.SharerProtocolState.PREPARE_INVITATION;
class ForumSharingManagerImpl extends BdfIncomingMessageHook
implements ForumSharingManager, Client, RemoveForumHook,
AddContactHook, RemoveContactHook {
static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
"cd11a5d04dccd9e2931d6fc3df456313"
+ "63bb3e9d9d0e9405fccdb051f41f5449"));
private static final Logger LOG =
Logger.getLogger(ForumSharingManagerImpl.class.getName());
private final DatabaseComponent db;
private final ForumManager forumManager;
private final MessageQueueManager messageQueueManager;
private final MetadataEncoder metadataEncoder;
private final SecureRandom random;
private final PrivateGroupFactory privateGroupFactory;
private final Clock clock;
private final Group localGroup;
@Inject
ForumSharingManagerImpl(DatabaseComponent db, ForumManager forumManager,
MessageQueueManager messageQueueManager, ClientHelper clientHelper,
MetadataParser metadataParser, MetadataEncoder metadataEncoder,
SecureRandom random, PrivateGroupFactory privateGroupFactory,
Clock clock) {
super(clientHelper, metadataParser);
this.db = db;
this.forumManager = forumManager;
this.messageQueueManager = messageQueueManager;
this.metadataEncoder = metadataEncoder;
this.random = random;
this.privateGroupFactory = privateGroupFactory;
this.clock = clock;
localGroup = privateGroupFactory.createLocalGroup(getClientId());
}
@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 {
// clean up session states with that contact from localGroup
Long id = (long) c.getId().getInt();
try {
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, localGroup.getId());
for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
BdfDictionary d = entry.getValue();
if (id.equals(d.getLong(CONTACT_ID))) {
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 msg) throws DbException, FormatException {
SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID));
long type = msg.getLong(TYPE);
if (type == 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 forum can be shared
Forum f = forumManager.createForum(msg.getString(FORUM_NAME),
msg.getRaw(FORUM_SALT));
ContactId contactId = getContactId(txn, m.getGroupId());
Contact contact = db.getContact(txn, contactId);
if (!canBeShared(txn, f.getId(), contact))
throw new FormatException();
// initialize state and process invitation
BdfDictionary state =
initializeInviteeState(txn, contactId, msg);
InviteeEngine engine = new InviteeEngine();
processStateUpdate(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 (type == SHARE_MSG_TYPE_ACCEPT ||
type == SHARE_MSG_TYPE_DECLINE) {
// we are a sharer who just received a response
BdfDictionary state = getSessionState(txn, sessionId, true);
SharerEngine engine = new SharerEngine();
processStateUpdate(txn, m.getId(),
engine.onMessageReceived(state, msg));
} else if (type == SHARE_MSG_TYPE_LEAVE ||
type == SHARE_MSG_TYPE_ABORT) {
// we don't know who we are, so figure it out
BdfDictionary state = getSessionState(txn, sessionId, true);
if (state.getBoolean(IS_SHARER)) {
// we are a sharer and the invitee wants to leave or abort
SharerEngine engine = new SharerEngine();
processStateUpdate(txn, m.getId(),
engine.onMessageReceived(state, msg));
} else {
// we are an invitee and the sharer wants to leave or abort
InviteeEngine engine = new InviteeEngine();
processStateUpdate(txn, m.getId(),
engine.onMessageReceived(state, msg));
}
} else {
// message has passed validator, so that should never happen
throw new RuntimeException("Illegal Forum Sharing Message");
}
}
@Override
public ClientId getClientId() {
return CLIENT_ID;
}
@Override
public void sendForumInvitation(GroupId groupId, ContactId contactId,
String msg) throws DbException {
Transaction txn = db.startTransaction(false);
try {
// initialize local state for sharer
Forum f = forumManager.getForum(txn, groupId);
BdfDictionary localState = initializeSharerState(txn, f, contactId);
// define action
BdfDictionary localAction = new BdfDictionary();
localAction.put(TYPE, SHARE_MSG_TYPE_INVITATION);
if (!StringUtils.isNullOrEmpty(msg)) {
localAction.put(INVITATION_MSG, msg);
}
// start engine and process its state update
SharerEngine engine = new SharerEngine();
processStateUpdate(txn, null,
engine.onLocalAction(localState, localAction));
txn.setComplete();
} catch (FormatException e) {
throw new DbException();
} finally {
db.endTransaction(txn);
}
}
@Override
public void respondToInvitation(Forum f, boolean accept)
throws DbException {
Transaction txn = db.startTransaction(false);
try {
// find session state based on forum
BdfDictionary localState = getSessionStateForResponse(txn, f);
// define action
BdfDictionary localAction = new BdfDictionary();
if (accept) {
localAction.put(TYPE, SHARE_MSG_TYPE_ACCEPT);
} else {
localAction.put(TYPE, SHARE_MSG_TYPE_DECLINE);
}
// start engine and process its state update
InviteeEngine engine = new InviteeEngine();
processStateUpdate(txn, null,
engine.onLocalAction(localState, localAction));
txn.setComplete();
} catch (FormatException e) {
throw new DbException(e);
} finally {
db.endTransaction(txn);
}
}
@Override
public Collection<ForumInvitationMessage> getForumInvitationMessages(
ContactId contactId) throws DbException {
Transaction txn = db.startTransaction(false);
try {
Contact contact = db.getContact(txn, contactId);
Group group = getContactGroup(contact);
Collection<ForumInvitationMessage> list =
new ArrayList<ForumInvitationMessage>();
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, group.getId());
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
BdfDictionary msg = m.getValue();
try {
if (msg.getLong(TYPE) != SHARE_MSG_TYPE_INVITATION)
continue;
MessageStatus status =
db.getMessageStatus(txn, contactId, m.getKey());
SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID));
String name = msg.getString(FORUM_NAME);
String message = msg.getOptionalString(INVITATION_MSG);
long time = msg.getLong(TIME);
boolean local = msg.getBoolean(LOCAL);
boolean read = msg.getBoolean(READ, false);
boolean available = false;
if (!local) {
// figure out whether the forum is still available
BdfDictionary sessionState =
getSessionState(txn, sessionId, true);
InviteeProtocolState state = InviteeProtocolState
.fromValue(
sessionState.getLong(STATE).intValue());
available = state == AWAIT_LOCAL_RESPONSE;
}
ForumInvitationMessage im =
new ForumInvitationMessage(m.getKey(), sessionId,
contactId, name, message, 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 void removingForum(Transaction txn, Forum 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)) {
leaveForum(txn, c.getId(), f);
}
if (removeFromList(txn, g, SHARED_BY_US, f)) {
leaveForum(txn, c.getId(), f);
}
if (removeFromList(txn, g, SHARED_WITH_US, f)) {
leaveForum(txn, c.getId(), f);
}
}
} catch (IOException e) {
throw new DbException(e);
}
}
@Override
public Collection<Forum> getAvailableForums() throws DbException {
try {
Set<Forum> available = new HashSet<Forum>();
Transaction txn = db.startTransaction(true);
try {
// Get any forums we subscribe to
Set<Group> subscribed = new HashSet<Group>(db.getGroups(txn,
forumManager.getClientId()));
// Get all forums shared by contacts
for (Contact c : db.getContacts(txn)) {
Group g = getContactGroup(c);
List<Forum> forums =
getForumList(txn, g.getId(), SHARED_WITH_US);
for (Forum f : forums) {
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<ContactId> getSharedWith(GroupId g) throws DbException {
try {
List<ContactId> shared = new ArrayList<ContactId>();
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.getId());
}
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);
}
}
private BdfDictionary initializeSharerState(Transaction txn, Forum 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[FORUM_SALT_LENGTH]);
random.nextBytes(salt.getBytes());
Message m = clientHelper.createMessage(localGroup.getId(), now,
BdfList.of(salt));
MessageId sessionId = m.getId();
BdfDictionary d = new BdfDictionary();
d.put(SESSION_ID, sessionId);
d.put(STORAGE_ID, sessionId);
d.put(GROUP_ID, group.getId());
d.put(IS_SHARER, true);
d.put(STATE, PREPARE_INVITATION.getValue());
d.put(CONTACT_ID, contactId.getInt());
d.put(FORUM_ID, f.getId());
d.put(FORUM_NAME, f.getName());
d.put(FORUM_SALT, f.getSalt());
// save local state to database
clientHelper.addLocalMessage(txn, m, getClientId(), d, false);
return d;
}
private BdfDictionary initializeInviteeState(Transaction txn,
ContactId contactId, BdfDictionary msg)
throws FormatException, DbException {
Contact c = db.getContact(txn, contactId);
Group group = getContactGroup(c);
String name = msg.getString(FORUM_NAME);
byte[] salt = msg.getRaw(FORUM_SALT);
Forum f = forumManager.createForum(name, salt);
// create local message to keep engine state
long now = clock.currentTimeMillis();
Bytes mSalt = new Bytes(new byte[FORUM_SALT_LENGTH]);
random.nextBytes(mSalt.getBytes());
Message m = clientHelper.createMessage(localGroup.getId(), now,
BdfList.of(mSalt));
BdfDictionary d = new BdfDictionary();
d.put(SESSION_ID, msg.getRaw(SESSION_ID));
d.put(STORAGE_ID, m.getId());
d.put(GROUP_ID, group.getId());
d.put(IS_SHARER, false);
d.put(STATE, AWAIT_INVITATION.getValue());
d.put(CONTACT_ID, contactId.getInt());
d.put(FORUM_ID, f.getId());
d.put(FORUM_NAME, name);
d.put(FORUM_SALT, salt);
// save local state to database
clientHelper.addLocalMessage(txn, m, getClientId(), d, false);
return d;
}
private BdfDictionary getSessionState(Transaction txn, SessionId sessionId,
boolean warn) throws DbException, FormatException {
try {
// we should be able to get the sharer state directly from sessionId
return clientHelper.getMessageMetadataAsDictionary(txn, sessionId);
} catch (NoSuchMessageException e) {
// State not found directly, so iterate over all states
// to find state for invitee
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, localGroup.getId());
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
BdfDictionary state = m.getValue();
if (Arrays.equals(state.getRaw(SESSION_ID),
sessionId.getBytes())) {
return state;
}
}
if (warn && LOG.isLoggable(WARNING)) {
LOG.warning(
"No session state found for message with session ID " +
Arrays.hashCode(sessionId.getBytes()));
}
throw new FormatException();
}
}
private BdfDictionary getSessionStateForResponse(Transaction txn, Forum f)
throws DbException, FormatException {
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, localGroup.getId());
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
BdfDictionary d = m.getValue();
try {
InviteeProtocolState state = InviteeProtocolState
.fromValue(d.getLong(STATE).intValue());
if (state == AWAIT_LOCAL_RESPONSE) {
byte[] id = d.getRaw(FORUM_ID);
if (Arrays.equals(f.getId().getBytes(), id)) {
// Note that there should always be only one session
// in this state for the same forum
return d;
}
}
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
throw new DbException();
}
private BdfDictionary getSessionStateForLeaving(Transaction txn, Forum f,
ContactId c) throws DbException, FormatException {
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, localGroup.getId());
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
BdfDictionary d = m.getValue();
try {
// check that this session is with the right contact
if (c.getInt() != d.getLong(CONTACT_ID)) continue;
// check that a forum get be left in current session
int intState = d.getLong(STATE).intValue();
if (d.getBoolean(IS_SHARER)) {
SharerProtocolState state =
SharerProtocolState.fromValue(intState);
if (state.next(SharerAction.LOCAL_LEAVE) ==
SharerProtocolState.ERROR) continue;
} else {
InviteeProtocolState state = InviteeProtocolState
.fromValue(intState);
if (state.next(InviteeAction.LOCAL_LEAVE) ==
InviteeProtocolState.ERROR) continue;
}
// check that this state actually concerns this forum
String name = d.getString(FORUM_NAME);
byte[] salt = d.getRaw(FORUM_SALT);
if (name.equals(f.getName()) &&
Arrays.equals(salt, f.getSalt())) {
// TODO what happens when there is more than one invitation?
return d;
}
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
throw new FormatException();
}
private void processStateUpdate(Transaction txn, MessageId messageId,
StateUpdate<BdfDictionary, BdfDictionary> result)
throws DbException, FormatException {
// perform actions based on new local state
performTasks(txn, result.localState);
// save new local state
MessageId storageId =
new MessageId(result.localState.getRaw(STORAGE_ID));
clientHelper.mergeMessageMetadata(txn, storageId, result.localState);
// send messages
for (BdfDictionary d : result.toSend) {
sendMessage(txn, d);
}
// 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 performTasks(Transaction txn, BdfDictionary localState)
throws FormatException, DbException {
if (!localState.containsKey(TASK)) return;
// remember task and remove it from localState
long task = localState.getLong(TASK);
localState.put(TASK, BdfDictionary.NULL_VALUE);
// get group ID for later
GroupId groupId = new GroupId(localState.getRaw(GROUP_ID));
// get contact ID for later
ContactId contactId =
new ContactId(localState.getLong(CONTACT_ID).intValue());
// get forum for later
String name = localState.getString(FORUM_NAME);
byte[] salt = localState.getRaw(FORUM_SALT);
Forum f = forumManager.createForum(name, salt);
// perform tasks
if (task == TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US) {
addToList(txn, groupId, SHARED_WITH_US, f);
}
else if (task == TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US) {
removeFromList(txn, groupId, SHARED_WITH_US, f);
}
else if (task == TASK_ADD_SHARED_FORUM) {
db.addGroup(txn, f.getGroup());
db.setVisibleToContact(txn, contactId, f.getId(), true);
}
else if (task == TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US) {
addToList(txn, groupId, TO_BE_SHARED_BY_US, f);
}
else if (task == TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US) {
removeFromList(txn, groupId, TO_BE_SHARED_BY_US, f);
}
else if (task == TASK_SHARE_FORUM) {
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_FORUM_SHARED_BY_US) {
db.setVisibleToContact(txn, contactId, f.getId(), false);
removeFromList(txn, groupId, SHARED_BY_US, f);
} else if (task == TASK_UNSHARE_FORUM_SHARED_WITH_US) {
db.setVisibleToContact(txn, contactId, f.getId(), false);
removeFromList(txn, groupId, SHARED_WITH_US, f);
}
}
private void sendMessage(Transaction txn, BdfDictionary m)
throws FormatException, DbException {
BdfList list = encodeMessage(m);
byte[] body = clientHelper.toByteArray(list);
GroupId groupId = new GroupId(m.getRaw(GROUP_ID));
Group group = db.getGroup(txn, groupId);
long timestamp = clock.currentTimeMillis();
// add message itself as metadata
m.put(LOCAL, true);
m.put(TIME, timestamp);
Metadata meta = metadataEncoder.encode(m);
messageQueueManager
.sendMessage(txn, group, timestamp, body, meta);
}
private BdfList encodeMessage(BdfDictionary m) throws FormatException {
long type = m.getLong(TYPE);
BdfList list;
if (type == SHARE_MSG_TYPE_INVITATION) {
list = BdfList.of(type,
m.getRaw(SESSION_ID),
m.getString(FORUM_NAME),
m.getRaw(FORUM_SALT)
);
String msg = m.getOptionalString(INVITATION_MSG);
if (msg != null) list.add(msg);
} else if (type == SHARE_MSG_TYPE_ACCEPT) {
list = BdfList.of(type, m.getRaw(SESSION_ID));
} else if (type == SHARE_MSG_TYPE_DECLINE) {
list = BdfList.of(type, m.getRaw(SESSION_ID));
} else if (type == SHARE_MSG_TYPE_LEAVE) {
list = BdfList.of(type, m.getRaw(SESSION_ID));
} else if (type == SHARE_MSG_TYPE_ABORT) {
list = BdfList.of(type, m.getRaw(SESSION_ID));
} else {
throw new FormatException();
}
return list;
}
private Group getContactGroup(Contact c) {
return privateGroupFactory.createPrivateGroup(CLIENT_ID, 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 leaveForum(Transaction txn, ContactId c, Forum f)
throws DbException, FormatException {
BdfDictionary state = getSessionStateForLeaving(txn, f, c);
BdfDictionary action = new BdfDictionary();
action.put(TYPE, SHARE_MSG_TYPE_LEAVE);
if (state.getBoolean(IS_SHARER)) {
SharerEngine engine = new SharerEngine();
processStateUpdate(txn, null,
engine.onLocalAction(state, action));
} else {
InviteeEngine engine = new InviteeEngine();
processStateUpdate(txn, null,
engine.onLocalAction(state, action));
}
}
private boolean listContains(Transaction txn, GroupId contactGroup,
GroupId forum, String key) throws DbException, FormatException {
List<Forum> list = getForumList(txn, contactGroup, key);
for (Forum f : list) {
if (f.getId().equals(forum)) return true;
}
return false;
}
private boolean addToList(Transaction txn, GroupId groupId, String key,
Forum f) throws DbException, FormatException {
List<Forum> forums = getForumList(txn, groupId, key);
if (forums.contains(f)) return false;
forums.add(f);
storeForumList(txn, groupId, key, forums);
return true;
}
private boolean removeFromList(Transaction txn, GroupId groupId, String key,
Forum f) throws DbException, FormatException {
List<Forum> forums = getForumList(txn, groupId, key);
if (forums.remove(f)) {
storeForumList(txn, groupId, key, forums);
return true;
}
return false;
}
private List<Forum> getForumList(Transaction txn, GroupId groupId,
String key) throws DbException, FormatException {
BdfDictionary metadata =
clientHelper.getGroupMetadataAsDictionary(txn, groupId);
BdfList list = metadata.getList(key);
return parseForumList(list);
}
private void storeForumList(Transaction txn, GroupId groupId, String key,
List<Forum> forums) throws DbException, FormatException {
BdfList list = encodeForumList(forums);
BdfDictionary metadata = BdfDictionary.of(
new BdfEntry(key, list)
);
clientHelper.mergeGroupMetadata(txn, groupId, metadata);
}
private BdfList encodeForumList(List<Forum> forums) {
BdfList forumList = new BdfList();
for (Forum f : forums)
forumList.add(BdfList.of(f.getName(), f.getSalt()));
return forumList;
}
private List<Forum> parseForumList(BdfList list) throws FormatException {
List<Forum> forums = new ArrayList<Forum>(list.size());
for (int i = 0; i < list.size(); i++) {
BdfList forum = list.getList(i);
forums.add(forumManager
.createForum(forum.getString(0), forum.getRaw(1)));
}
return forums;
}
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

@@ -1,81 +0,0 @@
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;
}
}

View File

@@ -1,265 +0,0 @@
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());
}
}

View File

@@ -1,264 +0,0 @@
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());
}
}