mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Moved subscription updates to the client layer.
This commit is contained in:
@@ -17,11 +17,10 @@ import org.briarproject.api.db.NoSuchGroupException;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.event.GroupAddedEvent;
|
||||
import org.briarproject.api.event.GroupRemovedEvent;
|
||||
import org.briarproject.api.event.MessageValidatedEvent;
|
||||
import org.briarproject.api.forum.Forum;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.forum.ForumSharingManager;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -44,7 +43,7 @@ implements EventListener, OnItemClickListener {
|
||||
private ListView list = null;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject private volatile ForumManager forumManager;
|
||||
@Inject private volatile ForumSharingManager forumSharingManager;
|
||||
@Inject private volatile EventBus eventBus;
|
||||
|
||||
@Override
|
||||
@@ -76,11 +75,10 @@ implements EventListener, OnItemClickListener {
|
||||
Collection<ForumContacts> available =
|
||||
new ArrayList<ForumContacts>();
|
||||
long now = System.currentTimeMillis();
|
||||
for (Forum f : forumManager.getAvailableForums()) {
|
||||
for (Forum f : forumSharingManager.getAvailableForums()) {
|
||||
try {
|
||||
GroupId id = f.getId();
|
||||
Collection<Contact> c =
|
||||
forumManager.getSubscribers(id);
|
||||
forumSharingManager.getSharedBy(f.getId());
|
||||
available.add(new ForumContacts(f, c));
|
||||
} catch (NoSuchGroupException e) {
|
||||
// Continue
|
||||
@@ -122,17 +120,12 @@ implements EventListener, OnItemClickListener {
|
||||
}
|
||||
|
||||
public void eventOccurred(Event e) {
|
||||
// TODO: What other events are needed here?
|
||||
if (e instanceof GroupAddedEvent) {
|
||||
GroupAddedEvent g = (GroupAddedEvent) e;
|
||||
if (g.getGroup().getClientId().equals(forumManager.getClientId())) {
|
||||
LOG.info("Forum added, reloading");
|
||||
loadForums();
|
||||
}
|
||||
} else if (e instanceof GroupRemovedEvent) {
|
||||
GroupRemovedEvent g = (GroupRemovedEvent) e;
|
||||
if (g.getGroup().getClientId().equals(forumManager.getClientId())) {
|
||||
LOG.info("Forum removed, reloading");
|
||||
if (e instanceof MessageValidatedEvent) {
|
||||
MessageValidatedEvent m = (MessageValidatedEvent) e;
|
||||
ClientId c = m.getClientId();
|
||||
if (m.isValid() && !m.isLocal()
|
||||
&& c.equals(forumSharingManager.getClientId())) {
|
||||
LOG.info("Available forums updated, reloading");
|
||||
loadForums();
|
||||
}
|
||||
}
|
||||
@@ -141,20 +134,20 @@ implements EventListener, OnItemClickListener {
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position,
|
||||
long id) {
|
||||
AvailableForumsItem item = adapter.getItem(position);
|
||||
Collection<ContactId> visible = new ArrayList<ContactId>();
|
||||
for (Contact c : item.getContacts()) visible.add(c.getId());
|
||||
addSubscription(item.getForum(), visible);
|
||||
Collection<ContactId> shared = new ArrayList<ContactId>();
|
||||
for (Contact c : item.getContacts()) shared.add(c.getId());
|
||||
subscribe(item.getForum(), shared);
|
||||
String subscribed = getString(R.string.subscribed_toast);
|
||||
Toast.makeText(this, subscribed, LENGTH_SHORT).show();
|
||||
loadForums();
|
||||
}
|
||||
|
||||
private void addSubscription(final Forum f,
|
||||
final Collection<ContactId> visible) {
|
||||
private void subscribe(final Forum f, final Collection<ContactId> shared) {
|
||||
runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
forumManager.addForum(f);
|
||||
forumManager.setVisibility(f.getId(), visible);
|
||||
forumSharingManager.addForum(f);
|
||||
forumSharingManager.setSharedWith(f.getId(), shared);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
|
||||
@@ -18,7 +18,7 @@ import org.briarproject.android.BriarActivity;
|
||||
import org.briarproject.android.util.LayoutUtils;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.forum.Forum;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
import org.briarproject.api.forum.ForumSharingManager;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
@@ -51,7 +51,7 @@ implements OnEditorActionListener, OnClickListener {
|
||||
private TextView feedback = null;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject private volatile ForumManager forumManager;
|
||||
@Inject private volatile ForumSharingManager forumSharingManager;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
@@ -138,8 +138,8 @@ implements OnEditorActionListener, OnClickListener {
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
Forum f = forumManager.createForum(name);
|
||||
forumManager.addForum(f);
|
||||
Forum f = forumSharingManager.createForum(name);
|
||||
forumSharingManager.addForum(f);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Storing forum took " + duration + " ms");
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.briarproject.android.util.LayoutUtils;
|
||||
import org.briarproject.android.util.ListLoadingProgressBar;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.NoSuchGroupException;
|
||||
import org.briarproject.api.event.ContactRemovedEvent;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.GroupAddedEvent;
|
||||
import org.briarproject.api.event.GroupRemovedEvent;
|
||||
@@ -32,6 +33,7 @@ import org.briarproject.api.event.MessageValidatedEvent;
|
||||
import org.briarproject.api.forum.Forum;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
import org.briarproject.api.forum.ForumPostHeader;
|
||||
import org.briarproject.api.forum.ForumSharingManager;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
@@ -81,8 +83,8 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
private ImageButton newForumButton = null;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject
|
||||
private volatile ForumManager forumManager;
|
||||
@Inject private volatile ForumManager forumManager;
|
||||
@Inject private volatile ForumSharingManager forumSharingManager;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -171,7 +173,8 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
// Continue
|
||||
}
|
||||
}
|
||||
int available = forumManager.getAvailableForums().size();
|
||||
int available =
|
||||
forumSharingManager.getAvailableForums().size();
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Full load took " + duration + " ms");
|
||||
@@ -252,14 +255,9 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
}
|
||||
|
||||
public void eventOccurred(Event e) {
|
||||
// TODO: What other events are needed here?
|
||||
if (e instanceof MessageValidatedEvent) {
|
||||
MessageValidatedEvent m = (MessageValidatedEvent) e;
|
||||
ClientId c = m.getClientId();
|
||||
if (m.isValid() && c.equals(forumManager.getClientId())) {
|
||||
LOG.info("Message added, reloading");
|
||||
loadHeaders(m.getMessage().getGroupId());
|
||||
}
|
||||
if (e instanceof ContactRemovedEvent) {
|
||||
LOG.info("Contact removed, reloading");
|
||||
loadAvailable();
|
||||
} else if (e instanceof GroupAddedEvent) {
|
||||
GroupAddedEvent g = (GroupAddedEvent) e;
|
||||
if (g.getGroup().getClientId().equals(forumManager.getClientId())) {
|
||||
@@ -272,6 +270,18 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
LOG.info("Forum removed, reloading");
|
||||
loadHeaders();
|
||||
}
|
||||
} else if (e instanceof MessageValidatedEvent) {
|
||||
MessageValidatedEvent m = (MessageValidatedEvent) e;
|
||||
if (m.isValid()) {
|
||||
ClientId c = m.getClientId();
|
||||
if (c.equals(forumManager.getClientId())) {
|
||||
LOG.info("Forum post added, reloading");
|
||||
loadHeaders(m.getMessage().getGroupId());
|
||||
} else if (c.equals(forumSharingManager.getClientId())) {
|
||||
LOG.info("Available forums updated, reloading");
|
||||
loadAvailable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +329,8 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
int available = forumManager.getAvailableForums().size();
|
||||
int available =
|
||||
forumSharingManager.getAvailableForums().size();
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading available took " + duration + " ms");
|
||||
@@ -363,22 +374,22 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
ContextMenuInfo info = menuItem.getMenuInfo();
|
||||
int position = ((AdapterContextMenuInfo) info).position;
|
||||
ForumListItem item = adapter.getItem(position);
|
||||
removeSubscription(item.getForum());
|
||||
unsubscribe(item.getForum());
|
||||
String unsubscribed = getString(R.string.unsubscribed_toast);
|
||||
Toast.makeText(getContext(), unsubscribed, LENGTH_SHORT).show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void removeSubscription(final Forum f) {
|
||||
private void unsubscribe(final Forum f) {
|
||||
listener.runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
forumManager.removeForum(f);
|
||||
forumSharingManager.removeForum(f);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Removing group took " + duration + " ms");
|
||||
LOG.info("Removing forum took " + duration + " ms");
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.contact.ContactManager;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
import org.briarproject.api.forum.ForumSharingManager;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -52,7 +52,7 @@ SelectContactsDialog.Listener {
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject private volatile ContactManager contactManager;
|
||||
@Inject private volatile ForumManager forumManager;
|
||||
@Inject private volatile ForumSharingManager forumSharingManager;
|
||||
private volatile GroupId groupId = null;
|
||||
private volatile Collection<Contact> contacts = null;
|
||||
private volatile Collection<ContactId> selected = null;
|
||||
@@ -139,7 +139,7 @@ SelectContactsDialog.Listener {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
contacts = contactManager.getContacts();
|
||||
selected = forumManager.getVisibility(groupId);
|
||||
selected = forumSharingManager.getSharedWith(groupId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Load took " + duration + " ms");
|
||||
@@ -175,8 +175,9 @@ SelectContactsDialog.Listener {
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
forumManager.setVisibleToAll(groupId, all);
|
||||
if (!all) forumManager.setVisibility(groupId, selected);
|
||||
if (all) forumSharingManager.setSharedWithAll(groupId);
|
||||
else forumSharingManager.setSharedWith(groupId,
|
||||
selected);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Update took " + duration + " ms");
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
package org.briarproject.plugins;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Executor;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.inject.Provides;
|
||||
|
||||
import org.briarproject.api.android.AndroidExecutor;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.api.plugins.duplex.DuplexPluginConfig;
|
||||
import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
|
||||
import org.briarproject.api.plugins.simplex.SimplexPluginConfig;
|
||||
import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.api.system.LocationUtils;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.plugins.droidtooth.DroidtoothPluginFactory;
|
||||
import org.briarproject.plugins.tcp.AndroidLanTcpPluginFactory;
|
||||
import org.briarproject.plugins.tor.TorPluginFactory;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.inject.Provides;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class AndroidPluginsModule extends PluginsModule {
|
||||
|
||||
@@ -37,11 +37,11 @@ public class AndroidPluginsModule extends PluginsModule {
|
||||
@Provides
|
||||
DuplexPluginConfig getDuplexPluginConfig(@IoExecutor Executor ioExecutor,
|
||||
AndroidExecutor androidExecutor, Application app,
|
||||
CryptoComponent crypto, LocationUtils locationUtils,
|
||||
SecureRandom random, LocationUtils locationUtils,
|
||||
EventBus eventBus) {
|
||||
Context appContext = app.getApplicationContext();
|
||||
DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor,
|
||||
androidExecutor, appContext, crypto.getSecureRandom());
|
||||
androidExecutor, appContext, random);
|
||||
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, appContext,
|
||||
locationUtils, eventBus);
|
||||
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
||||
|
||||
@@ -179,6 +179,9 @@ public interface DatabaseComponent {
|
||||
void incrementStreamCounter(ContactId c, TransportId t, long rotationPeriod)
|
||||
throws DbException;
|
||||
|
||||
/** Returns true if the given group is visible to the given contact. */
|
||||
boolean isVisibleToContact(ContactId c, GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Merges the given metadata with the existing metadata for the given
|
||||
* group.
|
||||
@@ -246,16 +249,14 @@ public interface DatabaseComponent {
|
||||
|
||||
/**
|
||||
* Makes a group visible to the given set of contacts and invisible to any
|
||||
* other current or future contacts.
|
||||
* other contacts.
|
||||
*/
|
||||
void setVisibility(GroupId g, Collection<ContactId> visible)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Makes a group visible to all current and future contacts, or invisible
|
||||
* to future contacts.
|
||||
*/
|
||||
void setVisibleToAll(GroupId g, boolean all) throws DbException;
|
||||
/** Makes a group visible or invisible to a contact. */
|
||||
void setVisibleToContact(ContactId c, GroupId g, boolean visible)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given transport keys, deleting any keys they have replaced.
|
||||
|
||||
@@ -7,10 +7,12 @@ public class Forum {
|
||||
|
||||
private final Group group;
|
||||
private final String name;
|
||||
private final byte[] salt;
|
||||
|
||||
public Forum(Group group, String name) {
|
||||
public Forum(Group group, String name, byte[] salt) {
|
||||
this.group = group;
|
||||
this.name = name;
|
||||
this.salt = salt;
|
||||
}
|
||||
|
||||
public GroupId getId() {
|
||||
@@ -25,6 +27,10 @@ public class Forum {
|
||||
return name;
|
||||
}
|
||||
|
||||
public byte[] getSalt() {
|
||||
return salt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return group.hashCode();
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
package org.briarproject.api.forum;
|
||||
|
||||
import static org.briarproject.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||
|
||||
public interface ForumConstants {
|
||||
|
||||
/** The maximum length of a forum's name in bytes. */
|
||||
int MAX_FORUM_NAME_LENGTH = MAX_GROUP_DESCRIPTOR_LENGTH - 10;
|
||||
/** The maximum length of a forum's name in UTF-8 bytes. */
|
||||
int MAX_FORUM_NAME_LENGTH = 100;
|
||||
|
||||
/** The length of a forum's random salt in bytes. */
|
||||
int FORUM_SALT_LENGTH = 32;
|
||||
|
||||
/** The maximum length of a forum post's content type in bytes. */
|
||||
/** The maximum length of a forum post's content type in UTF-8 bytes. */
|
||||
int MAX_CONTENT_TYPE_LENGTH = 50;
|
||||
|
||||
/** The maximum length of a forum post's body in bytes. */
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.briarproject.api.forum;
|
||||
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
@@ -14,18 +12,9 @@ public interface ForumManager {
|
||||
/** Returns the unique ID of the forum client. */
|
||||
ClientId getClientId();
|
||||
|
||||
/** Creates a forum with the given name. */
|
||||
Forum createForum(String name);
|
||||
|
||||
/** Subscribes to a forum. */
|
||||
void addForum(Forum f) throws DbException;
|
||||
|
||||
/** Stores a local forum post. */
|
||||
void addLocalPost(ForumPost p) throws DbException;
|
||||
|
||||
/** Returns all forums to which the user could subscribe. */
|
||||
Collection<Forum> getAvailableForums() throws DbException;
|
||||
|
||||
/** Returns the forum with the given ID. */
|
||||
Forum getForum(GroupId g) throws DbException;
|
||||
|
||||
@@ -38,28 +27,6 @@ public interface ForumManager {
|
||||
/** Returns the headers of all posts in the given forum. */
|
||||
Collection<ForumPostHeader> getPostHeaders(GroupId g) throws DbException;
|
||||
|
||||
/** Returns all contacts who subscribe to the given forum. */
|
||||
Collection<Contact> getSubscribers(GroupId g) throws DbException;
|
||||
|
||||
/** Returns the IDs of all contacts to which the given forum is visible. */
|
||||
Collection<ContactId> getVisibility(GroupId g) throws DbException;
|
||||
|
||||
/** Unsubscribes from a forum. */
|
||||
void removeForum(Forum f) throws DbException;
|
||||
|
||||
/** Marks a forum post as read or unread. */
|
||||
void setReadFlag(MessageId m, boolean read) throws DbException;
|
||||
|
||||
/**
|
||||
* Makes a forum visible to the given set of contacts and invisible to any
|
||||
* other current or future contacts.
|
||||
*/
|
||||
void setVisibility(GroupId g, Collection<ContactId> visible)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Makes a forum visible to all current and future contacts, or invisible
|
||||
* to future contacts.
|
||||
*/
|
||||
void setVisibleToAll(GroupId g, boolean all) throws DbException;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.briarproject.api.forum;
|
||||
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface ForumSharingManager {
|
||||
|
||||
/** Returns the unique ID of the forum sharing client. */
|
||||
ClientId getClientId();
|
||||
|
||||
/** Creates a forum with the given name. */
|
||||
Forum createForum(String name);
|
||||
|
||||
/** Subscribes to a forum. */
|
||||
void addForum(Forum f) throws DbException;
|
||||
|
||||
/** Unsubscribes from a forum. */
|
||||
void removeForum(Forum f) throws DbException;
|
||||
|
||||
/** Returns all forums to which the user could subscribe. */
|
||||
Collection<Forum> getAvailableForums() throws DbException;
|
||||
|
||||
/** Returns all contacts who are sharing the given forum with the user. */
|
||||
Collection<Contact> getSharedBy(GroupId g) throws DbException;
|
||||
|
||||
/** Returns the IDs of all contacts with whom the given forum is shared. */
|
||||
Collection<ContactId> getSharedWith(GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Shares a forum with the given contacts and unshares it with any other
|
||||
* contacts.
|
||||
*/
|
||||
void setSharedWith(GroupId g, Collection<ContactId> shared)
|
||||
throws DbException;
|
||||
|
||||
/** Shares a forum with all current and future contacts. */
|
||||
void setSharedWithAll(GroupId g) throws DbException;
|
||||
}
|
||||
@@ -8,5 +8,5 @@ public interface MessageValidator {
|
||||
* Validates the given message and returns its metadata if the message
|
||||
* is valid, or null if the message is invalid.
|
||||
*/
|
||||
Metadata validateMessage(Message m);
|
||||
Metadata validateMessage(Message m, Group g);
|
||||
}
|
||||
|
||||
@@ -598,13 +598,6 @@ interface Database<T> {
|
||||
void setReorderingWindow(T txn, ContactId c, TransportId t,
|
||||
long rotationPeriod, long base, byte[] bitmap) throws DbException;
|
||||
|
||||
/**
|
||||
* Makes a group visible or invisible to future contacts by default.
|
||||
* <p>
|
||||
* Locking: write.
|
||||
*/
|
||||
void setVisibleToAll(T txn, GroupId g, boolean all) throws DbException;
|
||||
|
||||
/**
|
||||
* Updates the transmission count and expiry time of the given message
|
||||
* with respect to the given contact, using the latency of the transport
|
||||
|
||||
@@ -223,6 +223,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
if (added) {
|
||||
eventBus.broadcast(new MessageAddedEvent(m, null));
|
||||
eventBus.broadcast(new MessageValidatedEvent(m, c, true, true));
|
||||
if (shared) eventBus.broadcast(new MessageSharedEvent(m));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -801,6 +802,28 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isVisibleToContact(ContactId c, GroupId g)
|
||||
throws DbException {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
if (!db.containsGroup(txn, g))
|
||||
throw new NoSuchGroupException();
|
||||
boolean visible = db.containsVisibleGroup(txn, c, g);
|
||||
db.commitTransaction(txn);
|
||||
return visible;
|
||||
} catch (DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void mergeGroupMetadata(GroupId g, Metadata meta)
|
||||
throws DbException {
|
||||
lock.writeLock().lock();
|
||||
@@ -900,7 +923,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
duplicate = db.containsMessage(txn, m.getId());
|
||||
visible = db.containsVisibleGroup(txn, c, m.getGroupId());
|
||||
if (visible) {
|
||||
if (!duplicate) addMessage(txn, m, UNKNOWN, true, c);
|
||||
if (!duplicate) addMessage(txn, m, UNKNOWN, false, c);
|
||||
db.raiseAckFlag(txn, c, m.getId());
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
@@ -1162,7 +1185,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
if (!db.containsGroup(txn, g))
|
||||
throw new NoSuchGroupException();
|
||||
// Use HashSets for O(1) lookups, O(n) overall running time
|
||||
HashSet<ContactId> now = new HashSet<ContactId>(visible);
|
||||
Collection<ContactId> now = new HashSet<ContactId>(visible);
|
||||
Collection<ContactId> before = db.getVisibility(txn, g);
|
||||
before = new HashSet<ContactId>(before);
|
||||
// Set the group's visibility for each current contact
|
||||
@@ -1177,8 +1200,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
affected.add(c);
|
||||
}
|
||||
}
|
||||
// Make the group invisible to future contacts
|
||||
db.setVisibleToAll(txn, g, false);
|
||||
db.commitTransaction(txn);
|
||||
} catch (DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
@@ -1191,27 +1212,20 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
eventBus.broadcast(new GroupVisibilityUpdatedEvent(affected));
|
||||
}
|
||||
|
||||
public void setVisibleToAll(GroupId g, boolean all) throws DbException {
|
||||
Collection<ContactId> affected = new ArrayList<ContactId>();
|
||||
public void setVisibleToContact(ContactId c, GroupId g, boolean visible)
|
||||
throws DbException {
|
||||
boolean wasVisible = false;
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
if (!db.containsGroup(txn, g))
|
||||
throw new NoSuchGroupException();
|
||||
// Make the group visible or invisible to future contacts
|
||||
db.setVisibleToAll(txn, g, all);
|
||||
if (all) {
|
||||
// Make the group visible to all current contacts
|
||||
Collection<ContactId> before = db.getVisibility(txn, g);
|
||||
before = new HashSet<ContactId>(before);
|
||||
for (ContactId c : db.getContactIds(txn)) {
|
||||
if (!before.contains(c)) {
|
||||
db.addVisibility(txn, c, g);
|
||||
affected.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
wasVisible = db.containsVisibleGroup(txn, c, g);
|
||||
if (visible && !wasVisible) db.addVisibility(txn, c, g);
|
||||
else if (!visible && wasVisible) db.removeVisibility(txn, c, g);
|
||||
db.commitTransaction(txn);
|
||||
} catch (DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
@@ -1220,8 +1234,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
if (!affected.isEmpty())
|
||||
eventBus.broadcast(new GroupVisibilityUpdatedEvent(affected));
|
||||
if (visible != wasVisible) {
|
||||
eventBus.broadcast(new GroupVisibilityUpdatedEvent(
|
||||
Collections.singletonList(c)));
|
||||
}
|
||||
}
|
||||
|
||||
public void updateTransportKeys(Map<ContactId, TransportKeys> keys)
|
||||
|
||||
@@ -66,8 +66,8 @@ import static org.briarproject.db.ExponentialBackoff.calculateExpiry;
|
||||
*/
|
||||
abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
private static final int SCHEMA_VERSION = 19;
|
||||
private static final int MIN_SCHEMA_VERSION = 19;
|
||||
private static final int SCHEMA_VERSION = 20;
|
||||
private static final int MIN_SCHEMA_VERSION = 20;
|
||||
|
||||
private static final String CREATE_SETTINGS =
|
||||
"CREATE TABLE settings"
|
||||
@@ -104,7 +104,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " (groupId HASH NOT NULL,"
|
||||
+ " clientId HASH NOT NULL,"
|
||||
+ " descriptor BINARY NOT NULL,"
|
||||
+ " visibleToAll BOOLEAN NOT NULL,"
|
||||
+ " PRIMARY KEY (groupId))";
|
||||
|
||||
private static final String CREATE_GROUP_METADATA =
|
||||
@@ -511,30 +510,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
if (rows != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
}
|
||||
// Make groups that are visible to everyone visible to this contact
|
||||
sql = "SELECT groupId FROM groups WHERE visibleToAll = TRUE";
|
||||
ps = txn.prepareStatement(sql);
|
||||
rs = ps.executeQuery();
|
||||
ids = new ArrayList<byte[]>();
|
||||
while (rs.next()) ids.add(rs.getBytes(1));
|
||||
rs.close();
|
||||
ps.close();
|
||||
if (!ids.isEmpty()) {
|
||||
sql = "INSERT INTO groupVisibilities (contactId, groupId)"
|
||||
+ " VALUES (?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
for (byte[] id : ids) {
|
||||
ps.setBytes(2, id);
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != ids.size())
|
||||
throw new DbStateException();
|
||||
for (int rows : batchAffected)
|
||||
if (rows != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
}
|
||||
return c;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
@@ -546,9 +521,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
public void addGroup(Connection txn, Group g) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO groups"
|
||||
+ " (groupId, clientId, descriptor, visibleToAll)"
|
||||
+ " VALUES (?, ?, ?, FALSE)";
|
||||
String sql = "INSERT INTO groups (groupId, clientId, descriptor)"
|
||||
+ " VALUES (?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getId().getBytes());
|
||||
ps.setBytes(2, g.getClientId().getBytes());
|
||||
@@ -2137,23 +2111,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
public void setVisibleToAll(Connection txn, GroupId g, boolean all)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "UPDATE groups SET visibleToAll = ? WHERE groupId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBoolean(1, all);
|
||||
ps.setBytes(2, g.getBytes());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateExpiryTime(Connection txn, ContactId c, MessageId m,
|
||||
int maxLatency) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.briarproject.forum;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfReader;
|
||||
import org.briarproject.api.data.BdfReaderFactory;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageValidator;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
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.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
|
||||
class ForumListValidator implements MessageValidator {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ForumListValidator.class.getName());
|
||||
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final MetadataEncoder metadataEncoder;
|
||||
|
||||
ForumListValidator(BdfReaderFactory bdfReaderFactory,
|
||||
MetadataEncoder metadataEncoder) {
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.metadataEncoder = metadataEncoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metadata validateMessage(Message m, Group g) {
|
||||
try {
|
||||
// Parse the message body
|
||||
byte[] raw = m.getRaw();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
||||
BdfReader r = bdfReaderFactory.createReader(in);
|
||||
r.readListStart();
|
||||
long version = r.readInteger();
|
||||
if (version < 0) throw new FormatException();
|
||||
r.readListStart();
|
||||
while (!r.hasListEnd()) {
|
||||
r.readListStart();
|
||||
String name = r.readString(MAX_FORUM_NAME_LENGTH);
|
||||
if (name.length() == 0) throw new FormatException();
|
||||
byte[] salt = r.readRaw(FORUM_SALT_LENGTH);
|
||||
if (salt.length != FORUM_SALT_LENGTH)
|
||||
throw new FormatException();
|
||||
r.readListEnd();
|
||||
}
|
||||
r.readListEnd();
|
||||
r.readListEnd();
|
||||
if (!r.eof()) throw new FormatException();
|
||||
// Return the metadata
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put("version", version);
|
||||
d.put("local", false);
|
||||
return metadataEncoder.encode(d);
|
||||
} catch (IOException e) {
|
||||
LOG.info("Invalid forum list");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,10 @@ import com.google.inject.Inject;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.contact.ContactManager;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfReader;
|
||||
import org.briarproject.api.data.BdfReaderFactory;
|
||||
import org.briarproject.api.data.BdfWriter;
|
||||
import org.briarproject.api.data.BdfWriterFactory;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.data.MetadataParser;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
@@ -25,15 +22,12 @@ 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.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -42,6 +36,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
@@ -63,25 +58,23 @@ class ForumManagerImpl implements ForumManager {
|
||||
Logger.getLogger(ForumManagerImpl.class.getName());
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final GroupFactory groupFactory;
|
||||
private final ContactManager contactManager;
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final BdfWriterFactory bdfWriterFactory;
|
||||
private final MetadataEncoder metadataEncoder;
|
||||
private final MetadataParser metadataParser;
|
||||
private final SecureRandom random;
|
||||
|
||||
/** Ensures isolation between database reads and writes. */
|
||||
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
|
||||
@Inject
|
||||
ForumManagerImpl(CryptoComponent crypto, DatabaseComponent db,
|
||||
GroupFactory groupFactory, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder,
|
||||
ForumManagerImpl(DatabaseComponent db, ContactManager contactManager,
|
||||
BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
|
||||
MetadataParser metadataParser) {
|
||||
this.db = db;
|
||||
this.groupFactory = groupFactory;
|
||||
this.contactManager = contactManager;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
this.metadataEncoder = metadataEncoder;
|
||||
this.metadataParser = metadataParser;
|
||||
random = crypto.getSecureRandom();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,62 +82,154 @@ class ForumManagerImpl implements ForumManager {
|
||||
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);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
BdfWriter w = bdfWriterFactory.createWriter(out);
|
||||
try {
|
||||
w.writeListStart();
|
||||
w.writeString(name);
|
||||
w.writeRaw(salt);
|
||||
w.writeListEnd();
|
||||
} catch (IOException e) {
|
||||
// Shouldn't happen with ByteArrayOutputStream
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
Group g = groupFactory.createGroup(CLIENT_ID, out.toByteArray());
|
||||
return new Forum(g, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addForum(Forum f) throws DbException {
|
||||
db.addGroup(f.getGroup());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLocalPost(ForumPost p) throws DbException {
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put("timestamp", p.getMessage().getTimestamp());
|
||||
if (p.getParent() != null) d.put("parent", p.getParent().getBytes());
|
||||
if (p.getAuthor() != null) {
|
||||
Author a = p.getAuthor();
|
||||
BdfDictionary d1 = new BdfDictionary();
|
||||
d1.put("id", a.getId().getBytes());
|
||||
d1.put("name", a.getName());
|
||||
d1.put("publicKey", a.getPublicKey());
|
||||
d.put("author", d1);
|
||||
}
|
||||
d.put("contentType", p.getContentType());
|
||||
d.put("local", true);
|
||||
d.put("read", true);
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put("timestamp", p.getMessage().getTimestamp());
|
||||
if (p.getParent() != null)
|
||||
d.put("parent", p.getParent().getBytes());
|
||||
if (p.getAuthor() != null) {
|
||||
Author a = p.getAuthor();
|
||||
BdfDictionary d1 = new BdfDictionary();
|
||||
d1.put("id", a.getId().getBytes());
|
||||
d1.put("name", a.getName());
|
||||
d1.put("publicKey", a.getPublicKey());
|
||||
d.put("author", d1);
|
||||
}
|
||||
d.put("contentType", p.getContentType());
|
||||
d.put("local", true);
|
||||
d.put("read", true);
|
||||
Metadata meta = metadataEncoder.encode(d);
|
||||
db.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Forum> getAvailableForums() throws DbException {
|
||||
// TODO
|
||||
return Collections.emptyList();
|
||||
public Forum getForum(GroupId g) throws DbException {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return parseForum(db.getGroup(g));
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Forum> getForums() throws DbException {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
List<Forum> forums = new ArrayList<Forum>();
|
||||
for (Group g : db.getGroups(CLIENT_ID)) forums.add(parseForum(g));
|
||||
return Collections.unmodifiableList(forums);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getPostBody(MessageId m) throws DbException {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
byte[] raw = db.getRawMessage(m);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
||||
BdfReader r = bdfReaderFactory.createReader(in);
|
||||
r.readListStart();
|
||||
if (r.hasRaw()) r.skipRaw(); // Parent ID
|
||||
else r.skipNull(); // No parent
|
||||
if (r.hasList()) r.skipList(); // Author
|
||||
else r.skipNull(); // No author
|
||||
r.skipString(); // Content type
|
||||
byte[] postBody = r.readRaw(MAX_FORUM_POST_BODY_LENGTH);
|
||||
if (r.hasRaw()) r.skipRaw(); // Signature
|
||||
else r.skipNull();
|
||||
r.readListEnd();
|
||||
if (!r.eof()) throw new FormatException();
|
||||
return postBody;
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
} catch (IOException e) {
|
||||
// Shouldn't happen with ByteArrayInputStream
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ForumPostHeader> getPostHeaders(GroupId g)
|
||||
throws DbException {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
// Load the IDs of the user's identities
|
||||
Set<AuthorId> localAuthorIds = new HashSet<AuthorId>();
|
||||
for (LocalAuthor a : db.getLocalAuthors())
|
||||
localAuthorIds.add(a.getId());
|
||||
// Load the IDs of contacts' identities
|
||||
Set<AuthorId> contactAuthorIds = new HashSet<AuthorId>();
|
||||
for (Contact c : contactManager.getContacts())
|
||||
contactAuthorIds.add(c.getAuthor().getId());
|
||||
// Load and parse the metadata
|
||||
Map<MessageId, Metadata> metadata = db.getMessageMetadata(g);
|
||||
Collection<ForumPostHeader> headers =
|
||||
new ArrayList<ForumPostHeader>();
|
||||
for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
|
||||
MessageId messageId = e.getKey();
|
||||
Metadata meta = e.getValue();
|
||||
try {
|
||||
BdfDictionary d = metadataParser.parse(meta);
|
||||
long timestamp = d.getInteger("timestamp");
|
||||
Author author = null;
|
||||
Author.Status authorStatus = ANONYMOUS;
|
||||
BdfDictionary d1 = d.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 = d.getString("contentType");
|
||||
boolean read = d.getBoolean("read");
|
||||
headers.add(new ForumPostHeader(messageId, timestamp,
|
||||
author, authorStatus, contentType, read));
|
||||
} catch (FormatException ex) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, ex.toString(), ex);
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadFlag(MessageId m, boolean read) throws DbException {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put("read", read);
|
||||
db.mergeMessageMetadata(m, metadataEncoder.encode(d));
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private Forum parseForum(Group g) throws FormatException {
|
||||
@@ -153,12 +238,10 @@ class ForumManagerImpl implements ForumManager {
|
||||
try {
|
||||
r.readListStart();
|
||||
String name = r.readString(MAX_FORUM_NAME_LENGTH);
|
||||
if (name.length() == 0) throw new FormatException();
|
||||
byte[] salt = r.readRaw(FORUM_SALT_LENGTH);
|
||||
if (salt.length != FORUM_SALT_LENGTH) throw new FormatException();
|
||||
r.readListEnd();
|
||||
if (!r.eof()) throw new FormatException();
|
||||
return new Forum(g, name);
|
||||
return new Forum(g, name, salt);
|
||||
} catch (FormatException e) {
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
@@ -166,137 +249,4 @@ class ForumManagerImpl implements ForumManager {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Forum getForum(GroupId g) throws DbException {
|
||||
Group group = db.getGroup(g);
|
||||
if (!group.getClientId().equals(CLIENT_ID))
|
||||
throw new IllegalArgumentException();
|
||||
try {
|
||||
return parseForum(group);
|
||||
} catch (FormatException e) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Forum> getForums() throws DbException {
|
||||
Collection<Group> groups = db.getGroups(CLIENT_ID);
|
||||
List<Forum> forums = new ArrayList<Forum>(groups.size());
|
||||
for (Group g : groups) {
|
||||
try {
|
||||
forums.add(parseForum(g));
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableList(forums);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getPostBody(MessageId m) throws DbException {
|
||||
byte[] raw = db.getRawMessage(m);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
||||
BdfReader r = bdfReaderFactory.createReader(in);
|
||||
try {
|
||||
// Extract the forum post body
|
||||
r.readListStart();
|
||||
if (r.hasRaw()) r.skipRaw(); // Parent ID
|
||||
else r.skipNull(); // No parent
|
||||
if (r.hasList()) r.skipList(); // Author
|
||||
else r.skipNull(); // No author
|
||||
r.skipString(); // Content type
|
||||
return r.readRaw(MAX_FORUM_POST_BODY_LENGTH);
|
||||
} catch (FormatException e) {
|
||||
// Not a valid forum post
|
||||
throw new IllegalArgumentException();
|
||||
} catch (IOException e) {
|
||||
// Shouldn't happen with ByteArrayInputStream
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ForumPostHeader> getPostHeaders(GroupId g)
|
||||
throws DbException {
|
||||
// Load the IDs of the user's own identities and contacts' identities
|
||||
Set<AuthorId> localAuthorIds = new HashSet<AuthorId>();
|
||||
for (LocalAuthor a : db.getLocalAuthors())
|
||||
localAuthorIds.add(a.getId());
|
||||
Set<AuthorId> contactAuthorIds = new HashSet<AuthorId>();
|
||||
for (Contact c : db.getContacts())
|
||||
contactAuthorIds.add(c.getAuthor().getId());
|
||||
// Load and parse the metadata
|
||||
Map<MessageId, Metadata> metadata = db.getMessageMetadata(g);
|
||||
Collection<ForumPostHeader> headers = new ArrayList<ForumPostHeader>();
|
||||
for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
|
||||
MessageId messageId = e.getKey();
|
||||
Metadata meta = e.getValue();
|
||||
try {
|
||||
BdfDictionary d = metadataParser.parse(meta);
|
||||
long timestamp = d.getInteger("timestamp");
|
||||
Author author = null;
|
||||
Author.Status authorStatus = ANONYMOUS;
|
||||
BdfDictionary d1 = d.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 = d.getString("contentType");
|
||||
boolean read = d.getBoolean("read");
|
||||
headers.add(new ForumPostHeader(messageId, timestamp, author,
|
||||
authorStatus, contentType, read));
|
||||
} catch (FormatException ex) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, ex.toString(), ex);
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Contact> getSubscribers(GroupId g) throws DbException {
|
||||
// TODO
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ContactId> getVisibility(GroupId g) throws DbException {
|
||||
return db.getVisibility(g);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeForum(Forum f) throws DbException {
|
||||
db.removeGroup(f.getGroup());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadFlag(MessageId m, boolean read) throws DbException {
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put("read", read);
|
||||
try {
|
||||
db.mergeMessageMetadata(m, metadataEncoder.encode(d));
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisibility(GroupId g, Collection<ContactId> visible)
|
||||
throws DbException {
|
||||
db.setVisibility(g, visible);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisibleToAll(GroupId g, boolean all) throws DbException {
|
||||
db.setVisibleToAll(g, all);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,39 +3,63 @@ package org.briarproject.forum;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
|
||||
import org.briarproject.api.contact.ContactManager;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.data.BdfReaderFactory;
|
||||
import org.briarproject.api.data.BdfWriterFactory;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.data.ObjectReader;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
import org.briarproject.api.forum.ForumPostFactory;
|
||||
import org.briarproject.api.forum.ForumSharingManager;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.sync.ValidationManager;
|
||||
import org.briarproject.api.system.Clock;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import static org.briarproject.forum.ForumManagerImpl.CLIENT_ID;
|
||||
|
||||
public class ForumModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(ForumManager.class).to(ForumManagerImpl.class);
|
||||
bind(ForumManager.class).to(ForumManagerImpl.class).in(Singleton.class);
|
||||
bind(ForumPostFactory.class).to(ForumPostFactoryImpl.class);
|
||||
}
|
||||
|
||||
@Provides @Singleton
|
||||
ForumPostValidator getValidator(ValidationManager validationManager,
|
||||
CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
||||
ForumPostValidator getForumPostValidator(
|
||||
ValidationManager validationManager, CryptoComponent crypto,
|
||||
BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory,
|
||||
ObjectReader<Author> authorReader, MetadataEncoder metadataEncoder,
|
||||
Clock clock) {
|
||||
ForumPostValidator validator = new ForumPostValidator(crypto,
|
||||
bdfReaderFactory, bdfWriterFactory, authorReader,
|
||||
metadataEncoder, clock);
|
||||
validationManager.registerMessageValidator(CLIENT_ID, validator);
|
||||
validationManager.registerMessageValidator(
|
||||
ForumManagerImpl.CLIENT_ID, validator);
|
||||
return validator;
|
||||
}
|
||||
|
||||
@Provides @Singleton
|
||||
ForumListValidator getForumListValidator(
|
||||
ValidationManager validationManager,
|
||||
BdfReaderFactory bdfReaderFactory,
|
||||
MetadataEncoder metadataEncoder) {
|
||||
ForumListValidator validator = new ForumListValidator(bdfReaderFactory,
|
||||
metadataEncoder);
|
||||
validationManager.registerMessageValidator(
|
||||
ForumSharingManagerImpl.CLIENT_ID, validator);
|
||||
return validator;
|
||||
}
|
||||
|
||||
@Provides @Singleton
|
||||
ForumSharingManager getForumSharingManager(ContactManager contactManager,
|
||||
EventBus eventBus, ForumSharingManagerImpl forumSharingManager) {
|
||||
contactManager.registerAddContactHook(forumSharingManager);
|
||||
contactManager.registerRemoveContactHook(forumSharingManager);
|
||||
eventBus.addListener(forumSharingManager);
|
||||
return forumSharingManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.data.ObjectReader;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.MessageValidator;
|
||||
@@ -26,8 +27,6 @@ import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
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;
|
||||
import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
@@ -47,7 +46,6 @@ class ForumPostValidator implements MessageValidator {
|
||||
private final Clock clock;
|
||||
private final KeyParser keyParser;
|
||||
|
||||
@Inject
|
||||
ForumPostValidator(CryptoComponent crypto,
|
||||
BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory,
|
||||
@@ -63,7 +61,7 @@ class ForumPostValidator implements MessageValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metadata validateMessage(Message m) {
|
||||
public Metadata validateMessage(Message m, Group g) {
|
||||
// Reject the message if it's too far in the future
|
||||
long now = clock.currentTimeMillis();
|
||||
if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
|
||||
@@ -78,8 +76,7 @@ class ForumPostValidator implements MessageValidator {
|
||||
BdfReader r = bdfReaderFactory.createReader(in);
|
||||
MessageId parent = null;
|
||||
Author author = null;
|
||||
String contentType;
|
||||
byte[] postBody, sig = null;
|
||||
byte[] sig = null;
|
||||
r.readListStart();
|
||||
// Read the parent ID, if any
|
||||
if (r.hasRaw()) {
|
||||
@@ -93,9 +90,9 @@ class ForumPostValidator implements MessageValidator {
|
||||
if (r.hasList()) author = authorReader.readObject(r);
|
||||
else r.readNull();
|
||||
// Read the content type
|
||||
contentType = r.readString(MAX_CONTENT_TYPE_LENGTH);
|
||||
String contentType = r.readString(MAX_CONTENT_TYPE_LENGTH);
|
||||
// Read the forum post body
|
||||
postBody = r.readRaw(MAX_FORUM_POST_BODY_LENGTH);
|
||||
byte[] postBody = r.readRaw(MAX_FORUM_POST_BODY_LENGTH);
|
||||
|
||||
// Read the signature, if any
|
||||
if (r.hasRaw()) sig = r.readRaw(MAX_SIGNATURE_LENGTH);
|
||||
|
||||
@@ -0,0 +1,558 @@
|
||||
package org.briarproject.forum;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.contact.ContactManager;
|
||||
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.BdfReader;
|
||||
import org.briarproject.api.data.BdfReaderFactory;
|
||||
import org.briarproject.api.data.BdfWriter;
|
||||
import org.briarproject.api.data.BdfWriterFactory;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.data.MetadataParser;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.db.DatabaseExecutor;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.event.MessageValidatedEvent;
|
||||
import org.briarproject.api.forum.Forum;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
import org.briarproject.api.forum.ForumSharingManager;
|
||||
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.Message;
|
||||
import org.briarproject.api.sync.MessageFactory;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.PrivateGroupFactory;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
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.Executor;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
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.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
|
||||
class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
|
||||
RemoveContactHook, EventListener {
|
||||
|
||||
static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
|
||||
"cd11a5d04dccd9e2931d6fc3df456313"
|
||||
+ "63bb3e9d9d0e9405fccdb051f41f5449"));
|
||||
|
||||
private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0];
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ForumSharingManagerImpl.class.getName());
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final Executor dbExecutor;
|
||||
private final ContactManager contactManager;
|
||||
private final ForumManager forumManager;
|
||||
private final GroupFactory groupFactory;
|
||||
private final PrivateGroupFactory privateGroupFactory;
|
||||
private final MessageFactory messageFactory;
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final BdfWriterFactory bdfWriterFactory;
|
||||
private final MetadataEncoder metadataEncoder;
|
||||
private final MetadataParser metadataParser;
|
||||
private final SecureRandom random;
|
||||
private final Clock clock;
|
||||
private final Group localGroup;
|
||||
|
||||
/** Ensures isolation between database reads and writes. */
|
||||
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
|
||||
@Inject
|
||||
ForumSharingManagerImpl(DatabaseComponent db,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
ContactManager contactManager, ForumManager forumManager,
|
||||
GroupFactory groupFactory, PrivateGroupFactory privateGroupFactory,
|
||||
MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder,
|
||||
MetadataParser metadataParser, SecureRandom random, Clock clock) {
|
||||
this.db = db;
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.contactManager = contactManager;
|
||||
this.forumManager = forumManager;
|
||||
this.groupFactory = groupFactory;
|
||||
this.privateGroupFactory = privateGroupFactory;
|
||||
this.messageFactory = messageFactory;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
this.metadataEncoder = metadataEncoder;
|
||||
this.metadataParser = metadataParser;
|
||||
this.random = random;
|
||||
this.clock = clock;
|
||||
localGroup = groupFactory.createGroup(CLIENT_ID,
|
||||
LOCAL_GROUP_DESCRIPTOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addingContact(ContactId c) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// Create a group to share with the contact
|
||||
Group g = getContactGroup(db.getContact(c));
|
||||
// Store the group and share it with the contact
|
||||
db.addGroup(g);
|
||||
db.setVisibility(g.getId(), Collections.singletonList(c));
|
||||
// Attach the contact ID to the group
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put("contactId", c.getInt());
|
||||
db.mergeGroupMetadata(g.getId(), metadataEncoder.encode(d));
|
||||
// Share any forums that are shared with all contacts
|
||||
List<Forum> shared = getForumsSharedWithAllContacts();
|
||||
storeMessage(g.getId(), shared, 0);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removingContact(ContactId c) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
db.removeGroup(getContactGroup(db.getContact(c)));
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessageValidatedEvent) {
|
||||
MessageValidatedEvent m = (MessageValidatedEvent) e;
|
||||
ClientId c = m.getClientId();
|
||||
if (m.isValid() && !m.isLocal() && c.equals(CLIENT_ID))
|
||||
remoteForumsUpdated(m.getMessage().getGroupId());
|
||||
}
|
||||
}
|
||||
|
||||
@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 void addForum(Forum f) throws DbException {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
db.addGroup(f.getGroup());
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeForum(Forum f) throws DbException {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// Update the list of forums shared with each contact
|
||||
for (Contact c : contactManager.getContacts()) {
|
||||
Group contactGroup = getContactGroup(c);
|
||||
removeFromList(contactGroup.getId(), f);
|
||||
}
|
||||
db.removeGroup(f.getGroup());
|
||||
} catch (IOException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Forum> getAvailableForums() throws DbException {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
// Get any forums we subscribe to
|
||||
Set<Group> subscribed = new HashSet<Group>(db.getGroups(
|
||||
forumManager.getClientId()));
|
||||
// Get all forums shared by contacts
|
||||
Set<Forum> available = new HashSet<Forum>();
|
||||
for (Contact c : contactManager.getContacts()) {
|
||||
Group g = getContactGroup(c);
|
||||
// Find the latest update version
|
||||
LatestUpdate latest = findLatest(g.getId(), false);
|
||||
if (latest != null) {
|
||||
// Retrieve and parse the latest update
|
||||
byte[] raw = db.getRawMessage(latest.messageId);
|
||||
for (Forum f : parseForumList(raw)) {
|
||||
if (!subscribed.contains(f.getGroup()))
|
||||
available.add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableSet(available);
|
||||
} catch (IOException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Contact> getSharedBy(GroupId g) throws DbException {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
List<Contact> subscribers = new ArrayList<Contact>();
|
||||
for (Contact c : contactManager.getContacts()) {
|
||||
Group contactGroup = getContactGroup(c);
|
||||
if (listContains(contactGroup.getId(), g, false))
|
||||
subscribers.add(c);
|
||||
}
|
||||
return Collections.unmodifiableList(subscribers);
|
||||
} catch (IOException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ContactId> getSharedWith(GroupId g) throws DbException {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
List<ContactId> shared = new ArrayList<ContactId>();
|
||||
for (Contact c : contactManager.getContacts()) {
|
||||
Group contactGroup = getContactGroup(c);
|
||||
if (listContains(contactGroup.getId(), g, true))
|
||||
shared.add(c.getId());
|
||||
}
|
||||
return Collections.unmodifiableList(shared);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSharedWith(GroupId g, Collection<ContactId> shared)
|
||||
throws DbException {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// Retrieve the forum
|
||||
Forum f = parseForum(db.getGroup(g));
|
||||
// Remove the forum from the list of forums shared with all contacts
|
||||
removeFromList(localGroup.getId(), f);
|
||||
// Update the list of forums shared with each contact
|
||||
shared = new HashSet<ContactId>(shared);
|
||||
for (Contact c : contactManager.getContacts()) {
|
||||
Group contactGroup = getContactGroup(c);
|
||||
if (shared.contains(c.getId())) {
|
||||
if (addToList(contactGroup.getId(), f)) {
|
||||
// If the contact is sharing the forum, make it visible
|
||||
if (listContains(contactGroup.getId(), g, false))
|
||||
db.setVisibleToContact(c.getId(), g, true);
|
||||
}
|
||||
} else {
|
||||
removeFromList(contactGroup.getId(), f);
|
||||
db.setVisibleToContact(c.getId(), g, false);
|
||||
}
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSharedWithAll(GroupId g) throws DbException {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// Retrieve the forum
|
||||
Forum f = parseForum(db.getGroup(g));
|
||||
// Add the forum to the list of forums shared with all contacts
|
||||
addToList(localGroup.getId(), f);
|
||||
// Add the forum to the list of forums shared with each contact
|
||||
for (Contact c : contactManager.getContacts()) {
|
||||
Group contactGroup = getContactGroup(c);
|
||||
if (addToList(contactGroup.getId(), f)) {
|
||||
// If the contact is sharing the forum, make it visible
|
||||
if (listContains(contactGroup.getId(), g, false))
|
||||
db.setVisibleToContact(getContactId(g), g, true);
|
||||
}
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private Group getContactGroup(Contact c) {
|
||||
return privateGroupFactory.createPrivateGroup(CLIENT_ID, c);
|
||||
}
|
||||
|
||||
// Locking: lock.writeLock
|
||||
private List<Forum> getForumsSharedWithAllContacts() throws DbException,
|
||||
FormatException {
|
||||
// Ensure the local group exists
|
||||
db.addGroup(localGroup);
|
||||
// Find the latest update in the local group
|
||||
LatestUpdate latest = findLatest(localGroup.getId(), true);
|
||||
if (latest == null) return Collections.emptyList();
|
||||
// Retrieve and parse the latest update
|
||||
return parseForumList(db.getRawMessage(latest.messageId));
|
||||
}
|
||||
|
||||
// Locking: lock.readLock
|
||||
private LatestUpdate findLatest(GroupId g, boolean local)
|
||||
throws DbException, FormatException {
|
||||
LatestUpdate latest = null;
|
||||
Map<MessageId, Metadata> metadata = db.getMessageMetadata(g);
|
||||
for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
|
||||
BdfDictionary d = metadataParser.parse(e.getValue());
|
||||
if (d.getBoolean("local") != local) continue;
|
||||
long version = d.getInteger("version");
|
||||
if (latest == null || version > latest.version)
|
||||
latest = new LatestUpdate(e.getKey(), version);
|
||||
}
|
||||
return latest;
|
||||
}
|
||||
|
||||
private List<Forum> parseForumList(byte[] raw) throws FormatException {
|
||||
List<Forum> forums = new ArrayList<Forum>();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
||||
BdfReader r = bdfReaderFactory.createReader(in);
|
||||
try {
|
||||
r.readListStart();
|
||||
r.skipInteger(); // Version
|
||||
r.readListStart();
|
||||
while (!r.hasListEnd()) {
|
||||
r.readListStart();
|
||||
String name = r.readString(MAX_FORUM_NAME_LENGTH);
|
||||
byte[] salt = r.readRaw(FORUM_SALT_LENGTH);
|
||||
r.readListEnd();
|
||||
forums.add(createForum(name, salt));
|
||||
}
|
||||
r.readListEnd();
|
||||
r.readListEnd();
|
||||
if (!r.eof()) throw new FormatException();
|
||||
return forums;
|
||||
} catch (FormatException e) {
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
// Shouldn't happen with ByteArrayInputStream
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Locking: lock.writeLock
|
||||
private void storeMessage(GroupId g, List<Forum> forums, long version)
|
||||
throws DbException, FormatException {
|
||||
byte[] body = encodeForumList(forums, version);
|
||||
long now = clock.currentTimeMillis();
|
||||
Message m = messageFactory.createMessage(g, now, body);
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put("version", version);
|
||||
d.put("local", true);
|
||||
db.addLocalMessage(m, CLIENT_ID, metadataEncoder.encode(d), true);
|
||||
}
|
||||
|
||||
private byte[] encodeForumList(List<Forum> forums, long version) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
BdfWriter w = bdfWriterFactory.createWriter(out);
|
||||
try {
|
||||
w.writeListStart();
|
||||
w.writeInteger(version);
|
||||
w.writeListStart();
|
||||
for (Forum f : forums) {
|
||||
w.writeListStart();
|
||||
w.writeString(f.getName());
|
||||
w.writeRaw(f.getSalt());
|
||||
w.writeListEnd();
|
||||
}
|
||||
w.writeListEnd();
|
||||
w.writeListEnd();
|
||||
} catch (IOException e) {
|
||||
// Shouldn't happen with ByteArrayOutputStream
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private void remoteForumsUpdated(final GroupId g) {
|
||||
dbExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
setForumVisibility(getContactId(g), getVisibleForums(g));
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Locking: lock.readLock
|
||||
private ContactId getContactId(GroupId contactGroupId) throws DbException,
|
||||
FormatException {
|
||||
Metadata meta = db.getGroupMetadata(contactGroupId);
|
||||
BdfDictionary d = metadataParser.parse(meta);
|
||||
int id = d.getInteger("contactId").intValue();
|
||||
return new ContactId(id);
|
||||
}
|
||||
|
||||
// Locking: lock.readLock
|
||||
private Set<GroupId> getVisibleForums(GroupId contactGroupId)
|
||||
throws DbException, FormatException {
|
||||
// Get the latest local and remote updates
|
||||
LatestUpdate local = findLatest(contactGroupId, true);
|
||||
LatestUpdate remote = findLatest(contactGroupId, false);
|
||||
// If there's no local and/or remote update, no forums are visible
|
||||
if (local == null || remote == null) return Collections.emptySet();
|
||||
// Intersect the sets of shared forums
|
||||
byte[] localRaw = db.getRawMessage(local.messageId);
|
||||
Set<Forum> shared = new HashSet<Forum>(parseForumList(localRaw));
|
||||
byte[] remoteRaw = db.getRawMessage(remote.messageId);
|
||||
shared.retainAll(parseForumList(remoteRaw));
|
||||
// Forums in the intersection should be visible
|
||||
Set<GroupId> visible = new HashSet<GroupId>(shared.size());
|
||||
for (Forum f : shared) visible.add(f.getId());
|
||||
return visible;
|
||||
}
|
||||
|
||||
// Locking: lock.writeLock
|
||||
private void setForumVisibility(ContactId c, Set<GroupId> visible)
|
||||
throws DbException {
|
||||
for (Group g : db.getGroups(forumManager.getClientId())) {
|
||||
boolean isVisible = db.isVisibleToContact(c, g.getId());
|
||||
boolean shouldBeVisible = visible.contains(g.getId());
|
||||
if (isVisible && !shouldBeVisible)
|
||||
db.setVisibleToContact(c, g.getId(), false);
|
||||
else if (!isVisible && shouldBeVisible)
|
||||
db.setVisibleToContact(c, g.getId(), true);
|
||||
}
|
||||
}
|
||||
|
||||
private Forum createForum(String name, byte[] salt) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
BdfWriter w = bdfWriterFactory.createWriter(out);
|
||||
try {
|
||||
w.writeListStart();
|
||||
w.writeString(name);
|
||||
w.writeRaw(salt);
|
||||
w.writeListEnd();
|
||||
} catch (IOException e) {
|
||||
// Shouldn't happen with ByteArrayOutputStream
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
Group g = groupFactory.createGroup(forumManager.getClientId(),
|
||||
out.toByteArray());
|
||||
return new Forum(g, name, salt);
|
||||
}
|
||||
|
||||
private Forum parseForum(Group g) throws FormatException {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(g.getDescriptor());
|
||||
BdfReader r = bdfReaderFactory.createReader(in);
|
||||
try {
|
||||
r.readListStart();
|
||||
String name = r.readString(MAX_FORUM_NAME_LENGTH);
|
||||
byte[] salt = r.readRaw(FORUM_SALT_LENGTH);
|
||||
r.readListEnd();
|
||||
if (!r.eof()) throw new FormatException();
|
||||
return new Forum(g, name, salt);
|
||||
} catch (FormatException e) {
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
// Shouldn't happen with ByteArrayInputStream
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Locking: lock.readLock
|
||||
private boolean listContains(GroupId g, GroupId forum, boolean local)
|
||||
throws DbException, FormatException {
|
||||
LatestUpdate latest = findLatest(g, local);
|
||||
if (latest == null) return false;
|
||||
List<Forum> list = parseForumList(db.getRawMessage(latest.messageId));
|
||||
for (Forum f : list) if (f.getId().equals(forum)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Locking: lock.writeLock
|
||||
private boolean addToList(GroupId g, Forum f) throws DbException,
|
||||
FormatException {
|
||||
LatestUpdate latest = findLatest(g, true);
|
||||
if (latest == null) {
|
||||
storeMessage(g, Collections.singletonList(f), 0);
|
||||
return true;
|
||||
}
|
||||
List<Forum> list = parseForumList(db.getRawMessage(latest.messageId));
|
||||
if (list.contains(f)) return false;
|
||||
list.add(f);
|
||||
storeMessage(g, list, latest.version + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Locking: lock.writeLock
|
||||
private void removeFromList(GroupId g, Forum f) throws DbException,
|
||||
FormatException {
|
||||
LatestUpdate latest = findLatest(g, true);
|
||||
if (latest == null) return;
|
||||
List<Forum> list = parseForumList(db.getRawMessage(latest.messageId));
|
||||
if (list.remove(f)) storeMessage(g, list, latest.version + 1);
|
||||
}
|
||||
|
||||
private static class LatestUpdate {
|
||||
|
||||
private final MessageId messageId;
|
||||
private final long version;
|
||||
|
||||
private LatestUpdate(MessageId messageId, long version) {
|
||||
this.messageId = messageId;
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import com.google.inject.Inject;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.contact.ContactManager;
|
||||
import org.briarproject.api.contact.ContactManager.AddContactHook;
|
||||
import org.briarproject.api.contact.ContactManager.RemoveContactHook;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
@@ -50,18 +51,19 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
|
||||
Logger.getLogger(MessagingManagerImpl.class.getName());
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final ContactManager contactManager;
|
||||
private final PrivateGroupFactory privateGroupFactory;
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final MetadataEncoder metadataEncoder;
|
||||
private final MetadataParser metadataParser;
|
||||
|
||||
@Inject
|
||||
MessagingManagerImpl(DatabaseComponent db,
|
||||
MessagingManagerImpl(DatabaseComponent db, ContactManager contactManager,
|
||||
PrivateGroupFactory privateGroupFactory,
|
||||
BdfReaderFactory bdfReaderFactory,
|
||||
MetadataEncoder metadataEncoder,
|
||||
BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
|
||||
MetadataParser metadataParser) {
|
||||
this.db = db;
|
||||
this.contactManager = contactManager;
|
||||
this.privateGroupFactory = privateGroupFactory;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.metadataEncoder = metadataEncoder;
|
||||
@@ -71,8 +73,8 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
|
||||
@Override
|
||||
public void addingContact(ContactId c) {
|
||||
try {
|
||||
// Create the conversation group
|
||||
Group g = getConversationGroup(db.getContact(c));
|
||||
// Create a group to share with the contact
|
||||
Group g = getContactGroup(db.getContact(c));
|
||||
// Store the group and share it with the contact
|
||||
db.addGroup(g);
|
||||
db.setVisibility(g.getId(), Collections.singletonList(c));
|
||||
@@ -87,14 +89,14 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
|
||||
}
|
||||
}
|
||||
|
||||
private Group getConversationGroup(Contact c) {
|
||||
private Group getContactGroup(Contact c) {
|
||||
return privateGroupFactory.createPrivateGroup(CLIENT_ID, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removingContact(ContactId c) {
|
||||
try {
|
||||
db.removeGroup(getConversationGroup(db.getContact(c)));
|
||||
db.removeGroup(getContactGroup(db.getContact(c)));
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
@@ -135,7 +137,7 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
|
||||
|
||||
@Override
|
||||
public GroupId getConversationId(ContactId c) throws DbException {
|
||||
return getConversationGroup(db.getContact(c)).getId();
|
||||
return getContactGroup(contactManager.getContact(c)).getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -172,15 +174,16 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
|
||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
||||
BdfReader r = bdfReaderFactory.createReader(in);
|
||||
try {
|
||||
// Extract the private message body
|
||||
r.readListStart();
|
||||
if (r.hasRaw()) r.skipRaw(); // Parent ID
|
||||
else r.skipNull(); // No parent
|
||||
r.skipString(); // Content type
|
||||
return r.readRaw(MAX_PRIVATE_MESSAGE_BODY_LENGTH);
|
||||
byte[] messageBody = r.readRaw(MAX_PRIVATE_MESSAGE_BODY_LENGTH);
|
||||
r.readListEnd();
|
||||
if (!r.eof()) throw new FormatException();
|
||||
return messageBody;
|
||||
} catch (FormatException e) {
|
||||
// Not a valid private message
|
||||
throw new IllegalArgumentException();
|
||||
throw new DbException(e);
|
||||
} catch (IOException e) {
|
||||
// Shouldn't happen with ByteArrayInputStream
|
||||
throw new RuntimeException(e);
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.briarproject.api.data.BdfReader;
|
||||
import org.briarproject.api.data.BdfReaderFactory;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.MessageValidator;
|
||||
@@ -16,8 +17,6 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
@@ -32,7 +31,6 @@ class PrivateMessageValidator implements MessageValidator {
|
||||
private final MetadataEncoder metadataEncoder;
|
||||
private final Clock clock;
|
||||
|
||||
@Inject
|
||||
PrivateMessageValidator(BdfReaderFactory bdfReaderFactory,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
@@ -41,7 +39,7 @@ class PrivateMessageValidator implements MessageValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metadata validateMessage(Message m) {
|
||||
public Metadata validateMessage(Message m, Group g) {
|
||||
// Reject the message if it's too far in the future
|
||||
long now = clock.currentTimeMillis();
|
||||
if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
|
||||
@@ -55,7 +53,6 @@ class PrivateMessageValidator implements MessageValidator {
|
||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
||||
BdfReader r = bdfReaderFactory.createReader(in);
|
||||
MessageId parent = null;
|
||||
String contentType;
|
||||
r.readListStart();
|
||||
// Read the parent ID, if any
|
||||
if (r.hasRaw()) {
|
||||
@@ -66,7 +63,7 @@ class PrivateMessageValidator implements MessageValidator {
|
||||
r.readNull();
|
||||
}
|
||||
// Read the content type
|
||||
contentType = r.readString(MAX_CONTENT_TYPE_LENGTH);
|
||||
String contentType = r.readString(MAX_CONTENT_TYPE_LENGTH);
|
||||
// Read the private message body
|
||||
r.readRaw(MAX_PRIVATE_MESSAGE_BODY_LENGTH);
|
||||
r.readListEnd();
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.TransportId;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.contact.ContactManager;
|
||||
import org.briarproject.api.contact.ContactManager.AddContactHook;
|
||||
import org.briarproject.api.contact.ContactManager.RemoveContactHook;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
@@ -60,6 +61,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
Logger.getLogger(TransportPropertyManagerImpl.class.getName());
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final ContactManager contactManager;
|
||||
private final PrivateGroupFactory privateGroupFactory;
|
||||
private final MessageFactory messageFactory;
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
@@ -74,11 +76,13 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
|
||||
@Inject
|
||||
TransportPropertyManagerImpl(DatabaseComponent db,
|
||||
GroupFactory groupFactory, PrivateGroupFactory privateGroupFactory,
|
||||
ContactManager contactManager, GroupFactory groupFactory,
|
||||
PrivateGroupFactory privateGroupFactory,
|
||||
MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder,
|
||||
MetadataParser metadataParser, Clock clock) {
|
||||
this.db = db;
|
||||
this.contactManager = contactManager;
|
||||
this.privateGroupFactory = privateGroupFactory;
|
||||
this.messageFactory = messageFactory;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
@@ -109,7 +113,145 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removingContact(ContactId c) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
db.removeGroup(getContactGroup(db.getContact(c)));
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRemoteProperties(ContactId c, DeviceId dev,
|
||||
Map<TransportId, TransportProperties> props) throws DbException {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
Group g = getContactGroup(db.getContact(c));
|
||||
for (Entry<TransportId, TransportProperties> e : props.entrySet()) {
|
||||
storeMessage(g.getId(), dev, e.getKey(), e.getValue(), 0, false,
|
||||
false);
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TransportId, TransportProperties> getLocalProperties()
|
||||
throws DbException {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
// Find the latest local update for each transport
|
||||
Map<TransportId, LatestUpdate> latest =
|
||||
findLatest(localGroup.getId(), true);
|
||||
// Retrieve and parse the latest local properties
|
||||
Map<TransportId, TransportProperties> local =
|
||||
new HashMap<TransportId, TransportProperties>();
|
||||
for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) {
|
||||
byte[] raw = db.getRawMessage(e.getValue().messageId);
|
||||
local.put(e.getKey(), parseProperties(raw));
|
||||
}
|
||||
return Collections.unmodifiableMap(local);
|
||||
} catch (NoSuchGroupException e) {
|
||||
// Local group doesn't exist - there are no local properties
|
||||
return Collections.emptyMap();
|
||||
} catch (IOException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportProperties getLocalProperties(TransportId t)
|
||||
throws DbException {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
// Find the latest local update
|
||||
LatestUpdate latest = findLatest(localGroup.getId(), t, true);
|
||||
if (latest == null) return null;
|
||||
// Retrieve and parse the latest local properties
|
||||
return parseProperties(db.getRawMessage(latest.messageId));
|
||||
} catch (NoSuchGroupException e) {
|
||||
// Local group doesn't exist - there are no local properties
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<ContactId, TransportProperties> getRemoteProperties(
|
||||
TransportId t) throws DbException {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
new HashMap<ContactId, TransportProperties>();
|
||||
for (Contact c : contactManager.getContacts()) {
|
||||
Group g = getContactGroup(c);
|
||||
// Find the latest remote update
|
||||
LatestUpdate latest = findLatest(g.getId(), t, false);
|
||||
if (latest != null) {
|
||||
// Retrieve and parse the latest remote properties
|
||||
byte[] raw = db.getRawMessage(latest.messageId);
|
||||
remote.put(c.getId(), parseProperties(raw));
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableMap(remote);
|
||||
} catch (IOException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeLocalProperties(TransportId t, TransportProperties p)
|
||||
throws DbException {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// Create the local group if necessary
|
||||
db.addGroup(localGroup);
|
||||
// Merge the new properties with any existing properties
|
||||
TransportProperties merged;
|
||||
LatestUpdate latest = findLatest(localGroup.getId(), t, true);
|
||||
if (latest == null) {
|
||||
merged = p;
|
||||
} else {
|
||||
byte[] raw = db.getRawMessage(latest.messageId);
|
||||
TransportProperties old = parseProperties(raw);
|
||||
merged = new TransportProperties(old);
|
||||
merged.putAll(p);
|
||||
if (merged.equals(old)) return; // Unchanged
|
||||
}
|
||||
// Store the merged properties in the local group
|
||||
DeviceId dev = db.getDeviceId();
|
||||
long version = latest == null ? 1 : latest.version + 1;
|
||||
storeMessage(localGroup.getId(), dev, t, merged, version, true,
|
||||
false);
|
||||
// Store the merged properties in each contact's group
|
||||
for (Contact c : contactManager.getContacts()) {
|
||||
Group g = getContactGroup(c);
|
||||
latest = findLatest(g.getId(), t, true);
|
||||
version = latest == null ? 1 : latest.version + 1;
|
||||
storeMessage(g.getId(), dev, t, merged, version, true, true);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
@@ -119,6 +261,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
return privateGroupFactory.createPrivateGroup(CLIENT_ID, c);
|
||||
}
|
||||
|
||||
// Locking: lock.writeLock
|
||||
private void storeMessage(GroupId g, DeviceId dev, TransportId t,
|
||||
TransportProperties p, long version, boolean local, boolean shared)
|
||||
throws DbException, FormatException {
|
||||
@@ -150,80 +293,43 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removingContact(ContactId c) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
db.removeGroup(getContactGroup(db.getContact(c)));
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRemoteProperties(ContactId c, DeviceId dev,
|
||||
Map<TransportId, TransportProperties> props) throws DbException {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
Group g = getContactGroup(db.getContact(c));
|
||||
for (Entry<TransportId, TransportProperties> e : props.entrySet()) {
|
||||
storeMessage(g.getId(), dev, e.getKey(), e.getValue(), 0, false,
|
||||
false);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TransportId, TransportProperties> getLocalProperties()
|
||||
throws DbException {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
// Find the latest local version for each transport
|
||||
Map<TransportId, LatestUpdate> latest =
|
||||
findLatest(localGroup.getId(), true);
|
||||
// Retrieve and decode the latest local properties
|
||||
Map<TransportId, TransportProperties> local =
|
||||
new HashMap<TransportId, TransportProperties>();
|
||||
for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) {
|
||||
byte[] raw = db.getRawMessage(e.getValue().messageId);
|
||||
local.put(e.getKey(), decodeProperties(raw));
|
||||
}
|
||||
return Collections.unmodifiableMap(local);
|
||||
} catch (NoSuchGroupException e) {
|
||||
// Local group doesn't exist - there are no local properties
|
||||
return Collections.emptyMap();
|
||||
} catch (IOException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// Locking: lock.readLock
|
||||
private Map<TransportId, LatestUpdate> findLatest(GroupId g, boolean local)
|
||||
throws DbException, FormatException {
|
||||
// TODO: Use metadata queries
|
||||
Map<TransportId, LatestUpdate> latestUpdates =
|
||||
new HashMap<TransportId, LatestUpdate>();
|
||||
Map<MessageId, Metadata> metadata = db.getMessageMetadata(g);
|
||||
for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
|
||||
BdfDictionary d = metadataParser.parse(e.getValue());
|
||||
if (d.getBoolean("local") != local) continue;
|
||||
TransportId t = new TransportId(d.getString("transportId"));
|
||||
long version = d.getInteger("version");
|
||||
LatestUpdate latest = latestUpdates.get(t);
|
||||
if (latest == null || version > latest.version)
|
||||
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
||||
if (d.getBoolean("local") == local) {
|
||||
TransportId t = new TransportId(d.getString("transportId"));
|
||||
long version = d.getInteger("version");
|
||||
LatestUpdate latest = latestUpdates.get(t);
|
||||
if (latest == null || version > latest.version)
|
||||
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
||||
}
|
||||
}
|
||||
return latestUpdates;
|
||||
}
|
||||
|
||||
private TransportProperties decodeProperties(byte[] raw)
|
||||
// Locking: lock.readLock
|
||||
private LatestUpdate findLatest(GroupId g, TransportId t, boolean local)
|
||||
throws DbException, FormatException {
|
||||
LatestUpdate latest = null;
|
||||
Map<MessageId, Metadata> metadata = db.getMessageMetadata(g);
|
||||
for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
|
||||
BdfDictionary d = metadataParser.parse(e.getValue());
|
||||
if (d.getString("transportId").equals(t.getString())
|
||||
&& d.getBoolean("local") == local) {
|
||||
long version = d.getInteger("version");
|
||||
if (latest == null || version > latest.version)
|
||||
latest = new LatestUpdate(e.getKey(), version);
|
||||
}
|
||||
}
|
||||
return latest;
|
||||
}
|
||||
|
||||
private TransportProperties parseProperties(byte[] raw)
|
||||
throws IOException {
|
||||
TransportProperties p = new TransportProperties();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
||||
@@ -239,92 +345,12 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
String value = r.readString(MAX_PROPERTY_LENGTH);
|
||||
p.put(key, value);
|
||||
}
|
||||
r.readDictionaryEnd();
|
||||
r.readListEnd();
|
||||
if (!r.eof()) throw new FormatException();
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportProperties getLocalProperties(TransportId t)
|
||||
throws DbException {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
// Find the latest local version
|
||||
LatestUpdate latest = findLatest(localGroup.getId(), true).get(t);
|
||||
if (latest == null) return null;
|
||||
// Retrieve and decode the latest local properties
|
||||
return decodeProperties(db.getRawMessage(latest.messageId));
|
||||
} catch (NoSuchGroupException e) {
|
||||
// Local group doesn't exist - there are no local properties
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<ContactId, TransportProperties> getRemoteProperties(
|
||||
TransportId t) throws DbException {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
new HashMap<ContactId, TransportProperties>();
|
||||
for (Contact c : db.getContacts()) {
|
||||
Group g = getContactGroup(c);
|
||||
// Find the latest remote version
|
||||
LatestUpdate latest = findLatest(g.getId(), false).get(t);
|
||||
if (latest != null) {
|
||||
// Retrieve and decode the latest remote properties
|
||||
byte[] raw = db.getRawMessage(latest.messageId);
|
||||
remote.put(c.getId(), decodeProperties(raw));
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableMap(remote);
|
||||
} catch (IOException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeLocalProperties(TransportId t, TransportProperties p)
|
||||
throws DbException {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// Create the local group if necessary
|
||||
db.addGroup(localGroup);
|
||||
// Merge the new properties with any existing properties
|
||||
TransportProperties merged;
|
||||
LatestUpdate latest = findLatest(localGroup.getId(), true).get(t);
|
||||
if (latest == null) {
|
||||
merged = p;
|
||||
} else {
|
||||
byte[] raw = db.getRawMessage(latest.messageId);
|
||||
TransportProperties old = decodeProperties(raw);
|
||||
merged = new TransportProperties(old);
|
||||
merged.putAll(p);
|
||||
if (merged.equals(old)) return; // Unchanged
|
||||
}
|
||||
// Store the merged properties in the local group
|
||||
DeviceId dev = db.getDeviceId();
|
||||
long version = latest == null ? 1 : latest.version + 1;
|
||||
storeMessage(localGroup.getId(), dev, t, merged, version, true,
|
||||
false);
|
||||
// Store the merged properties in each contact's group
|
||||
for (Contact c : db.getContacts()) {
|
||||
Group g = getContactGroup(c);
|
||||
latest = findLatest(g.getId(), true).get(t);
|
||||
version = latest == null ? 1 : latest.version + 1;
|
||||
storeMessage(g.getId(), dev, t, merged, version, true, true);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new DbException(e);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private static class LatestUpdate {
|
||||
|
||||
private final MessageId messageId;
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.briarproject.api.data.BdfReader;
|
||||
import org.briarproject.api.data.BdfReaderFactory;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageValidator;
|
||||
import org.briarproject.api.system.Clock;
|
||||
@@ -15,8 +16,6 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.api.TransportId.MAX_TRANSPORT_ID_LENGTH;
|
||||
import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
||||
import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||
@@ -32,7 +31,6 @@ class TransportPropertyValidator implements MessageValidator {
|
||||
private final MetadataEncoder metadataEncoder;
|
||||
private final Clock clock;
|
||||
|
||||
@Inject
|
||||
TransportPropertyValidator(BdfReaderFactory bdfReaderFactory,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
@@ -41,7 +39,7 @@ class TransportPropertyValidator implements MessageValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metadata validateMessage(Message m) {
|
||||
public Metadata validateMessage(Message m, Group g) {
|
||||
// Reject the message if it's too far in the future
|
||||
long now = clock.currentTimeMillis();
|
||||
if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
|
||||
|
||||
@@ -13,7 +13,6 @@ import org.briarproject.api.event.MessageRequestedEvent;
|
||||
import org.briarproject.api.event.MessageSharedEvent;
|
||||
import org.briarproject.api.event.MessageToAckEvent;
|
||||
import org.briarproject.api.event.MessageToRequestEvent;
|
||||
import org.briarproject.api.event.MessageValidatedEvent;
|
||||
import org.briarproject.api.event.ShutdownEvent;
|
||||
import org.briarproject.api.event.TransportRemovedEvent;
|
||||
import org.briarproject.api.sync.Ack;
|
||||
@@ -151,9 +150,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
if (c.getContactId().equals(contactId)) interrupt();
|
||||
} else if (e instanceof MessageSharedEvent) {
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
} else if (e instanceof MessageValidatedEvent) {
|
||||
if (((MessageValidatedEvent) e).isValid())
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
||||
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
||||
if (g.getAffectedContacts().contains(contactId))
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.event.MessageAddedEvent;
|
||||
import org.briarproject.api.lifecycle.Service;
|
||||
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;
|
||||
@@ -84,7 +85,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
for (MessageId id : db.getMessagesToValidate(c)) {
|
||||
try {
|
||||
Message m = parseMessage(id, db.getRawMessage(id));
|
||||
validateMessage(m, c);
|
||||
Group g = db.getGroup(m.getGroupId());
|
||||
validateMessage(m, g);
|
||||
} catch (NoSuchMessageException e) {
|
||||
LOG.info("Message removed before validation");
|
||||
}
|
||||
@@ -106,15 +108,15 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
return new Message(id, new GroupId(groupId), timestamp, raw);
|
||||
}
|
||||
|
||||
private void validateMessage(final Message m, final ClientId c) {
|
||||
private void validateMessage(final Message m, final Group g) {
|
||||
cryptoExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
MessageValidator v = validators.get(c);
|
||||
MessageValidator v = validators.get(g.getClientId());
|
||||
if (v == null) {
|
||||
LOG.warning("No validator");
|
||||
} else {
|
||||
Metadata meta = v.validateMessage(m);
|
||||
storeValidationResult(m, c, meta);
|
||||
Metadata meta = v.validateMessage(m, g);
|
||||
storeValidationResult(m, g.getClientId(), meta);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -132,6 +134,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
hook.validatingMessage(m, c, meta);
|
||||
db.mergeMessageMetadata(m.getId(), meta);
|
||||
db.setMessageValid(m, c, true);
|
||||
db.setMessageShared(m, true);
|
||||
}
|
||||
} catch (NoSuchMessageException e) {
|
||||
LOG.info("Message removed during validation");
|
||||
@@ -146,18 +149,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessageAddedEvent) {
|
||||
MessageAddedEvent m = (MessageAddedEvent) e;
|
||||
// Validate the message if it wasn't created locally
|
||||
if (m.getContactId() != null) loadClientId(m.getMessage());
|
||||
MessageAddedEvent m = (MessageAddedEvent) e;
|
||||
if (m.getContactId() != null) loadGroup(m.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void loadClientId(final Message m) {
|
||||
private void loadGroup(final Message m) {
|
||||
dbExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
ClientId c = db.getGroup(m.getGroupId()).getClientId();
|
||||
validateMessage(m, c);
|
||||
validateMessage(m, db.getGroup(m.getGroupId()));
|
||||
} catch (NoSuchGroupException e) {
|
||||
LOG.info("Group removed before validation");
|
||||
} catch (DbException e) {
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.briarproject.plugins;
|
||||
|
||||
import com.google.inject.Provides;
|
||||
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.api.lifecycle.ShutdownManager;
|
||||
import org.briarproject.api.plugins.duplex.DuplexPluginConfig;
|
||||
@@ -16,6 +15,7 @@ import org.briarproject.plugins.modem.ModemPluginFactory;
|
||||
import org.briarproject.plugins.tcp.LanTcpPluginFactory;
|
||||
import org.briarproject.plugins.tcp.WanTcpPluginFactory;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -39,10 +39,10 @@ public class DesktopPluginsModule extends PluginsModule {
|
||||
|
||||
@Provides
|
||||
DuplexPluginConfig getDuplexPluginConfig(@IoExecutor Executor ioExecutor,
|
||||
CryptoComponent crypto, ReliabilityLayerFactory reliabilityFactory,
|
||||
SecureRandom random, ReliabilityLayerFactory reliabilityFactory,
|
||||
ShutdownManager shutdownManager) {
|
||||
DuplexPluginFactory bluetooth = new BluetoothPluginFactory(
|
||||
ioExecutor, crypto.getSecureRandom());
|
||||
DuplexPluginFactory bluetooth = new BluetoothPluginFactory(ioExecutor,
|
||||
random);
|
||||
DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor,
|
||||
reliabilityFactory);
|
||||
DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor);
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.briarproject.api.event.GroupRemovedEvent;
|
||||
import org.briarproject.api.event.GroupVisibilityUpdatedEvent;
|
||||
import org.briarproject.api.event.MessageAddedEvent;
|
||||
import org.briarproject.api.event.MessageRequestedEvent;
|
||||
import org.briarproject.api.event.MessageSharedEvent;
|
||||
import org.briarproject.api.event.MessageToAckEvent;
|
||||
import org.briarproject.api.event.MessageToRequestEvent;
|
||||
import org.briarproject.api.event.MessageValidatedEvent;
|
||||
@@ -246,6 +247,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
// The message was added, so the listeners should be called
|
||||
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
|
||||
oneOf(eventBus).broadcast(with(any(MessageValidatedEvent.class)));
|
||||
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
@@ -265,11 +267,11 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the contact is in the DB (which it's not)
|
||||
exactly(15).of(database).startTransaction();
|
||||
exactly(17).of(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
exactly(15).of(database).containsContact(txn, contactId);
|
||||
exactly(17).of(database).containsContact(txn, contactId);
|
||||
will(returnValue(false));
|
||||
exactly(15).of(database).abortTransaction(txn);
|
||||
exactly(17).of(database).abortTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
@@ -337,6 +339,13 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.isVisibleToContact(contactId, groupId);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
Ack a = new Ack(Collections.singletonList(messageId));
|
||||
db.receiveAck(contactId, a);
|
||||
@@ -382,6 +391,13 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.setVisibleToContact(contactId, groupId, true);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@@ -438,13 +454,14 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the group is in the DB (which it's not)
|
||||
exactly(7).of(database).startTransaction();
|
||||
exactly(9).of(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
exactly(7).of(database).containsGroup(txn, groupId);
|
||||
exactly(9).of(database).containsGroup(txn, groupId);
|
||||
will(returnValue(false));
|
||||
exactly(7).of(database).abortTransaction(txn);
|
||||
// This is needed for getMessageStatus() to proceed
|
||||
exactly(1).of(database).containsContact(txn, contactId);
|
||||
exactly(9).of(database).abortTransaction(txn);
|
||||
// This is needed for getMessageStatus(), isVisibleToContact(), and
|
||||
// setVisibleToContact() to proceed
|
||||
exactly(3).of(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
@@ -478,6 +495,13 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.isVisibleToContact(contactId, groupId);
|
||||
fail();
|
||||
} catch (NoSuchGroupException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.mergeGroupMetadata(groupId, metadata);
|
||||
fail();
|
||||
@@ -499,6 +523,13 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.setVisibleToContact(contactId, groupId, true);
|
||||
fail();
|
||||
} catch (NoSuchGroupException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@@ -847,7 +878,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
will(returnValue(false));
|
||||
oneOf(database).containsVisibleGroup(txn, contactId, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).addMessage(txn, message, UNKNOWN, true);
|
||||
oneOf(database).addMessage(txn, message, UNKNOWN, false);
|
||||
oneOf(database).getVisibility(txn, groupId);
|
||||
will(returnValue(Collections.singletonList(contactId)));
|
||||
oneOf(database).getContactIds(txn);
|
||||
@@ -1019,7 +1050,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
oneOf(database).getContactIds(txn);
|
||||
will(returnValue(both));
|
||||
oneOf(database).removeVisibility(txn, contactId1, groupId);
|
||||
oneOf(database).setVisibleToAll(txn, groupId, false);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(eventBus).broadcast(with(any(
|
||||
GroupVisibilityUpdatedEvent.class)));
|
||||
@@ -1051,7 +1081,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
will(returnValue(both));
|
||||
oneOf(database).getContactIds(txn);
|
||||
will(returnValue(both));
|
||||
oneOf(database).setVisibleToAll(txn, groupId, false);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
@@ -1062,55 +1091,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSettingVisibleToAllAffectsCurrentContacts()
|
||||
throws Exception {
|
||||
final ContactId contactId1 = new ContactId(123);
|
||||
final Collection<ContactId> both = Arrays.asList(contactId, contactId1);
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// setVisibility()
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getVisibility(txn, groupId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(database).getContactIds(txn);
|
||||
will(returnValue(both));
|
||||
oneOf(database).addVisibility(txn, contactId, groupId);
|
||||
oneOf(database).setVisibleToAll(txn, groupId, false);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(eventBus).broadcast(with(any(
|
||||
GroupVisibilityUpdatedEvent.class)));
|
||||
// setVisibleToAll()
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).setVisibleToAll(txn, groupId, true);
|
||||
oneOf(database).getVisibility(txn, groupId);
|
||||
will(returnValue(Collections.singletonList(contactId)));
|
||||
oneOf(database).getContactIds(txn);
|
||||
will(returnValue(both));
|
||||
oneOf(database).addVisibility(txn, contactId1, groupId);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(eventBus).broadcast(with(any(
|
||||
GroupVisibilityUpdatedEvent.class)));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
db.setVisibility(groupId, Collections.singletonList(contactId));
|
||||
db.setVisibleToAll(groupId, true);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportKeys() throws Exception {
|
||||
final TransportKeys keys = createTransportKeys();
|
||||
|
||||
@@ -205,7 +205,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, subscribe to a group and store an unvalidated message
|
||||
// Add a contact, a group and an unvalidated message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addGroup(txn, group);
|
||||
@@ -243,7 +243,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, subscribe to a group and store an unshared message
|
||||
// Add a contact, a group and an unshared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addGroup(txn, group);
|
||||
@@ -329,7 +329,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
|
||||
// Making the subscription invisible should make the message unsendable
|
||||
// Making the group invisible should make the message unsendable
|
||||
db.removeVisibility(txn, contactId, groupId);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||
assertTrue(ids.isEmpty());
|
||||
@@ -1017,6 +1017,31 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroupsVisibleToContacts() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact and a group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addGroup(txn, group);
|
||||
|
||||
// The group should not be visible to the contact
|
||||
assertFalse(db.containsVisibleGroup(txn, contactId, groupId));
|
||||
|
||||
// Make the group visible to the contact
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
assertTrue(db.containsVisibleGroup(txn, contactId, groupId));
|
||||
|
||||
// Make the group invisible to the contact
|
||||
db.removeVisibility(txn, contactId, groupId);
|
||||
assertFalse(db.containsVisibleGroup(txn, contactId, groupId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferentLocalPseudonymsCanHaveTheSameContact()
|
||||
throws Exception {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.briarproject.forum;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class ForumListValidatorTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testUnitTestsExist() {
|
||||
fail(); // FIXME: Write tests
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.briarproject.forum;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class ForumSharingManagerImplTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testUnitTestsExist() {
|
||||
fail(); // FIXME: Write tests
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user