mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Merge branch 'remove-subscription-updates' into 'master'
Move subscription updates to the client layer This is part of #112. I created a separate client for sharing lists of available forums, as that turned out to be cleaner than using the forum client to manage two kinds of group. The same will presumably be true for the blog and group messaging clients when we come to implement them. The UX for sharing forums is due to change - instead of sharing a list of available forums with no explanation, we'll explicitly invite contacts to join forums. When that happens, the ForumSharingManager will change but the ForumManager should remain pretty much the same. When hooks for message status changes have been implemented, the ForumSharingManager will use those instead of listening for MessageValidatedEvents. When client layer transactions have been implemented, the ForumManager, ForumSharingManager and TransportPropertyManager will use them instead of locks to ensure atomicity and isolation. See merge request !94
This commit is contained in:
@@ -13,16 +13,18 @@ import org.briarproject.android.util.ListLoadingProgressBar;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.NoSuchSubscriptionException;
|
||||
import org.briarproject.api.db.NoSuchGroupException;
|
||||
import org.briarproject.api.event.ContactRemovedEvent;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.event.RemoteSubscriptionsUpdatedEvent;
|
||||
import org.briarproject.api.event.SubscriptionAddedEvent;
|
||||
import org.briarproject.api.event.SubscriptionRemovedEvent;
|
||||
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;
|
||||
@@ -46,6 +48,7 @@ implements EventListener, OnItemClickListener {
|
||||
|
||||
// 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
|
||||
@@ -77,13 +80,12 @@ 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 (NoSuchSubscriptionException e) {
|
||||
} catch (NoSuchGroupException e) {
|
||||
// Continue
|
||||
}
|
||||
}
|
||||
@@ -123,35 +125,48 @@ implements EventListener, OnItemClickListener {
|
||||
}
|
||||
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof RemoteSubscriptionsUpdatedEvent) {
|
||||
LOG.info("Remote subscriptions changed, reloading");
|
||||
loadForums();
|
||||
} else if (e instanceof SubscriptionAddedEvent) {
|
||||
LOG.info("Subscription added, reloading");
|
||||
loadForums();
|
||||
} else if (e instanceof SubscriptionRemovedEvent) {
|
||||
LOG.info("Subscription removed, reloading");
|
||||
if (e instanceof ContactRemovedEvent) {
|
||||
LOG.info("Contact removed, reloading");
|
||||
loadForums();
|
||||
} else 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");
|
||||
loadForums();
|
||||
}
|
||||
} else 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
@@ -19,13 +19,13 @@ import org.briarproject.android.util.HorizontalBorder;
|
||||
import org.briarproject.android.util.ListLoadingProgressBar;
|
||||
import org.briarproject.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.NoSuchGroupException;
|
||||
import org.briarproject.api.db.NoSuchMessageException;
|
||||
import org.briarproject.api.db.NoSuchSubscriptionException;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.event.GroupRemovedEvent;
|
||||
import org.briarproject.api.event.MessageValidatedEvent;
|
||||
import org.briarproject.api.event.SubscriptionRemovedEvent;
|
||||
import org.briarproject.api.forum.Forum;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
import org.briarproject.api.forum.ForumPostHeader;
|
||||
@@ -159,7 +159,7 @@ public class ForumActivity extends BriarActivity implements EventListener,
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading forum " + duration + " ms");
|
||||
displayForumName();
|
||||
} catch (NoSuchSubscriptionException e) {
|
||||
} catch (NoSuchGroupException e) {
|
||||
finishOnUiThread();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
@@ -188,7 +188,7 @@ public class ForumActivity extends BriarActivity implements EventListener,
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Load took " + duration + " ms");
|
||||
displayHeaders(headers);
|
||||
} catch (NoSuchSubscriptionException e) {
|
||||
} catch (NoSuchGroupException e) {
|
||||
finishOnUiThread();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
@@ -319,10 +319,10 @@ public class ForumActivity extends BriarActivity implements EventListener,
|
||||
LOG.info("Message added, reloading");
|
||||
loadHeaders();
|
||||
}
|
||||
} else if (e instanceof SubscriptionRemovedEvent) {
|
||||
SubscriptionRemovedEvent s = (SubscriptionRemovedEvent) e;
|
||||
} else if (e instanceof GroupRemovedEvent) {
|
||||
GroupRemovedEvent s = (GroupRemovedEvent) e;
|
||||
if (s.getGroup().getId().equals(groupId)) {
|
||||
LOG.info("Subscription removed");
|
||||
LOG.info("Forum removed");
|
||||
finishOnUiThread();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,17 +24,17 @@ import org.briarproject.android.util.HorizontalBorder;
|
||||
import org.briarproject.android.util.LayoutUtils;
|
||||
import org.briarproject.android.util.ListLoadingProgressBar;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.NoSuchSubscriptionException;
|
||||
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;
|
||||
import org.briarproject.api.event.MessageValidatedEvent;
|
||||
import org.briarproject.api.event.RemoteSubscriptionsUpdatedEvent;
|
||||
import org.briarproject.api.event.SubscriptionAddedEvent;
|
||||
import org.briarproject.api.event.SubscriptionRemovedEvent;
|
||||
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.Group;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -83,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
|
||||
@@ -164,19 +164,23 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
boolean displayedHeaders = false;
|
||||
for (Forum f : forumManager.getForums()) {
|
||||
try {
|
||||
Collection<ForumPostHeader> headers =
|
||||
forumManager.getPostHeaders(f.getId());
|
||||
displayHeaders(f, headers);
|
||||
} catch (NoSuchSubscriptionException e) {
|
||||
displayedHeaders = true;
|
||||
} catch (NoSuchGroupException e) {
|
||||
// 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");
|
||||
if (!displayedHeaders) displayEmpty();
|
||||
displayAvailable(available);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
@@ -215,11 +219,18 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
});
|
||||
}
|
||||
|
||||
private void displayEmpty() {
|
||||
listener.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
empty.setVisibility(VISIBLE);
|
||||
loading.setVisibility(GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayAvailable(final int availableCount) {
|
||||
listener.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (adapter.isEmpty()) empty.setVisibility(VISIBLE);
|
||||
loading.setVisibility(GONE);
|
||||
if (availableCount == 0) {
|
||||
available.setVisibility(GONE);
|
||||
} else {
|
||||
@@ -254,25 +265,34 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
}
|
||||
|
||||
public void eventOccurred(Event e) {
|
||||
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());
|
||||
}
|
||||
} else if (e instanceof RemoteSubscriptionsUpdatedEvent) {
|
||||
LOG.info("Remote subscriptions changed, reloading");
|
||||
if (e instanceof ContactRemovedEvent) {
|
||||
LOG.info("Contact removed, reloading");
|
||||
loadAvailable();
|
||||
} else if (e instanceof SubscriptionAddedEvent) {
|
||||
LOG.info("Group added, reloading");
|
||||
loadHeaders();
|
||||
} else if (e instanceof SubscriptionRemovedEvent) {
|
||||
Group g = ((SubscriptionRemovedEvent) e).getGroup();
|
||||
if (g.getClientId().equals(forumManager.getClientId())) {
|
||||
LOG.info("Group removed, reloading");
|
||||
} else if (e instanceof GroupAddedEvent) {
|
||||
GroupAddedEvent g = (GroupAddedEvent) e;
|
||||
if (g.getGroup().getClientId().equals(forumManager.getClientId())) {
|
||||
LOG.info("Forum added, reloading");
|
||||
loadHeaders();
|
||||
}
|
||||
} else if (e instanceof GroupRemovedEvent) {
|
||||
GroupRemovedEvent g = (GroupRemovedEvent) e;
|
||||
if (g.getGroup().getClientId().equals(forumManager.getClientId())) {
|
||||
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 (!m.isLocal()
|
||||
&& c.equals(forumSharingManager.getClientId())) {
|
||||
LOG.info("Available forums updated, reloading");
|
||||
loadAvailable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +308,7 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Partial load took " + duration + " ms");
|
||||
displayHeaders(f, headers);
|
||||
} catch (NoSuchSubscriptionException e) {
|
||||
} catch (NoSuchGroupException e) {
|
||||
removeForum(g);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
@@ -320,7 +340,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");
|
||||
@@ -364,22 +385,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,
|
||||
|
||||
@@ -17,8 +17,6 @@ import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.MessageStatus;
|
||||
import org.briarproject.api.sync.Offer;
|
||||
import org.briarproject.api.sync.Request;
|
||||
import org.briarproject.api.sync.SubscriptionAck;
|
||||
import org.briarproject.api.sync.SubscriptionUpdate;
|
||||
import org.briarproject.api.transport.TransportKeys;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -47,14 +45,8 @@ public interface DatabaseComponent {
|
||||
*/
|
||||
ContactId addContact(Author remote, AuthorId local) throws DbException;
|
||||
|
||||
/** Adds a group to the given contact's subscriptions. */
|
||||
void addContactGroup(ContactId c, Group g) throws DbException;
|
||||
|
||||
/**
|
||||
* Subscribes to a group, or returns false if the user already has the
|
||||
* maximum number of subscriptions.
|
||||
*/
|
||||
boolean addGroup(Group g) throws DbException;
|
||||
/** Stores a group. */
|
||||
void addGroup(Group g) throws DbException;
|
||||
|
||||
/** Stores a local pseudonym. */
|
||||
void addLocalAuthor(LocalAuthor a) throws DbException;
|
||||
@@ -63,14 +55,11 @@ public interface DatabaseComponent {
|
||||
void addLocalMessage(Message m, ClientId c, Metadata meta, boolean shared)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a transport and returns true if the transport was not previously
|
||||
* in the database.
|
||||
*/
|
||||
boolean addTransport(TransportId t, int maxLatency) throws DbException;
|
||||
/** Stores a transport. */
|
||||
void addTransport(TransportId t, int maxLatency) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given transport keys for a newly added contact.
|
||||
* Stores transport keys for a newly added contact.
|
||||
*/
|
||||
void addTransportKeys(ContactId c, TransportKeys k) throws DbException;
|
||||
|
||||
@@ -113,26 +102,6 @@ public interface DatabaseComponent {
|
||||
Collection<byte[]> generateRequestedBatch(ContactId c, int maxLength,
|
||||
int maxLatency) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns a subscription ack for the given contact, or null if no
|
||||
* subscription ack is due.
|
||||
*/
|
||||
SubscriptionAck generateSubscriptionAck(ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns a subscription update for the given contact, for transmission
|
||||
* over a transport with the given latency. Returns null if no update is
|
||||
* due.
|
||||
*/
|
||||
SubscriptionUpdate generateSubscriptionUpdate(ContactId c, int maxLatency)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all groups belonging to the given client to which the user could
|
||||
* subscribe.
|
||||
*/
|
||||
Collection<Group> getAvailableGroups(ClientId c) throws DbException;
|
||||
|
||||
/** Returns the contact with the given ID. */
|
||||
Contact getContact(ContactId c) throws DbException;
|
||||
|
||||
@@ -145,16 +114,13 @@ public interface DatabaseComponent {
|
||||
/** Returns the unique ID for this device. */
|
||||
DeviceId getDeviceId() throws DbException;
|
||||
|
||||
/** Returns the group with the given ID, if the user subscribes to it. */
|
||||
/** Returns the group with the given ID. */
|
||||
Group getGroup(GroupId g) throws DbException;
|
||||
|
||||
/** Returns the metadata for the given group. */
|
||||
Metadata getGroupMetadata(GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all groups belonging to the given client to which the user
|
||||
* subscribes.
|
||||
*/
|
||||
/** Returns all groups belonging to the given client. */
|
||||
Collection<Group> getGroups(ClientId c) throws DbException;
|
||||
|
||||
/** Returns the local pseudonym with the given ID. */
|
||||
@@ -196,9 +162,6 @@ public interface DatabaseComponent {
|
||||
/** Returns all settings in the given namespace. */
|
||||
Settings getSettings(String namespace) throws DbException;
|
||||
|
||||
/** Returns all contacts who subscribe to the given group. */
|
||||
Collection<Contact> getSubscribers(GroupId g) throws DbException;
|
||||
|
||||
/** Returns all transport keys for the given transport. */
|
||||
Map<ContactId, TransportKeys> getTransportKeys(TransportId t)
|
||||
throws DbException;
|
||||
@@ -216,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,21 +212,10 @@ public interface DatabaseComponent {
|
||||
/** Processes a request from the given contact. */
|
||||
void receiveRequest(ContactId c, Request r) throws DbException;
|
||||
|
||||
/** Processes a subscription ack from the given contact. */
|
||||
void receiveSubscriptionAck(ContactId c, SubscriptionAck a)
|
||||
throws DbException;
|
||||
|
||||
/** Processes a subscription update from the given contact. */
|
||||
void receiveSubscriptionUpdate(ContactId c, SubscriptionUpdate u)
|
||||
throws DbException;
|
||||
|
||||
/** Removes a contact (and all associated state) from the database. */
|
||||
void removeContact(ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Unsubscribes from a group. Any messages belonging to the group
|
||||
* are deleted from the database.
|
||||
*/
|
||||
/** Removes a group (and all associated state) from the database. */
|
||||
void removeGroup(Group g) throws DbException;
|
||||
|
||||
/**
|
||||
@@ -268,10 +223,7 @@ public interface DatabaseComponent {
|
||||
*/
|
||||
void removeLocalAuthor(AuthorId a) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a transport (and any associated configuration and local
|
||||
* properties) from the database.
|
||||
*/
|
||||
/** Removes a transport (and all associated state) from the database. */
|
||||
void removeTransport(TransportId t) throws DbException;
|
||||
|
||||
/** Sets the status of the given contact. */
|
||||
@@ -297,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.
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package org.briarproject.api.db;
|
||||
|
||||
/**
|
||||
* Thrown when a duplicate pseudonym is added to the database. This exception
|
||||
* may occur due to concurrent updates and does not indicate a database error.
|
||||
*/
|
||||
public class LocalAuthorExistsException extends DbException {
|
||||
|
||||
private static final long serialVersionUID = -1483877298070151673L;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.briarproject.api.db;
|
||||
|
||||
/**
|
||||
* Thrown when a duplicate message is added to the database. This exception may
|
||||
* occur due to concurrent updates and does not indicate a database error.
|
||||
*/
|
||||
public class MessageExistsException extends DbException {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.briarproject.api.db;
|
||||
|
||||
/**
|
||||
* Thrown when a database operation is attempted for a group that is not in the
|
||||
* database. This exception may occur due to concurrent updates and does not
|
||||
* indicate a database error.
|
||||
*/
|
||||
public class NoSuchGroupException extends DbException {
|
||||
|
||||
private static final long serialVersionUID = -5494178507342571697L;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package org.briarproject.api.db;
|
||||
|
||||
/**
|
||||
* Thrown when a database operation is attempted for a group to which the user
|
||||
* does not subscribe. This exception may occur due to concurrent updates and
|
||||
* does not indicate a database error.
|
||||
*/
|
||||
public class NoSuchSubscriptionException extends DbException {
|
||||
|
||||
private static final long serialVersionUID = -5494178507342571697L;
|
||||
|
||||
}
|
||||
@@ -2,12 +2,12 @@ package org.briarproject.api.event;
|
||||
|
||||
import org.briarproject.api.sync.Group;
|
||||
|
||||
/** An event that is broadcast when the user subscribes to a group. */
|
||||
public class SubscriptionAddedEvent extends Event {
|
||||
/** An event that is broadcast when a group is added. */
|
||||
public class GroupAddedEvent extends Event {
|
||||
|
||||
private final Group group;
|
||||
|
||||
public SubscriptionAddedEvent(Group group) {
|
||||
public GroupAddedEvent(Group group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package org.briarproject.api.event;
|
||||
|
||||
import org.briarproject.api.sync.Group;
|
||||
|
||||
/** An event that is broadcast when the user unsubscribes from a group. */
|
||||
public class SubscriptionRemovedEvent extends Event {
|
||||
/** An event that is broadcast when a group is removed. */
|
||||
public class GroupRemovedEvent extends Event {
|
||||
|
||||
private final Group group;
|
||||
|
||||
public SubscriptionRemovedEvent(Group group) {
|
||||
public GroupRemovedEvent(Group group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
@@ -4,15 +4,12 @@ import org.briarproject.api.contact.ContactId;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when the set of subscriptions visible to one or
|
||||
* more contacts is updated.
|
||||
*/
|
||||
public class LocalSubscriptionsUpdatedEvent extends Event {
|
||||
/** An event that is broadcast when the visibility of a group is updated. */
|
||||
public class GroupVisibilityUpdatedEvent extends Event {
|
||||
|
||||
private final Collection<ContactId> affected;
|
||||
|
||||
public LocalSubscriptionsUpdatedEvent(Collection<ContactId> affected) {
|
||||
public GroupVisibilityUpdatedEvent(Collection<ContactId> affected) {
|
||||
this.affected = affected;
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package org.briarproject.api.event;
|
||||
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
|
||||
/** An event that is broadcast when a contact's subscriptions are updated. */
|
||||
public class RemoteSubscriptionsUpdatedEvent extends Event {
|
||||
|
||||
private final ContactId contactId;
|
||||
|
||||
public RemoteSubscriptionsUpdatedEvent(ContactId contactId) {
|
||||
this.contactId = contactId;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
}
|
||||
@@ -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,22 +12,10 @@ 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, or returns false if the user already has the
|
||||
* maximum number of forum subscriptions.
|
||||
*/
|
||||
boolean 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, if the user subscribes to it. */
|
||||
/** Returns the forum with the given ID. */
|
||||
Forum getForum(GroupId g) throws DbException;
|
||||
|
||||
/** Returns all forums to which the user subscribes. */
|
||||
@@ -41,31 +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. Any messages belonging to the forum are
|
||||
* deleted.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package org.briarproject.api.sync;
|
||||
|
||||
import static org.briarproject.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||
|
||||
/** A group to which users may subscribe. */
|
||||
public class Group {
|
||||
|
||||
private final GroupId id;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package org.briarproject.api.sync;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface MessageFactory {
|
||||
|
||||
Message createMessage(GroupId groupId, long timestamp, byte[] body)
|
||||
throws IOException;
|
||||
Message createMessage(GroupId groupId, long timestamp, byte[] body);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -17,10 +17,4 @@ public interface PacketReader {
|
||||
|
||||
boolean hasRequest() throws IOException;
|
||||
Request readRequest() throws IOException;
|
||||
|
||||
boolean hasSubscriptionAck() throws IOException;
|
||||
SubscriptionAck readSubscriptionAck() throws IOException;
|
||||
|
||||
boolean hasSubscriptionUpdate() throws IOException;
|
||||
SubscriptionUpdate readSubscriptionUpdate() throws IOException;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,4 @@ public interface PacketTypes {
|
||||
byte MESSAGE = 1;
|
||||
byte OFFER = 2;
|
||||
byte REQUEST = 3;
|
||||
byte SUBSCRIPTION_ACK = 6;
|
||||
byte SUBSCRIPTION_UPDATE = 7;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,5 @@ public interface PacketWriter {
|
||||
|
||||
void writeRequest(Request r) throws IOException;
|
||||
|
||||
void writeSubscriptionAck(SubscriptionAck a) throws IOException;
|
||||
|
||||
void writeSubscriptionUpdate(SubscriptionUpdate u) throws IOException;
|
||||
|
||||
void flush() throws IOException;
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.briarproject.api.sync;
|
||||
|
||||
/** A packet acknowledging a {@link SubscriptionUpdate}. */
|
||||
public class SubscriptionAck {
|
||||
|
||||
private final long version;
|
||||
|
||||
public SubscriptionAck(long version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/** Returns the version number of the acknowledged update. */
|
||||
public long getVersion() {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.briarproject.api.sync;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/** A packet updating the recipient's view of the sender's subscriptions. */
|
||||
public class SubscriptionUpdate {
|
||||
|
||||
private final Collection<Group> groups;
|
||||
private final long version;
|
||||
|
||||
public SubscriptionUpdate(Collection<Group> groups, long version) {
|
||||
this.groups = groups;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the groups to which the sender subscribes, and which the sender
|
||||
* has made visible to the recipient.
|
||||
*/
|
||||
public Collection<Group> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
/** Returns the update's version number. */
|
||||
public long getVersion() {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,8 @@ public interface SyncConstants {
|
||||
/** The maximum length of the packet payload in bytes. */
|
||||
int MAX_PACKET_PAYLOAD_LENGTH = 32 * 1024; // 32 KiB
|
||||
|
||||
/** The maximum number of groups a user may subscribe to. */
|
||||
int MAX_SUBSCRIPTIONS = 200;
|
||||
|
||||
/** The maximum length of a group descriptor in bytes. */
|
||||
int MAX_GROUP_DESCRIPTOR_LENGTH = 100;
|
||||
int MAX_GROUP_DESCRIPTOR_LENGTH = 100; // TODO: Remove
|
||||
|
||||
/** The maximum length of a message in bytes. */
|
||||
int MAX_MESSAGE_LENGTH = MAX_PACKET_PAYLOAD_LENGTH - PACKET_HEADER_LENGTH;
|
||||
|
||||
@@ -17,8 +17,6 @@ import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.MessageStatus;
|
||||
import org.briarproject.api.sync.SubscriptionAck;
|
||||
import org.briarproject.api.sync.SubscriptionUpdate;
|
||||
import org.briarproject.api.sync.ValidationManager.Validity;
|
||||
import org.briarproject.api.transport.TransportKeys;
|
||||
|
||||
@@ -86,19 +84,11 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Adds a group to the given contact's subscriptions.
|
||||
* Stores a group.
|
||||
* <p>
|
||||
* Locking: write.
|
||||
*/
|
||||
void addContactGroup(T txn, ContactId c, Group g) throws DbException;
|
||||
|
||||
/**
|
||||
* Subscribes to a group, or returns false if the user already has the
|
||||
* maximum number of subscriptions.
|
||||
* <p>
|
||||
* Locking: write.
|
||||
*/
|
||||
boolean addGroup(T txn, Group g) throws DbException;
|
||||
void addGroup(T txn, Group g) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a local pseudonym.
|
||||
@@ -134,20 +124,20 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a transport and returns true if the transport was not previously
|
||||
* in the database.
|
||||
* Stores a transport.
|
||||
* <p>
|
||||
* Locking: write.
|
||||
*/
|
||||
boolean addTransport(T txn, TransportId t, int maxLatency)
|
||||
void addTransport(T txn, TransportId t, int maxLatency)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given transport keys for a newly added contact.
|
||||
* Stores transport keys for a newly added contact.
|
||||
* <p>
|
||||
* Locking: write.
|
||||
*/
|
||||
void addTransportKeys(T txn, ContactId c, TransportKeys k) throws DbException;
|
||||
void addTransportKeys(T txn, ContactId c, TransportKeys k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Makes a group visible to the given contact.
|
||||
@@ -173,7 +163,7 @@ interface Database<T> {
|
||||
boolean containsContact(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the user subscribes to the given group.
|
||||
* Returns true if the database contains the given group.
|
||||
* <p>
|
||||
* Locking: read.
|
||||
*/
|
||||
@@ -201,7 +191,7 @@ interface Database<T> {
|
||||
boolean containsTransport(T txn, TransportId t) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the user subscribes to the given group and the group is
|
||||
* Returns true if the database contains the given group and the group is
|
||||
* visible to the given contact.
|
||||
* <p>
|
||||
* Locking: read.
|
||||
@@ -225,14 +215,6 @@ interface Database<T> {
|
||||
*/
|
||||
int countOfferedMessages(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all groups belonging to the given client to which the user could
|
||||
* subscribe.
|
||||
* <p>
|
||||
* Locking: read.
|
||||
*/
|
||||
Collection<Group> getAvailableGroups(T txn, ClientId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the contact with the given ID.
|
||||
* <p>
|
||||
@@ -276,7 +258,7 @@ interface Database<T> {
|
||||
long getFreeSpace() throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the group with the given ID, if the user subscribes to it.
|
||||
* Returns the group with the given ID.
|
||||
* <p>
|
||||
* Locking: read.
|
||||
*/
|
||||
@@ -290,8 +272,7 @@ interface Database<T> {
|
||||
Metadata getGroupMetadata(T txn, GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all groups belonging to the given client to which the user
|
||||
* subscribes.
|
||||
* Returns all groups belonging to the given client.
|
||||
* <p>
|
||||
* Locking: read.
|
||||
*/
|
||||
@@ -413,30 +394,6 @@ interface Database<T> {
|
||||
*/
|
||||
Settings getSettings(T txn, String namespace) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all contacts who subscribe to the given group.
|
||||
* <p>
|
||||
* Locking: read.
|
||||
*/
|
||||
Collection<Contact> getSubscribers(T txn, GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns a subscription ack for the given contact, or null if no ack is
|
||||
* due.
|
||||
* <p>
|
||||
* Locking: write.
|
||||
*/
|
||||
SubscriptionAck getSubscriptionAck(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns a subscription update for the given contact and updates its
|
||||
* expiry time using the given latency, or returns null if no update is due.
|
||||
* <p>
|
||||
* Locking: write.
|
||||
*/
|
||||
SubscriptionUpdate getSubscriptionUpdate(T txn, ContactId c,
|
||||
int maxLatency) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all transport keys for the given transport.
|
||||
* <p>
|
||||
@@ -541,16 +498,14 @@ interface Database<T> {
|
||||
void removeContact(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Unsubscribes from a group. Any messages belonging to the group are
|
||||
* deleted from the database.
|
||||
* Removes a group (and all associated state) from the database.
|
||||
* <p>
|
||||
* Locking: write.
|
||||
*/
|
||||
void removeGroup(T txn, GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a local pseudonym (and all associated contacts) from the
|
||||
* database.
|
||||
* Removes a local pseudonym (and all associated state) from the database.
|
||||
* <p>
|
||||
* Locking: write.
|
||||
*/
|
||||
@@ -643,32 +598,6 @@ interface Database<T> {
|
||||
void setReorderingWindow(T txn, ContactId c, TransportId t,
|
||||
long rotationPeriod, long base, byte[] bitmap) throws DbException;
|
||||
|
||||
/**
|
||||
* Updates the given contact's subscriptions and returns true, unless an
|
||||
* update with an equal or higher version number has already been received
|
||||
* from the contact.
|
||||
* <p>
|
||||
* Locking: write.
|
||||
*/
|
||||
boolean setGroups(T txn, ContactId c, Collection<Group> groups,
|
||||
long version) throws DbException;
|
||||
|
||||
/**
|
||||
* Records a subscription ack from the given contact for the given version,
|
||||
* unless the contact has already acked an equal or higher version.
|
||||
* <p>
|
||||
* Locking: write.
|
||||
*/
|
||||
void setSubscriptionUpdateAcked(T txn, ContactId c, long version)
|
||||
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
|
||||
|
||||
@@ -7,17 +7,17 @@ import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.db.ContactExistsException;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.LocalAuthorExistsException;
|
||||
import org.briarproject.api.db.MessageExistsException;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.db.NoSuchContactException;
|
||||
import org.briarproject.api.db.NoSuchGroupException;
|
||||
import org.briarproject.api.db.NoSuchLocalAuthorException;
|
||||
import org.briarproject.api.db.NoSuchMessageException;
|
||||
import org.briarproject.api.db.NoSuchSubscriptionException;
|
||||
import org.briarproject.api.db.NoSuchTransportException;
|
||||
import org.briarproject.api.db.StorageStatus;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent;
|
||||
import org.briarproject.api.event.GroupAddedEvent;
|
||||
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;
|
||||
@@ -26,10 +26,7 @@ import org.briarproject.api.event.MessageToRequestEvent;
|
||||
import org.briarproject.api.event.MessageValidatedEvent;
|
||||
import org.briarproject.api.event.MessagesAckedEvent;
|
||||
import org.briarproject.api.event.MessagesSentEvent;
|
||||
import org.briarproject.api.event.RemoteSubscriptionsUpdatedEvent;
|
||||
import org.briarproject.api.event.SettingsUpdatedEvent;
|
||||
import org.briarproject.api.event.SubscriptionAddedEvent;
|
||||
import org.briarproject.api.event.SubscriptionRemovedEvent;
|
||||
import org.briarproject.api.event.TransportAddedEvent;
|
||||
import org.briarproject.api.event.TransportRemovedEvent;
|
||||
import org.briarproject.api.identity.Author;
|
||||
@@ -46,8 +43,6 @@ import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.MessageStatus;
|
||||
import org.briarproject.api.sync.Offer;
|
||||
import org.briarproject.api.sync.Request;
|
||||
import org.briarproject.api.sync.SubscriptionAck;
|
||||
import org.briarproject.api.sync.SubscriptionUpdate;
|
||||
import org.briarproject.api.sync.ValidationManager.Validity;
|
||||
import org.briarproject.api.transport.TransportKeys;
|
||||
|
||||
@@ -164,30 +159,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public void addContactGroup(ContactId c, Group g) throws DbException {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
db.addContactGroup(txn, c, g);
|
||||
db.commitTransaction(txn);
|
||||
} catch (DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addGroup(Group g) throws DbException {
|
||||
public void addGroup(Group g) throws DbException {
|
||||
boolean added = false;
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if (!db.containsGroup(txn, g.getId()))
|
||||
added = db.addGroup(txn, g);
|
||||
if (!db.containsGroup(txn, g.getId())) {
|
||||
db.addGroup(txn, g);
|
||||
added = true;
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
} catch (DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
@@ -196,8 +177,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
if (added) eventBus.broadcast(new SubscriptionAddedEvent(g));
|
||||
return added;
|
||||
if (added) eventBus.broadcast(new GroupAddedEvent(g));
|
||||
}
|
||||
|
||||
public void addLocalAuthor(LocalAuthor a) throws DbException {
|
||||
@@ -205,9 +185,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if (db.containsLocalAuthor(txn, a.getId()))
|
||||
throw new LocalAuthorExistsException();
|
||||
db.addLocalAuthor(txn, a);
|
||||
if (!db.containsLocalAuthor(txn, a.getId())) {
|
||||
db.addLocalAuthor(txn, a);
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
} catch (DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
@@ -220,15 +200,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
|
||||
public void addLocalMessage(Message m, ClientId c, Metadata meta,
|
||||
boolean shared) throws DbException {
|
||||
boolean added = false;
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if (db.containsMessage(txn, m.getId()))
|
||||
throw new MessageExistsException();
|
||||
if (!db.containsGroup(txn, m.getGroupId()))
|
||||
throw new NoSuchSubscriptionException();
|
||||
addMessage(txn, m, VALID, shared, null);
|
||||
throw new NoSuchGroupException();
|
||||
if (!db.containsMessage(txn, m.getId())) {
|
||||
addMessage(txn, m, VALID, shared, null);
|
||||
added = true;
|
||||
}
|
||||
db.mergeMessageMetadata(txn, m.getId(), meta);
|
||||
db.commitTransaction(txn);
|
||||
} catch (DbException e) {
|
||||
@@ -238,8 +220,11 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
eventBus.broadcast(new MessageAddedEvent(m, null));
|
||||
eventBus.broadcast(new MessageValidatedEvent(m, c, true, true));
|
||||
if (added) {
|
||||
eventBus.broadcast(new MessageAddedEvent(m, null));
|
||||
eventBus.broadcast(new MessageValidatedEvent(m, c, true, true));
|
||||
if (shared) eventBus.broadcast(new MessageSharedEvent(m));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,14 +251,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addTransport(TransportId t, int maxLatency)
|
||||
throws DbException {
|
||||
boolean added;
|
||||
public void addTransport(TransportId t, int maxLatency) throws DbException {
|
||||
boolean added = false;
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
added = db.addTransport(txn, t, maxLatency);
|
||||
if (!db.containsTransport(txn, t)) {
|
||||
db.addTransport(txn, t, maxLatency);
|
||||
added = true;
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
} catch (DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
@@ -283,7 +270,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
if (added) eventBus.broadcast(new TransportAddedEvent(t, maxLatency));
|
||||
return added;
|
||||
}
|
||||
|
||||
public void addTransportKeys(ContactId c, TransportKeys k)
|
||||
@@ -434,64 +420,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return Collections.unmodifiableList(messages);
|
||||
}
|
||||
|
||||
public SubscriptionAck generateSubscriptionAck(ContactId c)
|
||||
throws DbException {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
SubscriptionAck a = db.getSubscriptionAck(txn, c);
|
||||
db.commitTransaction(txn);
|
||||
return a;
|
||||
} catch (DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public SubscriptionUpdate generateSubscriptionUpdate(ContactId c,
|
||||
int maxLatency) throws DbException {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
SubscriptionUpdate u =
|
||||
db.getSubscriptionUpdate(txn, c, maxLatency);
|
||||
db.commitTransaction(txn);
|
||||
return u;
|
||||
} catch (DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<Group> getAvailableGroups(ClientId c) throws DbException {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
Collection<Group> groups = db.getAvailableGroups(txn, c);
|
||||
db.commitTransaction(txn);
|
||||
return groups;
|
||||
} catch (DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public Contact getContact(ContactId c) throws DbException {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
@@ -570,7 +498,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if (!db.containsGroup(txn, g))
|
||||
throw new NoSuchSubscriptionException();
|
||||
throw new NoSuchGroupException();
|
||||
Group group = db.getGroup(txn, g);
|
||||
db.commitTransaction(txn);
|
||||
return group;
|
||||
@@ -589,7 +517,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if (!db.containsGroup(txn, g))
|
||||
throw new NoSuchSubscriptionException();
|
||||
throw new NoSuchGroupException();
|
||||
Metadata metadata = db.getGroupMetadata(txn, g);
|
||||
db.commitTransaction(txn);
|
||||
return metadata;
|
||||
@@ -699,7 +627,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if (!db.containsGroup(txn, g))
|
||||
throw new NoSuchSubscriptionException();
|
||||
throw new NoSuchGroupException();
|
||||
Map<MessageId, Metadata> metadata =
|
||||
db.getMessageMetadata(txn, g);
|
||||
db.commitTransaction(txn);
|
||||
@@ -741,7 +669,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
if (!db.containsGroup(txn, g))
|
||||
throw new NoSuchSubscriptionException();
|
||||
throw new NoSuchGroupException();
|
||||
Collection<MessageStatus> statuses =
|
||||
db.getMessageStatus(txn, c, g);
|
||||
db.commitTransaction(txn);
|
||||
@@ -794,23 +722,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<Contact> getSubscribers(GroupId g) throws DbException {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
Collection<Contact> contacts = db.getSubscribers(txn, g);
|
||||
db.commitTransaction(txn);
|
||||
return contacts;
|
||||
} catch (DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public Map<ContactId, TransportKeys> getTransportKeys(TransportId t)
|
||||
throws DbException {
|
||||
lock.readLock().lock();
|
||||
@@ -857,7 +768,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if (!db.containsGroup(txn, g))
|
||||
throw new NoSuchSubscriptionException();
|
||||
throw new NoSuchGroupException();
|
||||
Collection<ContactId> visible = db.getVisibility(txn, g);
|
||||
db.commitTransaction(txn);
|
||||
return visible;
|
||||
@@ -891,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();
|
||||
@@ -898,7 +831,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if (!db.containsGroup(txn, g))
|
||||
throw new NoSuchSubscriptionException();
|
||||
throw new NoSuchGroupException();
|
||||
db.mergeGroupMetadata(txn, g, meta);
|
||||
db.commitTransaction(txn);
|
||||
} catch (DbException e) {
|
||||
@@ -990,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);
|
||||
@@ -1002,8 +935,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
if (visible) {
|
||||
if (!duplicate)
|
||||
eventBus.broadcast(new MessageAddedEvent(m, c));
|
||||
if (!duplicate) eventBus.broadcast(new MessageAddedEvent(m, c));
|
||||
eventBus.broadcast(new MessageToAckEvent(c));
|
||||
}
|
||||
}
|
||||
@@ -1066,46 +998,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
if (requested) eventBus.broadcast(new MessageRequestedEvent(c));
|
||||
}
|
||||
|
||||
public void receiveSubscriptionAck(ContactId c, SubscriptionAck a)
|
||||
throws DbException {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
db.setSubscriptionUpdateAcked(txn, c, a.getVersion());
|
||||
db.commitTransaction(txn);
|
||||
} catch (DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void receiveSubscriptionUpdate(ContactId c, SubscriptionUpdate u)
|
||||
throws DbException {
|
||||
boolean updated;
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
updated = db.setGroups(txn, c, u.getGroups(), u.getVersion());
|
||||
db.commitTransaction(txn);
|
||||
} catch (DbException e) {
|
||||
db.abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
if (updated) eventBus.broadcast(new RemoteSubscriptionsUpdatedEvent(c));
|
||||
}
|
||||
|
||||
public void removeContact(ContactId c) throws DbException {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
@@ -1132,7 +1024,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
try {
|
||||
GroupId id = g.getId();
|
||||
if (!db.containsGroup(txn, id))
|
||||
throw new NoSuchSubscriptionException();
|
||||
throw new NoSuchGroupException();
|
||||
affected = db.getVisibility(txn, id);
|
||||
db.removeGroup(txn, id);
|
||||
db.commitTransaction(txn);
|
||||
@@ -1143,8 +1035,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
eventBus.broadcast(new SubscriptionRemovedEvent(g));
|
||||
eventBus.broadcast(new LocalSubscriptionsUpdatedEvent(affected));
|
||||
eventBus.broadcast(new GroupRemovedEvent(g));
|
||||
eventBus.broadcast(new GroupVisibilityUpdatedEvent(affected));
|
||||
}
|
||||
|
||||
public void removeLocalAuthor(AuthorId a) throws DbException {
|
||||
@@ -1291,9 +1183,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
T txn = db.startTransaction();
|
||||
try {
|
||||
if (!db.containsGroup(txn, g))
|
||||
throw new NoSuchSubscriptionException();
|
||||
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
|
||||
@@ -1308,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);
|
||||
@@ -1319,30 +1209,23 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
if (!affected.isEmpty())
|
||||
eventBus.broadcast(new LocalSubscriptionsUpdatedEvent(affected));
|
||||
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 NoSuchSubscriptionException();
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new NoSuchGroupException();
|
||||
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);
|
||||
@@ -1351,8 +1234,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
if (!affected.isEmpty())
|
||||
eventBus.broadcast(new LocalSubscriptionsUpdatedEvent(affected));
|
||||
if (visible != wasVisible) {
|
||||
eventBus.broadcast(new GroupVisibilityUpdatedEvent(
|
||||
Collections.singletonList(c)));
|
||||
}
|
||||
}
|
||||
|
||||
public void updateTransportKeys(Map<ContactId, TransportKeys> keys)
|
||||
|
||||
@@ -20,8 +20,6 @@ import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.MessageStatus;
|
||||
import org.briarproject.api.sync.SubscriptionAck;
|
||||
import org.briarproject.api.sync.SubscriptionUpdate;
|
||||
import org.briarproject.api.sync.ValidationManager.Validity;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.api.transport.IncomingKeys;
|
||||
@@ -39,12 +37,10 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
@@ -54,7 +50,6 @@ import java.util.logging.Logger;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.api.db.Metadata.REMOVE;
|
||||
import static org.briarproject.api.db.StorageStatus.ADDING;
|
||||
import static org.briarproject.api.sync.SyncConstants.MAX_SUBSCRIPTIONS;
|
||||
import static org.briarproject.api.sync.ValidationManager.Validity.INVALID;
|
||||
import static org.briarproject.api.sync.ValidationManager.Validity.UNKNOWN;
|
||||
import static org.briarproject.api.sync.ValidationManager.Validity.VALID;
|
||||
@@ -71,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"
|
||||
@@ -109,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 =
|
||||
@@ -134,31 +128,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " REFERENCES groups (groupId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private static final String CREATE_CONTACT_GROUPS =
|
||||
"CREATE TABLE contactGroups"
|
||||
+ " (contactId INT NOT NULL,"
|
||||
+ " groupId HASH NOT NULL," // Not a foreign key
|
||||
+ " clientId HASH NOT NULL,"
|
||||
+ " descriptor BINARY NOT NULL,"
|
||||
+ " PRIMARY KEY (contactId, groupId),"
|
||||
+ " FOREIGN KEY (contactId)"
|
||||
+ " REFERENCES contacts (contactId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private static final String CREATE_GROUP_VERSIONS =
|
||||
"CREATE TABLE groupVersions"
|
||||
+ " (contactId INT NOT NULL,"
|
||||
+ " localVersion BIGINT NOT NULL,"
|
||||
+ " localAcked BIGINT NOT NULL,"
|
||||
+ " remoteVersion BIGINT NOT NULL,"
|
||||
+ " remoteAcked BOOLEAN NOT NULL,"
|
||||
+ " expiry BIGINT NOT NULL,"
|
||||
+ " txCount INT NOT NULL,"
|
||||
+ " PRIMARY KEY (contactId),"
|
||||
+ " FOREIGN KEY (contactid)"
|
||||
+ " REFERENCES contacts (contactId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private static final String CREATE_MESSAGES =
|
||||
"CREATE TABLE messages"
|
||||
+ " (messageId HASH NOT NULL,"
|
||||
@@ -353,8 +322,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
s.executeUpdate(insertTypeNames(CREATE_GROUPS));
|
||||
s.executeUpdate(insertTypeNames(CREATE_GROUP_METADATA));
|
||||
s.executeUpdate(insertTypeNames(CREATE_GROUP_VISIBILITIES));
|
||||
s.executeUpdate(insertTypeNames(CREATE_CONTACT_GROUPS));
|
||||
s.executeUpdate(insertTypeNames(CREATE_GROUP_VERSIONS));
|
||||
s.executeUpdate(insertTypeNames(CREATE_MESSAGES));
|
||||
s.executeUpdate(insertTypeNames(CREATE_MESSAGE_METADATA));
|
||||
s.executeUpdate(insertTypeNames(CREATE_OFFERS));
|
||||
@@ -543,40 +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();
|
||||
}
|
||||
// Create a group version row
|
||||
sql = "INSERT INTO groupVersions (contactId, localVersion,"
|
||||
+ " localAcked, remoteVersion, remoteAcked, expiry,"
|
||||
+ " txCount)"
|
||||
+ " VALUES (?, 1, 0, 0, TRUE, 0, 0)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
return c;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
@@ -585,57 +518,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
public void addContactGroup(Connection txn, ContactId c, Group g)
|
||||
throws DbException {
|
||||
public void addGroup(Connection txn, Group g) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT NULL FROM contactGroups"
|
||||
+ " WHERE contactId = ? AND groupId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setBytes(2, g.getId().getBytes());
|
||||
rs = ps.executeQuery();
|
||||
boolean found = rs.next();
|
||||
if (rs.next()) throw new DbStateException();
|
||||
rs.close();
|
||||
ps.close();
|
||||
if (found) return;
|
||||
sql = "INSERT INTO contactGroups"
|
||||
+ " (contactId, groupId, clientId, descriptor)"
|
||||
+ " VALUES (?, ?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setBytes(2, g.getId().getBytes());
|
||||
ps.setBytes(3, g.getClientId().getBytes());
|
||||
ps.setBytes(4, g.getDescriptor());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addGroup(Connection txn, Group g) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT COUNT (groupId) FROM groups";
|
||||
ps = txn.prepareStatement(sql);
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) throw new DbStateException();
|
||||
int count = rs.getInt(1);
|
||||
if (rs.next()) throw new DbStateException();
|
||||
rs.close();
|
||||
ps.close();
|
||||
if (count > MAX_SUBSCRIPTIONS) throw new DbStateException();
|
||||
if (count == MAX_SUBSCRIPTIONS) return false;
|
||||
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());
|
||||
@@ -643,9 +530,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
@@ -750,23 +635,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addTransport(Connection txn, TransportId t, int maxLatency)
|
||||
public void addTransport(Connection txn, TransportId t, int maxLatency)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
// Return false if the transport is already in the database
|
||||
String sql = "SELECT NULL FROM transports WHERE transportId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, t.getString());
|
||||
rs = ps.executeQuery();
|
||||
boolean found = rs.next();
|
||||
if (rs.next()) throw new DbStateException();
|
||||
rs.close();
|
||||
ps.close();
|
||||
if (found) return false;
|
||||
// Create a transport row
|
||||
sql = "INSERT INTO transports (transportId, maxLatency)"
|
||||
String sql = "INSERT INTO transports (transportId, maxLatency)"
|
||||
+ " VALUES (?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, t.getString());
|
||||
@@ -774,10 +647,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
tryToClose(rs);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
@@ -855,16 +726,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Bump the subscription version
|
||||
sql = "UPDATE groupVersions"
|
||||
+ " SET localVersion = localVersion + 1,"
|
||||
+ " expiry = 0, txCount = 0"
|
||||
+ " WHERE contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -1072,39 +933,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<Group> getAvailableGroups(Connection txn, ClientId c)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT DISTINCT cg.groupId, cg.descriptor"
|
||||
+ " FROM contactGroups AS cg"
|
||||
+ " LEFT OUTER JOIN groups AS g"
|
||||
+ " ON cg.groupId = g.groupId"
|
||||
+ " WHERE cg.clientId = ?"
|
||||
+ " AND g.groupId IS NULL"
|
||||
+ " GROUP BY cg.groupId";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, c.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
List<Group> groups = new ArrayList<Group>();
|
||||
Set<GroupId> ids = new HashSet<GroupId>();
|
||||
while (rs.next()) {
|
||||
GroupId id = new GroupId(rs.getBytes(1));
|
||||
if (!ids.add(id)) throw new DbStateException();
|
||||
byte[] descriptor = rs.getBytes(2);
|
||||
groups.add(new Group(id, c, descriptor));
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
return Collections.unmodifiableList(groups);
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Contact getContact(Connection txn, ContactId c) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
@@ -1474,15 +1302,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT m.messageId FROM messages AS m"
|
||||
+ " JOIN contactGroups AS cg"
|
||||
+ " ON m.groupId = cg.groupId"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " AND cg.contactId = gv.contactId"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " ON m.messageId = s.messageId"
|
||||
+ " AND cg.contactId = s.contactId"
|
||||
+ " WHERE cg.contactId = ?"
|
||||
+ " AND gv.contactId = s.contactId"
|
||||
+ " WHERE gv.contactId = ?"
|
||||
+ " AND valid = ? AND shared = TRUE"
|
||||
+ " AND seen = FALSE AND requested = FALSE"
|
||||
+ " AND s.expiry < ?"
|
||||
@@ -1536,15 +1361,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT length, m.messageId FROM messages AS m"
|
||||
+ " JOIN contactGroups AS cg"
|
||||
+ " ON m.groupId = cg.groupId"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " AND cg.contactId = gv.contactId"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " ON m.messageId = s.messageId"
|
||||
+ " AND cg.contactId = s.contactId"
|
||||
+ " WHERE cg.contactId = ?"
|
||||
+ " AND gv.contactId = s.contactId"
|
||||
+ " WHERE gv.contactId = ?"
|
||||
+ " AND valid = ? AND shared = TRUE"
|
||||
+ " AND seen = FALSE"
|
||||
+ " AND s.expiry < ?"
|
||||
@@ -1625,15 +1447,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT length, m.messageId FROM messages AS m"
|
||||
+ " JOIN contactGroups AS cg"
|
||||
+ " ON m.groupId = cg.groupId"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " AND cg.contactId = gv.contactId"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " ON m.messageId = s.messageId"
|
||||
+ " AND cg.contactId = s.contactId"
|
||||
+ " WHERE cg.contactId = ?"
|
||||
+ " AND gv.contactId = s.contactId"
|
||||
+ " WHERE gv.contactId = ?"
|
||||
+ " AND valid = ? AND shared = TRUE"
|
||||
+ " AND seen = FALSE AND requested = TRUE"
|
||||
+ " AND s.expiry < ?"
|
||||
@@ -1682,130 +1501,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<Contact> getSubscribers(Connection txn, GroupId g)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT c.contactId, authorId, c.name, publicKey,"
|
||||
+ " localAuthorId, status"
|
||||
+ " FROM contacts AS c"
|
||||
+ " JOIN contactGroups AS cg"
|
||||
+ " ON c.contactId = cg.contactId"
|
||||
+ " WHERE groupId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
List<Contact> contacts = new ArrayList<Contact>();
|
||||
while (rs.next()) {
|
||||
ContactId contactId = new ContactId(rs.getInt(1));
|
||||
AuthorId authorId = new AuthorId(rs.getBytes(2));
|
||||
String name = rs.getString(3);
|
||||
byte[] publicKey = rs.getBytes(4);
|
||||
Author author = new Author(authorId, name, publicKey);
|
||||
AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
|
||||
StorageStatus status = StorageStatus.fromValue(rs.getInt(6));
|
||||
contacts.add(new Contact(contactId, author, localAuthorId,
|
||||
status));
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
return Collections.unmodifiableList(contacts);
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public SubscriptionAck getSubscriptionAck(Connection txn, ContactId c)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT remoteVersion FROM groupVersions"
|
||||
+ " WHERE contactId = ? AND remoteAcked = FALSE";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) {
|
||||
rs.close();
|
||||
ps.close();
|
||||
return null;
|
||||
}
|
||||
long version = rs.getLong(1);
|
||||
if (rs.next()) throw new DbStateException();
|
||||
rs.close();
|
||||
ps.close();
|
||||
sql = "UPDATE groupVersions SET remoteAcked = TRUE"
|
||||
+ " WHERE contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
return new SubscriptionAck(version);
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
tryToClose(rs);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public SubscriptionUpdate getSubscriptionUpdate(Connection txn, ContactId c,
|
||||
int maxLatency) throws DbException {
|
||||
long now = clock.currentTimeMillis();
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT g.groupId, clientId, descriptor,"
|
||||
+ " localVersion, txCount"
|
||||
+ " FROM groups AS g"
|
||||
+ " JOIN groupVisibilities AS gvis"
|
||||
+ " ON g.groupId = gvis.groupId"
|
||||
+ " JOIN groupVersions AS gver"
|
||||
+ " ON gvis.contactId = gver.contactId"
|
||||
+ " WHERE gvis.contactId = ?"
|
||||
+ " AND localVersion > localAcked"
|
||||
+ " AND expiry < ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setLong(2, now);
|
||||
rs = ps.executeQuery();
|
||||
List<Group> groups = new ArrayList<Group>();
|
||||
Set<GroupId> ids = new HashSet<GroupId>();
|
||||
long version = 0;
|
||||
int txCount = 0;
|
||||
while (rs.next()) {
|
||||
GroupId id = new GroupId(rs.getBytes(1));
|
||||
if (!ids.add(id)) throw new DbStateException();
|
||||
ClientId clientId = new ClientId(rs.getBytes(2));
|
||||
byte[] descriptor = rs.getBytes(3);
|
||||
groups.add(new Group(id, clientId, descriptor));
|
||||
version = rs.getLong(4);
|
||||
txCount = rs.getInt(5);
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
if (groups.isEmpty()) return null;
|
||||
sql = "UPDATE groupVersions"
|
||||
+ " SET expiry = ?, txCount = txCount + 1"
|
||||
+ " WHERE contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setLong(1, calculateExpiry(now, maxLatency, txCount));
|
||||
ps.setInt(2, c.getInt());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
groups = Collections.unmodifiableList(groups);
|
||||
return new SubscriptionUpdate(groups, version);
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
tryToClose(rs);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<ContactId, TransportKeys> getTransportKeys(Connection txn,
|
||||
TransportId t) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
@@ -2187,44 +1882,15 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
public void removeGroup(Connection txn, GroupId g) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
// Find out which contacts are affected
|
||||
String sql = "SELECT contactId FROM groupVisibilities"
|
||||
+ " WHERE groupId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
Collection<Integer> visible = new ArrayList<Integer>();
|
||||
while (rs.next()) visible.add(rs.getInt(1));
|
||||
rs.close();
|
||||
ps.close();
|
||||
// Delete the group
|
||||
sql = "DELETE FROM groups WHERE groupId = ?";
|
||||
String sql = "DELETE FROM groups WHERE groupId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
if (visible.isEmpty()) return;
|
||||
// Bump the subscription versions for the affected contacts
|
||||
sql = "UPDATE groupVersions"
|
||||
+ " SET localVersion = localVersion + 1, expiry = 0"
|
||||
+ " WHERE contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
for (Integer c : visible) {
|
||||
ps.setInt(1, c);
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != visible.size())
|
||||
throw new DbStateException();
|
||||
for (int rows : batchAffected)
|
||||
if (rows != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
tryToClose(rs);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
@@ -2331,15 +1997,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Bump the subscription version
|
||||
sql = "UPDATE groupVersions"
|
||||
+ " SET localVersion = localVersion + 1, expiry = 0"
|
||||
+ " WHERE contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -2454,130 +2111,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean setGroups(Connection txn, ContactId c,
|
||||
Collection<Group> groups, long version) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
// Mark the update as needing to be acked
|
||||
String sql = "UPDATE groupVersions"
|
||||
+ " SET remoteVersion = ?, remoteAcked = FALSE"
|
||||
+ " WHERE contactId = ? AND remoteVersion < ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setLong(1, version);
|
||||
ps.setInt(2, c.getInt());
|
||||
ps.setLong(3, version);
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Return false if the update is obsolete
|
||||
if (affected == 0) return false;
|
||||
// Find any messages in groups that are being removed
|
||||
Set<GroupId> newIds = new HashSet<GroupId>();
|
||||
for (Group g : groups) newIds.add(g.getId());
|
||||
sql = "SELECT messageId, m.groupId"
|
||||
+ " FROM messages AS m"
|
||||
+ " JOIN contactGroups AS cg"
|
||||
+ " ON m.groupId = cg.groupId"
|
||||
+ " WHERE contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> removed = new ArrayList<MessageId>();
|
||||
while (rs.next()) {
|
||||
if (!newIds.contains(new GroupId(rs.getBytes(2))))
|
||||
removed.add(new MessageId(rs.getBytes(1)));
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
// Reset any statuses for messages in groups that are being removed
|
||||
if (!removed.isEmpty()) {
|
||||
sql = "UPDATE statuses SET ack = FALSE, seen = FALSE,"
|
||||
+ " requested = FALSE, expiry = 0, txCount = 0"
|
||||
+ " WHERE contactId = ? AND messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
for (MessageId m : removed) {
|
||||
ps.setBytes(2, m.getBytes());
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != removed.size())
|
||||
throw new DbStateException();
|
||||
for (int rows : batchAffected)
|
||||
if (rows < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
}
|
||||
// Delete the existing subscriptions, if any
|
||||
sql = "DELETE FROM contactGroups WHERE contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.executeUpdate();
|
||||
// Store the new subscriptions, if any
|
||||
if (groups.isEmpty()) return true;
|
||||
sql = "INSERT INTO contactGroups"
|
||||
+ " (contactId, groupId, clientId, descriptor)"
|
||||
+ " VALUES (?, ?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
for (Group g : groups) {
|
||||
ps.setBytes(2, g.getId().getBytes());
|
||||
ps.setBytes(3, g.getClientId().getBytes());
|
||||
ps.setBytes(4, g.getDescriptor());
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != groups.size())
|
||||
throw new DbStateException();
|
||||
for (int rows : batchAffected)
|
||||
if (rows != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
tryToClose(rs);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSubscriptionUpdateAcked(Connection txn, ContactId c,
|
||||
long version) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "UPDATE groupVersions SET localAcked = ?"
|
||||
+ " WHERE contactId = ?"
|
||||
+ " AND localAcked < ? AND localVersion >= ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setLong(1, version);
|
||||
ps.setInt(2, c.getInt());
|
||||
ps.setLong(3, version);
|
||||
ps.setLong(4, version);
|
||||
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 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,70 +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 boolean addForum(Forum f) throws DbException {
|
||||
return 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 {
|
||||
Collection<Group> groups = db.getAvailableGroups(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);
|
||||
}
|
||||
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();
|
||||
}
|
||||
return Collections.unmodifiableList(forums);
|
||||
}
|
||||
|
||||
private Forum parseForum(Group g) throws FormatException {
|
||||
@@ -161,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) {
|
||||
@@ -174,136 +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 {
|
||||
return db.getSubscribers(g);
|
||||
}
|
||||
|
||||
@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,11 +73,10 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
|
||||
@Override
|
||||
public void addingContact(ContactId c) {
|
||||
try {
|
||||
// Create the conversation group
|
||||
Group g = getConversationGroup(db.getContact(c));
|
||||
// Subscribe to the group and share it with the contact
|
||||
// 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.addContactGroup(c, g);
|
||||
db.setVisibility(g.getId(), Collections.singletonList(c));
|
||||
// Attach the contact ID to the group
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
@@ -88,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);
|
||||
}
|
||||
@@ -136,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
|
||||
@@ -173,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;
|
||||
@@ -19,7 +20,7 @@ import org.briarproject.api.data.MetadataParser;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.db.NoSuchSubscriptionException;
|
||||
import org.briarproject.api.db.NoSuchGroupException;
|
||||
import org.briarproject.api.properties.TransportProperties;
|
||||
import org.briarproject.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
@@ -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;
|
||||
@@ -96,9 +100,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
try {
|
||||
// Create a group to share with the contact
|
||||
Group g = getContactGroup(db.getContact(c));
|
||||
// Subscribe to the group and share it with the contact
|
||||
// Store the group and share it with the contact
|
||||
db.addGroup(g);
|
||||
db.addContactGroup(c, g);
|
||||
db.setVisibility(g.getId(), Collections.singletonList(c));
|
||||
// Copy the latest local properties into the group
|
||||
DeviceId dev = db.getDeviceId();
|
||||
@@ -110,21 +113,158 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IOException 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();
|
||||
}
|
||||
}
|
||||
|
||||
private Group getContactGroup(Contact c) {
|
||||
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, IOException {
|
||||
throws DbException, FormatException {
|
||||
byte[] body = encodeProperties(dev, t, p, version);
|
||||
long now = clock.currentTimeMillis();
|
||||
Message m = messageFactory.createMessage(g, now, body);
|
||||
@@ -153,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 (NoSuchSubscriptionException 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,
|
||||
@@ -242,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 (NoSuchSubscriptionException 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) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.api.db.StorageStatus.ADDING;
|
||||
|
||||
// TODO: Move this class to the identity package
|
||||
class AuthorFactoryImpl implements AuthorFactory {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.io.IOException;
|
||||
import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
|
||||
// TODO: Move this class to the identity package
|
||||
class AuthorReader implements ObjectReader<Author> {
|
||||
|
||||
private final AuthorFactory authorFactory;
|
||||
|
||||
@@ -8,21 +8,17 @@ import org.briarproject.api.event.ContactRemovedEvent;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent;
|
||||
import org.briarproject.api.event.GroupVisibilityUpdatedEvent;
|
||||
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.RemoteSubscriptionsUpdatedEvent;
|
||||
import org.briarproject.api.event.ShutdownEvent;
|
||||
import org.briarproject.api.event.TransportRemovedEvent;
|
||||
import org.briarproject.api.sync.Ack;
|
||||
import org.briarproject.api.sync.Offer;
|
||||
import org.briarproject.api.sync.PacketWriter;
|
||||
import org.briarproject.api.sync.Request;
|
||||
import org.briarproject.api.sync.SubscriptionAck;
|
||||
import org.briarproject.api.sync.SubscriptionUpdate;
|
||||
import org.briarproject.api.sync.SyncSession;
|
||||
import org.briarproject.api.system.Clock;
|
||||
|
||||
@@ -87,9 +83,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
public void run() throws IOException {
|
||||
eventBus.addListener(this);
|
||||
try {
|
||||
// Start a query for each type of packet, in order of urgency
|
||||
dbExecutor.execute(new GenerateSubscriptionAck());
|
||||
dbExecutor.execute(new GenerateSubscriptionUpdate());
|
||||
// Start a query for each type of packet
|
||||
dbExecutor.execute(new GenerateAck());
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
@@ -118,7 +112,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
now = clock.currentTimeMillis();
|
||||
if (now >= nextRetxQuery) {
|
||||
// Check for retransmittable packets
|
||||
dbExecutor.execute(new GenerateSubscriptionUpdate());
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
nextRetxQuery = now + RETX_QUERY_INTERVAL;
|
||||
@@ -157,16 +150,10 @@ 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())
|
||||
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
||||
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
||||
if (g.getAffectedContacts().contains(contactId))
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
} else if (e instanceof LocalSubscriptionsUpdatedEvent) {
|
||||
LocalSubscriptionsUpdatedEvent l =
|
||||
(LocalSubscriptionsUpdatedEvent) e;
|
||||
if (l.getAffectedContacts().contains(contactId)) {
|
||||
dbExecutor.execute(new GenerateSubscriptionUpdate());
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
}
|
||||
} else if (e instanceof MessageRequestedEvent) {
|
||||
if (((MessageRequestedEvent) e).getContactId().equals(contactId))
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
@@ -176,13 +163,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
} else if (e instanceof MessageToRequestEvent) {
|
||||
if (((MessageToRequestEvent) e).getContactId().equals(contactId))
|
||||
dbExecutor.execute(new GenerateRequest());
|
||||
} else if (e instanceof RemoteSubscriptionsUpdatedEvent) {
|
||||
RemoteSubscriptionsUpdatedEvent r =
|
||||
(RemoteSubscriptionsUpdatedEvent) e;
|
||||
if (r.getContactId().equals(contactId)) {
|
||||
dbExecutor.execute(new GenerateSubscriptionAck());
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
}
|
||||
} else if (e instanceof ShutdownEvent) {
|
||||
interrupt();
|
||||
} else if (e instanceof TransportRemovedEvent) {
|
||||
@@ -332,75 +312,4 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
dbExecutor.execute(new GenerateRequest());
|
||||
}
|
||||
}
|
||||
|
||||
// This task runs on the database thread
|
||||
private class GenerateSubscriptionAck implements Runnable {
|
||||
|
||||
public void run() {
|
||||
if (interrupted) return;
|
||||
try {
|
||||
SubscriptionAck a = db.generateSubscriptionAck(contactId);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Generated subscription ack: " + (a != null));
|
||||
if (a != null) writerTasks.add(new WriteSubscriptionAck(a));
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This tasks runs on the writer thread
|
||||
private class WriteSubscriptionAck
|
||||
implements ThrowingRunnable<IOException> {
|
||||
|
||||
private final SubscriptionAck ack;
|
||||
|
||||
private WriteSubscriptionAck(SubscriptionAck ack) {
|
||||
this.ack = ack;
|
||||
}
|
||||
|
||||
public void run() throws IOException {
|
||||
if (interrupted) return;
|
||||
packetWriter.writeSubscriptionAck(ack);
|
||||
LOG.info("Sent subscription ack");
|
||||
dbExecutor.execute(new GenerateSubscriptionAck());
|
||||
}
|
||||
}
|
||||
|
||||
// This task runs on the database thread
|
||||
private class GenerateSubscriptionUpdate implements Runnable {
|
||||
|
||||
public void run() {
|
||||
if (interrupted) return;
|
||||
try {
|
||||
SubscriptionUpdate u =
|
||||
db.generateSubscriptionUpdate(contactId, maxLatency);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Generated subscription update: " + (u != null));
|
||||
if (u != null) writerTasks.add(new WriteSubscriptionUpdate(u));
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This task runs on the writer thread
|
||||
private class WriteSubscriptionUpdate
|
||||
implements ThrowingRunnable<IOException> {
|
||||
|
||||
private final SubscriptionUpdate update;
|
||||
|
||||
private WriteSubscriptionUpdate(SubscriptionUpdate update) {
|
||||
this.update = update;
|
||||
}
|
||||
|
||||
public void run() throws IOException {
|
||||
if (interrupted) return;
|
||||
packetWriter.writeSubscriptionUpdate(update);
|
||||
LOG.info("Sent subscription update");
|
||||
dbExecutor.execute(new GenerateSubscriptionUpdate());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package org.briarproject.sync;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.UniqueId;
|
||||
import org.briarproject.api.data.BdfReader;
|
||||
import org.briarproject.api.data.ObjectReader;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.GroupFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.briarproject.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||
|
||||
class GroupReader implements ObjectReader<Group> {
|
||||
|
||||
private final GroupFactory groupFactory;
|
||||
|
||||
GroupReader(GroupFactory groupFactory) {
|
||||
this.groupFactory = groupFactory;
|
||||
}
|
||||
|
||||
public Group readObject(BdfReader r) throws IOException {
|
||||
r.readListStart();
|
||||
byte[] id = r.readRaw(UniqueId.LENGTH);
|
||||
if (id.length != UniqueId.LENGTH) throw new FormatException();
|
||||
byte[] descriptor = r.readRaw(MAX_GROUP_DESCRIPTOR_LENGTH);
|
||||
r.readListEnd();
|
||||
return groupFactory.createGroup(new ClientId(id), descriptor);
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,6 @@ import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.Offer;
|
||||
import org.briarproject.api.sync.PacketReader;
|
||||
import org.briarproject.api.sync.Request;
|
||||
import org.briarproject.api.sync.SubscriptionAck;
|
||||
import org.briarproject.api.sync.SubscriptionUpdate;
|
||||
import org.briarproject.api.sync.SyncSession;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -69,12 +67,6 @@ class IncomingSession implements SyncSession, EventListener {
|
||||
} else if (packetReader.hasRequest()) {
|
||||
Request r = packetReader.readRequest();
|
||||
dbExecutor.execute(new ReceiveRequest(r));
|
||||
} else if (packetReader.hasSubscriptionAck()) {
|
||||
SubscriptionAck a = packetReader.readSubscriptionAck();
|
||||
dbExecutor.execute(new ReceiveSubscriptionAck(a));
|
||||
} else if (packetReader.hasSubscriptionUpdate()) {
|
||||
SubscriptionUpdate u = packetReader.readSubscriptionUpdate();
|
||||
dbExecutor.execute(new ReceiveSubscriptionUpdate(u));
|
||||
} else {
|
||||
throw new FormatException();
|
||||
}
|
||||
@@ -172,40 +164,4 @@ class IncomingSession implements SyncSession, EventListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ReceiveSubscriptionAck implements Runnable {
|
||||
|
||||
private final SubscriptionAck ack;
|
||||
|
||||
private ReceiveSubscriptionAck(SubscriptionAck ack) {
|
||||
this.ack = ack;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
db.receiveSubscriptionAck(contactId, ack);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ReceiveSubscriptionUpdate implements Runnable {
|
||||
|
||||
private final SubscriptionUpdate update;
|
||||
|
||||
private ReceiveSubscriptionUpdate(SubscriptionUpdate update) {
|
||||
this.update = update;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
db.receiveSubscriptionUpdate(contactId, update);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ import org.briarproject.api.sync.MessageFactory;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
|
||||
@@ -25,8 +23,7 @@ class MessageFactoryImpl implements MessageFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message createMessage(GroupId groupId, long timestamp, byte[] body)
|
||||
throws IOException {
|
||||
public Message createMessage(GroupId groupId, long timestamp, byte[] body) {
|
||||
if (body.length > MAX_MESSAGE_BODY_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package org.briarproject.sync;
|
||||
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.data.BdfReaderFactory;
|
||||
import org.briarproject.api.data.ObjectReader;
|
||||
import org.briarproject.api.sync.PacketReader;
|
||||
import org.briarproject.api.sync.PacketReaderFactory;
|
||||
import org.briarproject.api.sync.SubscriptionUpdate;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
@@ -14,20 +11,13 @@ import javax.inject.Inject;
|
||||
class PacketReaderFactoryImpl implements PacketReaderFactory {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final ObjectReader<SubscriptionUpdate> subscriptionUpdateReader;
|
||||
|
||||
@Inject
|
||||
PacketReaderFactoryImpl(CryptoComponent crypto,
|
||||
BdfReaderFactory bdfReaderFactory,
|
||||
ObjectReader<SubscriptionUpdate> subscriptionUpdateReader) {
|
||||
PacketReaderFactoryImpl(CryptoComponent crypto) {
|
||||
this.crypto = crypto;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.subscriptionUpdateReader = subscriptionUpdateReader;
|
||||
}
|
||||
|
||||
public PacketReader createPacketReader(InputStream in) {
|
||||
return new PacketReaderImpl(crypto, bdfReaderFactory,
|
||||
subscriptionUpdateReader, in);
|
||||
return new PacketReaderImpl(crypto, in);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,6 @@ package org.briarproject.sync;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.UniqueId;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.data.BdfReader;
|
||||
import org.briarproject.api.data.BdfReaderFactory;
|
||||
import org.briarproject.api.data.ObjectReader;
|
||||
import org.briarproject.api.sync.Ack;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.Message;
|
||||
@@ -13,11 +10,8 @@ import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.Offer;
|
||||
import org.briarproject.api.sync.PacketReader;
|
||||
import org.briarproject.api.sync.Request;
|
||||
import org.briarproject.api.sync.SubscriptionAck;
|
||||
import org.briarproject.api.sync.SubscriptionUpdate;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
@@ -28,8 +22,6 @@ import static org.briarproject.api.sync.PacketTypes.ACK;
|
||||
import static org.briarproject.api.sync.PacketTypes.MESSAGE;
|
||||
import static org.briarproject.api.sync.PacketTypes.OFFER;
|
||||
import static org.briarproject.api.sync.PacketTypes.REQUEST;
|
||||
import static org.briarproject.api.sync.PacketTypes.SUBSCRIPTION_ACK;
|
||||
import static org.briarproject.api.sync.PacketTypes.SUBSCRIPTION_UPDATE;
|
||||
import static org.briarproject.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.PACKET_HEADER_LENGTH;
|
||||
@@ -41,20 +33,14 @@ class PacketReaderImpl implements PacketReader {
|
||||
private enum State { BUFFER_EMPTY, BUFFER_FULL, EOF }
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final ObjectReader<SubscriptionUpdate> subscriptionUpdateReader;
|
||||
private final InputStream in;
|
||||
private final byte[] header, payload;
|
||||
|
||||
private State state = State.BUFFER_EMPTY;
|
||||
private int payloadLength = 0;
|
||||
|
||||
PacketReaderImpl(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
||||
ObjectReader<SubscriptionUpdate> subscriptionUpdateReader,
|
||||
InputStream in) {
|
||||
PacketReaderImpl(CryptoComponent crypto, InputStream in) {
|
||||
this.crypto = crypto;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.subscriptionUpdateReader = subscriptionUpdateReader;
|
||||
this.in = in;
|
||||
header = new byte[PACKET_HEADER_LENGTH];
|
||||
payload = new byte[MAX_PACKET_PAYLOAD_LENGTH];
|
||||
@@ -156,42 +142,4 @@ class PacketReaderImpl implements PacketReader {
|
||||
if (!hasRequest()) throw new FormatException();
|
||||
return new Request(Collections.unmodifiableList(readMessageIds()));
|
||||
}
|
||||
|
||||
public boolean hasSubscriptionAck() throws IOException {
|
||||
return !eof() && header[1] == SUBSCRIPTION_ACK;
|
||||
}
|
||||
|
||||
public SubscriptionAck readSubscriptionAck() throws IOException {
|
||||
if (!hasSubscriptionAck()) throw new FormatException();
|
||||
// Set up the reader
|
||||
InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
|
||||
BdfReader r = bdfReaderFactory.createReader(bais);
|
||||
// Read the start of the payload
|
||||
r.readListStart();
|
||||
// Read the version
|
||||
long version = r.readInteger();
|
||||
if (version < 0) throw new FormatException();
|
||||
// Read the end of the payload
|
||||
r.readListEnd();
|
||||
if (!r.eof()) throw new FormatException();
|
||||
state = State.BUFFER_EMPTY;
|
||||
// Build and return the subscription ack
|
||||
return new SubscriptionAck(version);
|
||||
}
|
||||
|
||||
public boolean hasSubscriptionUpdate() throws IOException {
|
||||
return !eof() && header[1] == SUBSCRIPTION_UPDATE;
|
||||
}
|
||||
|
||||
public SubscriptionUpdate readSubscriptionUpdate() throws IOException {
|
||||
if (!hasSubscriptionUpdate()) throw new FormatException();
|
||||
// Set up the reader
|
||||
InputStream bais = new ByteArrayInputStream(payload, 0, payloadLength);
|
||||
BdfReader r = bdfReaderFactory.createReader(bais);
|
||||
// Read and build the subscription update
|
||||
SubscriptionUpdate u = subscriptionUpdateReader.readObject(r);
|
||||
if (!r.eof()) throw new FormatException();
|
||||
state = State.BUFFER_EMPTY;
|
||||
return u;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
package org.briarproject.sync;
|
||||
|
||||
import org.briarproject.api.data.BdfWriterFactory;
|
||||
import org.briarproject.api.sync.PacketWriter;
|
||||
import org.briarproject.api.sync.PacketWriterFactory;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
class PacketWriterFactoryImpl implements PacketWriterFactory {
|
||||
|
||||
private final BdfWriterFactory bdfWriterFactory;
|
||||
|
||||
@Inject
|
||||
PacketWriterFactoryImpl(BdfWriterFactory bdfWriterFactory) {
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
}
|
||||
|
||||
public PacketWriter createPacketWriter(OutputStream out) {
|
||||
return new PacketWriterImpl(bdfWriterFactory, out);
|
||||
return new PacketWriterImpl(out);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
package org.briarproject.sync;
|
||||
|
||||
import org.briarproject.api.UniqueId;
|
||||
import org.briarproject.api.data.BdfWriter;
|
||||
import org.briarproject.api.data.BdfWriterFactory;
|
||||
import org.briarproject.api.sync.Ack;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.Offer;
|
||||
import org.briarproject.api.sync.PacketTypes;
|
||||
import org.briarproject.api.sync.PacketWriter;
|
||||
import org.briarproject.api.sync.Request;
|
||||
import org.briarproject.api.sync.SubscriptionAck;
|
||||
import org.briarproject.api.sync.SubscriptionUpdate;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -21,8 +16,6 @@ import java.io.OutputStream;
|
||||
import static org.briarproject.api.sync.PacketTypes.ACK;
|
||||
import static org.briarproject.api.sync.PacketTypes.OFFER;
|
||||
import static org.briarproject.api.sync.PacketTypes.REQUEST;
|
||||
import static org.briarproject.api.sync.PacketTypes.SUBSCRIPTION_ACK;
|
||||
import static org.briarproject.api.sync.PacketTypes.SUBSCRIPTION_UPDATE;
|
||||
import static org.briarproject.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.PACKET_HEADER_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||
@@ -30,13 +23,11 @@ import static org.briarproject.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||
// This class is not thread-safe
|
||||
class PacketWriterImpl implements PacketWriter {
|
||||
|
||||
private final BdfWriterFactory bdfWriterFactory;
|
||||
private final OutputStream out;
|
||||
private final byte[] header;
|
||||
private final ByteArrayOutputStream payload;
|
||||
|
||||
PacketWriterImpl(BdfWriterFactory bdfWriterFactory, OutputStream out) {
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
PacketWriterImpl(OutputStream out) {
|
||||
this.out = out;
|
||||
header = new byte[PACKET_HEADER_LENGTH];
|
||||
header[0] = PROTOCOL_VERSION;
|
||||
@@ -94,33 +85,6 @@ class PacketWriterImpl implements PacketWriter {
|
||||
writePacket(REQUEST);
|
||||
}
|
||||
|
||||
public void writeSubscriptionAck(SubscriptionAck a) throws IOException {
|
||||
if (payload.size() != 0) throw new IllegalStateException();
|
||||
BdfWriter w = bdfWriterFactory.createWriter(payload);
|
||||
w.writeListStart();
|
||||
w.writeInteger(a.getVersion());
|
||||
w.writeListEnd();
|
||||
writePacket(SUBSCRIPTION_ACK);
|
||||
}
|
||||
|
||||
public void writeSubscriptionUpdate(SubscriptionUpdate u)
|
||||
throws IOException {
|
||||
if (payload.size() != 0) throw new IllegalStateException();
|
||||
BdfWriter w = bdfWriterFactory.createWriter(payload);
|
||||
w.writeListStart();
|
||||
w.writeListStart();
|
||||
for (Group g : u.getGroups()) {
|
||||
w.writeListStart();
|
||||
w.writeRaw(g.getClientId().getBytes());
|
||||
w.writeRaw(g.getDescriptor());
|
||||
w.writeListEnd();
|
||||
}
|
||||
w.writeListEnd();
|
||||
w.writeInteger(u.getVersion());
|
||||
w.writeListEnd();
|
||||
writePacket(SUBSCRIPTION_UPDATE);
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
out.flush();
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@ import org.briarproject.api.event.ShutdownEvent;
|
||||
import org.briarproject.api.event.TransportRemovedEvent;
|
||||
import org.briarproject.api.sync.Ack;
|
||||
import org.briarproject.api.sync.PacketWriter;
|
||||
import org.briarproject.api.sync.SubscriptionAck;
|
||||
import org.briarproject.api.sync.SubscriptionUpdate;
|
||||
import org.briarproject.api.sync.SyncSession;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -66,16 +64,14 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
this.transportId = transportId;
|
||||
this.maxLatency = maxLatency;
|
||||
this.packetWriter = packetWriter;
|
||||
outstandingQueries = new AtomicInteger(4); // One per type of packet
|
||||
outstandingQueries = new AtomicInteger(2); // One per type of packet
|
||||
writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
|
||||
}
|
||||
|
||||
public void run() throws IOException {
|
||||
eventBus.addListener(this);
|
||||
try {
|
||||
// Start a query for each type of packet, in order of urgency
|
||||
dbExecutor.execute(new GenerateSubscriptionAck());
|
||||
dbExecutor.execute(new GenerateSubscriptionUpdate());
|
||||
// Start a query for each type of packet
|
||||
dbExecutor.execute(new GenerateAck());
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
// Write packets until interrupted or no more packets to write
|
||||
@@ -187,77 +183,4 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
}
|
||||
}
|
||||
|
||||
// This task runs on the database thread
|
||||
private class GenerateSubscriptionAck implements Runnable {
|
||||
|
||||
public void run() {
|
||||
if (interrupted) return;
|
||||
try {
|
||||
SubscriptionAck a = db.generateSubscriptionAck(contactId);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Generated subscription ack: " + (a != null));
|
||||
if (a == null) decrementOutstandingQueries();
|
||||
else writerTasks.add(new WriteSubscriptionAck(a));
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This tasks runs on the writer thread
|
||||
private class WriteSubscriptionAck
|
||||
implements ThrowingRunnable<IOException> {
|
||||
|
||||
private final SubscriptionAck ack;
|
||||
|
||||
private WriteSubscriptionAck(SubscriptionAck ack) {
|
||||
this.ack = ack;
|
||||
}
|
||||
|
||||
public void run() throws IOException {
|
||||
if (interrupted) return;
|
||||
packetWriter.writeSubscriptionAck(ack);
|
||||
LOG.info("Sent subscription ack");
|
||||
dbExecutor.execute(new GenerateSubscriptionAck());
|
||||
}
|
||||
}
|
||||
|
||||
// This task runs on the database thread
|
||||
private class GenerateSubscriptionUpdate implements Runnable {
|
||||
|
||||
public void run() {
|
||||
if (interrupted) return;
|
||||
try {
|
||||
SubscriptionUpdate u =
|
||||
db.generateSubscriptionUpdate(contactId, maxLatency);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Generated subscription update: " + (u != null));
|
||||
if (u == null) decrementOutstandingQueries();
|
||||
else writerTasks.add(new WriteSubscriptionUpdate(u));
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This task runs on the writer thread
|
||||
private class WriteSubscriptionUpdate
|
||||
implements ThrowingRunnable<IOException> {
|
||||
|
||||
private final SubscriptionUpdate update;
|
||||
|
||||
private WriteSubscriptionUpdate(SubscriptionUpdate update) {
|
||||
this.update = update;
|
||||
}
|
||||
|
||||
public void run() throws IOException {
|
||||
if (interrupted) return;
|
||||
packetWriter.writeSubscriptionUpdate(update);
|
||||
LOG.info("Sent subscription update");
|
||||
dbExecutor.execute(new GenerateSubscriptionUpdate());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package org.briarproject.sync;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.data.BdfReader;
|
||||
import org.briarproject.api.data.ObjectReader;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.SubscriptionUpdate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.briarproject.api.sync.SyncConstants.MAX_SUBSCRIPTIONS;
|
||||
|
||||
class SubscriptionUpdateReader implements ObjectReader<SubscriptionUpdate> {
|
||||
|
||||
private final ObjectReader<Group> groupReader;
|
||||
|
||||
SubscriptionUpdateReader(ObjectReader<Group> groupReader) {
|
||||
this.groupReader = groupReader;
|
||||
}
|
||||
|
||||
public SubscriptionUpdate readObject(BdfReader r) throws IOException {
|
||||
r.readListStart();
|
||||
List<Group> groups = new ArrayList<Group>();
|
||||
Set<GroupId> ids = new HashSet<GroupId>();
|
||||
r.readListStart();
|
||||
for (int i = 0; i < MAX_SUBSCRIPTIONS && !r.hasListEnd(); i++) {
|
||||
Group g = groupReader.readObject(r);
|
||||
if (!ids.add(g.getId())) throw new FormatException(); // Duplicate
|
||||
groups.add(g);
|
||||
}
|
||||
r.readListEnd();
|
||||
long version = r.readInteger();
|
||||
if (version < 0) throw new FormatException();
|
||||
r.readListEnd();
|
||||
groups = Collections.unmodifiableList(groups);
|
||||
return new SubscriptionUpdate(groups, version);
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,11 @@ import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.identity.AuthorFactory;
|
||||
import org.briarproject.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.GroupFactory;
|
||||
import org.briarproject.api.sync.MessageFactory;
|
||||
import org.briarproject.api.sync.PacketReaderFactory;
|
||||
import org.briarproject.api.sync.PacketWriterFactory;
|
||||
import org.briarproject.api.sync.PrivateGroupFactory;
|
||||
import org.briarproject.api.sync.SubscriptionUpdate;
|
||||
import org.briarproject.api.sync.SyncSessionFactory;
|
||||
import org.briarproject.api.sync.ValidationManager;
|
||||
|
||||
@@ -39,17 +37,6 @@ public class SyncModule extends AbstractModule {
|
||||
return new AuthorReader(authorFactory);
|
||||
}
|
||||
|
||||
@Provides
|
||||
ObjectReader<Group> getGroupReader(GroupFactory groupFactory) {
|
||||
return new GroupReader(groupFactory);
|
||||
}
|
||||
|
||||
@Provides
|
||||
ObjectReader<SubscriptionUpdate> getSubscriptionUpdateReader(
|
||||
ObjectReader<Group> groupReader) {
|
||||
return new SubscriptionUpdateReader(groupReader);
|
||||
}
|
||||
|
||||
@Provides @Singleton
|
||||
ValidationManager getValidationManager(LifecycleManager lifecycleManager,
|
||||
EventBus eventBus, ValidationManagerImpl validationManager) {
|
||||
|
||||
@@ -8,13 +8,14 @@ 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.db.NoSuchGroupException;
|
||||
import org.briarproject.api.db.NoSuchMessageException;
|
||||
import org.briarproject.api.db.NoSuchSubscriptionException;
|
||||
import org.briarproject.api.event.Event;
|
||||
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,19 +149,18 @@ 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);
|
||||
} catch (NoSuchSubscriptionException e) {
|
||||
validateMessage(m, db.getGroup(m.getGroupId()));
|
||||
} catch (NoSuchGroupException e) {
|
||||
LOG.info("Group removed before validation");
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -19,7 +19,6 @@ import org.briarproject.api.sync.PacketReaderFactory;
|
||||
import org.briarproject.api.sync.PacketWriter;
|
||||
import org.briarproject.api.sync.PacketWriterFactory;
|
||||
import org.briarproject.api.sync.Request;
|
||||
import org.briarproject.api.sync.SubscriptionUpdate;
|
||||
import org.briarproject.api.transport.StreamContext;
|
||||
import org.briarproject.api.transport.StreamReaderFactory;
|
||||
import org.briarproject.api.transport.StreamWriterFactory;
|
||||
@@ -37,7 +36,6 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.briarproject.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
||||
@@ -56,7 +54,6 @@ public class ProtocolIntegrationTest extends BriarTestCase {
|
||||
private final ContactId contactId;
|
||||
private final TransportId transportId;
|
||||
private final SecretKey tagKey, headerKey;
|
||||
private final Group group;
|
||||
private final Message message, message1;
|
||||
private final Collection<MessageId> messageIds;
|
||||
|
||||
@@ -79,7 +76,7 @@ public class ProtocolIntegrationTest extends BriarTestCase {
|
||||
GroupFactory groupFactory = i.getInstance(GroupFactory.class);
|
||||
ClientId clientId = new ClientId(TestUtils.getRandomId());
|
||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
||||
group = groupFactory.createGroup(clientId, descriptor);
|
||||
Group group = groupFactory.createGroup(clientId, descriptor);
|
||||
// Add two messages to the group
|
||||
MessageFactory messageFactory = i.getInstance(MessageFactory.class);
|
||||
long timestamp = System.currentTimeMillis();
|
||||
@@ -114,10 +111,6 @@ public class ProtocolIntegrationTest extends BriarTestCase {
|
||||
|
||||
packetWriter.writeRequest(new Request(messageIds));
|
||||
|
||||
SubscriptionUpdate su = new SubscriptionUpdate(
|
||||
Collections.singletonList(group), 1);
|
||||
packetWriter.writeSubscriptionUpdate(su);
|
||||
|
||||
streamWriter.flush();
|
||||
return out.toByteArray();
|
||||
}
|
||||
@@ -158,12 +151,6 @@ public class ProtocolIntegrationTest extends BriarTestCase {
|
||||
Request req = packetReader.readRequest();
|
||||
assertEquals(messageIds, req.getMessageIds());
|
||||
|
||||
// Read the subscription update
|
||||
assertTrue(packetReader.hasSubscriptionUpdate());
|
||||
SubscriptionUpdate su = packetReader.readSubscriptionUpdate();
|
||||
assertEquals(Collections.singletonList(group), su.getGroups());
|
||||
assertEquals(1, su.getVersion());
|
||||
|
||||
in.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,26 +7,26 @@ import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.db.MessageExistsException;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.db.NoSuchContactException;
|
||||
import org.briarproject.api.db.NoSuchGroupException;
|
||||
import org.briarproject.api.db.NoSuchLocalAuthorException;
|
||||
import org.briarproject.api.db.NoSuchMessageException;
|
||||
import org.briarproject.api.db.NoSuchSubscriptionException;
|
||||
import org.briarproject.api.db.NoSuchTransportException;
|
||||
import org.briarproject.api.db.StorageStatus;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent;
|
||||
import org.briarproject.api.event.GroupAddedEvent;
|
||||
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;
|
||||
import org.briarproject.api.event.MessagesAckedEvent;
|
||||
import org.briarproject.api.event.MessagesSentEvent;
|
||||
import org.briarproject.api.event.SettingsUpdatedEvent;
|
||||
import org.briarproject.api.event.SubscriptionAddedEvent;
|
||||
import org.briarproject.api.event.SubscriptionRemovedEvent;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.identity.AuthorId;
|
||||
import org.briarproject.api.identity.LocalAuthor;
|
||||
@@ -40,8 +40,6 @@ import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.Offer;
|
||||
import org.briarproject.api.sync.Request;
|
||||
import org.briarproject.api.sync.SubscriptionAck;
|
||||
import org.briarproject.api.sync.SubscriptionUpdate;
|
||||
import org.briarproject.api.transport.IncomingKeys;
|
||||
import org.briarproject.api.transport.OutgoingKeys;
|
||||
import org.briarproject.api.transport.TransportKeys;
|
||||
@@ -60,7 +58,6 @@ import static org.briarproject.api.sync.ValidationManager.Validity.VALID;
|
||||
import static org.briarproject.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
@@ -149,8 +146,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addGroup(txn, group);
|
||||
will(returnValue(true));
|
||||
oneOf(eventBus).broadcast(with(any(SubscriptionAddedEvent.class)));
|
||||
oneOf(eventBus).broadcast(with(any(GroupAddedEvent.class)));
|
||||
// addGroup() again
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
@@ -163,10 +159,9 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
oneOf(database).getVisibility(txn, groupId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(database).removeGroup(txn, groupId);
|
||||
oneOf(eventBus).broadcast(with(any(GroupRemovedEvent.class)));
|
||||
oneOf(eventBus).broadcast(with(any(
|
||||
SubscriptionRemovedEvent.class)));
|
||||
oneOf(eventBus).broadcast(with(any(
|
||||
LocalSubscriptionsUpdatedEvent.class)));
|
||||
GroupVisibilityUpdatedEvent.class)));
|
||||
// removeContact()
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
@@ -198,34 +193,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicateLocalMessagesAreNotStored() throws Exception {
|
||||
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() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).abortTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
try {
|
||||
db.addLocalMessage(message, clientId, metadata, true);
|
||||
fail();
|
||||
} catch (MessageExistsException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalMessagesAreNotStoredUnlessSubscribed()
|
||||
public void testLocalMessagesAreNotStoredUnlessGroupExists()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -235,8 +203,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).abortTransaction(txn);
|
||||
@@ -247,7 +213,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
try {
|
||||
db.addLocalMessage(message, clientId, metadata, true);
|
||||
fail();
|
||||
} catch (NoSuchSubscriptionException expected) {
|
||||
} catch (NoSuchGroupException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
@@ -264,10 +230,10 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addMessage(txn, message, VALID, true);
|
||||
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
|
||||
oneOf(database).getVisibility(txn, groupId);
|
||||
@@ -281,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);
|
||||
@@ -338,14 +305,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.generateSubscriptionAck(contactId);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.generateSubscriptionUpdate(contactId, 123);
|
||||
db.generateRequest(contactId, 123);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
@@ -379,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);
|
||||
@@ -403,17 +370,8 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
SubscriptionAck a = new SubscriptionAck(0);
|
||||
db.receiveSubscriptionAck(contactId, a);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
SubscriptionUpdate u = new SubscriptionUpdate(
|
||||
Collections.<Group>emptyList(), 1);
|
||||
db.receiveSubscriptionUpdate(contactId, u);
|
||||
Request r = new Request(Collections.singletonList(messageId));
|
||||
db.receiveRequest(contactId, r);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
@@ -433,6 +391,13 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.setVisibleToContact(contactId, groupId, true);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@@ -480,7 +445,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariousMethodsThrowExceptionIfSubscriptionIsMissing()
|
||||
public void testVariousMethodsThrowExceptionIfGroupIsMissing()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -488,14 +453,15 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the subscription is in the DB (which it's not)
|
||||
exactly(7).of(database).startTransaction();
|
||||
// Check whether the group is in the DB (which it's not)
|
||||
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,
|
||||
@@ -504,49 +470,63 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
try {
|
||||
db.getGroup(groupId);
|
||||
fail();
|
||||
} catch (NoSuchSubscriptionException expected) {
|
||||
} catch (NoSuchGroupException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.getGroupMetadata(groupId);
|
||||
fail();
|
||||
} catch (NoSuchSubscriptionException expected) {
|
||||
} catch (NoSuchGroupException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.getMessageStatus(contactId, groupId);
|
||||
fail();
|
||||
} catch (NoSuchSubscriptionException expected) {
|
||||
} catch (NoSuchGroupException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.getVisibility(groupId);
|
||||
fail();
|
||||
} catch (NoSuchSubscriptionException expected) {
|
||||
} catch (NoSuchGroupException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.isVisibleToContact(contactId, groupId);
|
||||
fail();
|
||||
} catch (NoSuchGroupException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.mergeGroupMetadata(groupId, metadata);
|
||||
fail();
|
||||
} catch (NoSuchSubscriptionException expected) {
|
||||
} catch (NoSuchGroupException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.removeGroup(group);
|
||||
fail();
|
||||
} catch (NoSuchSubscriptionException expected) {
|
||||
} catch (NoSuchGroupException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.setVisibility(groupId, Collections.<ContactId>emptyList());
|
||||
fail();
|
||||
} catch (NoSuchSubscriptionException expected) {
|
||||
} catch (NoSuchGroupException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.setVisibleToContact(contactId, groupId, true);
|
||||
fail();
|
||||
} catch (NoSuchGroupException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
@@ -856,58 +836,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateSubscriptionUpdateNoUpdateDue() throws Exception {
|
||||
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() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getSubscriptionUpdate(txn, contactId, maxLatency);
|
||||
will(returnValue(null));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
assertNull(db.generateSubscriptionUpdate(contactId, maxLatency));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateSubscriptionUpdate() throws Exception {
|
||||
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() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getSubscriptionUpdate(txn, contactId, maxLatency);
|
||||
will(returnValue(new SubscriptionUpdate(
|
||||
Collections.singletonList(group), 1)));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
SubscriptionUpdate u = db.generateSubscriptionUpdate(contactId,
|
||||
maxLatency);
|
||||
assertEquals(Collections.singletonList(group), u.getGroups());
|
||||
assertEquals(1, u.getVersion());
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveAck() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@@ -950,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);
|
||||
@@ -1103,56 +1031,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveSubscriptionAck() throws Exception {
|
||||
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() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).setSubscriptionUpdateAcked(txn, contactId, 1);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
SubscriptionAck a = new SubscriptionAck(1);
|
||||
db.receiveSubscriptionAck(contactId, a);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveSubscriptionUpdate() throws Exception {
|
||||
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() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).setGroups(txn, contactId,
|
||||
Collections.singletonList(group), 1);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
SubscriptionUpdate u = new SubscriptionUpdate(
|
||||
Collections.singletonList(group), 1);
|
||||
db.receiveSubscriptionUpdate(contactId, u);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangingVisibilityCallsListeners() throws Exception {
|
||||
final ContactId contactId1 = new ContactId(123);
|
||||
@@ -1172,10 +1050,9 @@ 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(
|
||||
LocalSubscriptionsUpdatedEvent.class)));
|
||||
GroupVisibilityUpdatedEvent.class)));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
@@ -1204,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,
|
||||
@@ -1215,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(
|
||||
LocalSubscriptionsUpdatedEvent.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(
|
||||
LocalSubscriptionsUpdatedEvent.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();
|
||||
|
||||
@@ -62,7 +62,6 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
private final Random random = new Random();
|
||||
private final ClientId clientId;
|
||||
private final GroupId groupId;
|
||||
private final Group group;
|
||||
private final Author author;
|
||||
@@ -77,8 +76,8 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
private final ContactId contactId;
|
||||
|
||||
public H2DatabaseTest() throws Exception {
|
||||
clientId = new ClientId(TestUtils.getRandomId());
|
||||
groupId = new GroupId(TestUtils.getRandomId());
|
||||
ClientId clientId = new ClientId(TestUtils.getRandomId());
|
||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
||||
group = new Group(groupId, clientId, descriptor);
|
||||
AuthorId authorId = new AuthorId(TestUtils.getRandomId());
|
||||
@@ -147,15 +146,15 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsubscribingRemovesMessage() throws Exception {
|
||||
public void testRemovingGroupRemovesMessage() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Subscribe to a group and store a message
|
||||
// Add a group and a message
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, VALID, true);
|
||||
|
||||
// Unsubscribing from the group should remove the message
|
||||
// Removing the group should remove the message
|
||||
assertTrue(db.containsMessage(txn, messageId));
|
||||
db.removeGroup(txn, groupId);
|
||||
assertFalse(db.containsMessage(txn, messageId));
|
||||
@@ -169,12 +168,11 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, subscribe to a group and store a message
|
||||
// Add a contact, a group and a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||
db.addMessage(txn, message, VALID, true);
|
||||
|
||||
// The message has no status yet, so it should not be sendable
|
||||
@@ -207,12 +205,11 @@ 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);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||
db.addMessage(txn, message, UNKNOWN, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
|
||||
@@ -246,12 +243,11 @@ 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);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||
db.addMessage(txn, message, VALID, false);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
|
||||
@@ -280,55 +276,16 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendableMessagesMustBeSubscribed() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, subscribe to a group and store a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.addMessage(txn, message, VALID, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
|
||||
// The contact is not subscribed, so the message should not be sendable
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
ONE_MEGABYTE);
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||
assertTrue(ids.isEmpty());
|
||||
|
||||
// The contact subscribing should make the message sendable
|
||||
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
|
||||
// The contact unsubscribing should make the message unsendable
|
||||
db.setGroups(txn, contactId, Collections.<Group>emptyList(), 2);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||
assertTrue(ids.isEmpty());
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendableMessagesMustFitCapacity() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, subscribe to a group and store a message
|
||||
// Add a contact, a group and a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||
db.addMessage(txn, message, VALID, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
|
||||
@@ -350,15 +307,14 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, subscribe to a group and store a message
|
||||
// Add a contact, a group and a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addGroup(txn, group);
|
||||
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||
db.addMessage(txn, message, VALID, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
|
||||
// The subscription is not visible to the contact, so the message
|
||||
// The group is not visible to the contact, so the message
|
||||
// should not be sendable
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
ONE_MEGABYTE);
|
||||
@@ -366,14 +322,14 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||
assertTrue(ids.isEmpty());
|
||||
|
||||
// Making the subscription visible should make the message sendable
|
||||
// Making the group visible should make the message sendable
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
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());
|
||||
@@ -389,11 +345,11 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact and subscribe to a group
|
||||
// Add a contact and a group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addGroup(txn, group);
|
||||
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
|
||||
// Add some messages to ack
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
@@ -425,12 +381,11 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, subscribe to a group and store a message
|
||||
// Add a contact, a group and a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||
db.addMessage(txn, message, VALID, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
|
||||
@@ -559,21 +514,20 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a transport to the database
|
||||
db.addTransport(txn, transportId, 123);
|
||||
|
||||
// Set the transport config
|
||||
// Store some settings
|
||||
Settings s = new Settings();
|
||||
s.put("foo", "foo");
|
||||
s.put("bar", "bar");
|
||||
db.mergeSettings(txn, s, "test");
|
||||
assertEquals(s, db.getSettings(txn, "test"));
|
||||
|
||||
// Update one of the properties and add another
|
||||
// Update one of the settings and add another
|
||||
Settings s1 = new Settings();
|
||||
s1.put("bar", "baz");
|
||||
s1.put("bam", "bam");
|
||||
db.mergeSettings(txn, s1, "test");
|
||||
|
||||
// Check that the settings were merged
|
||||
Settings merged = new Settings();
|
||||
merged.put("foo", "foo");
|
||||
merged.put("bar", "baz");
|
||||
@@ -590,12 +544,11 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact and subscribe to a group
|
||||
// Add a contact and a group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||
|
||||
// The message is not in the database
|
||||
assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
|
||||
@@ -605,17 +558,16 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainsVisibleMessageRequiresLocalSubscription()
|
||||
public void testContainsVisibleMessageRequiresGroupInDatabase()
|
||||
throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact with a subscription
|
||||
// Add a contact
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||
|
||||
// There's no local subscription for the group
|
||||
// The group is not in the database
|
||||
assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
@@ -623,20 +575,19 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainsVisibleMessageRequiresVisibileSubscription()
|
||||
public void testContainsVisibleMessageRequiresVisibileGroup()
|
||||
throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, subscribe to a group and store a message
|
||||
// Add a contact, a group and a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addGroup(txn, group);
|
||||
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||
db.addMessage(txn, message, VALID, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
|
||||
// The subscription is not visible
|
||||
// The group is not visible
|
||||
assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
@@ -648,7 +599,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact and subscribe to a group
|
||||
// Add a contact and a group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.addGroup(txn, group);
|
||||
@@ -670,7 +621,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleSubscriptionsAndUnsubscriptions() throws Exception {
|
||||
public void testMultipleGroupChanges() throws Exception {
|
||||
// Create some groups
|
||||
List<Group> groups = new ArrayList<Group>();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
@@ -683,7 +634,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact and subscribe to the groups
|
||||
// Add a contact and the groups
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
for (Group g : groups) db.addGroup(txn, g);
|
||||
@@ -838,57 +789,6 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAvailableGroups() throws Exception {
|
||||
ContactId contactId1 = new ContactId(2);
|
||||
AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
|
||||
Author author1 = new Author(authorId1, "Carol",
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH]);
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add two contacts who subscribe to a group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
assertEquals(contactId1, db.addContact(txn, author1, localAuthorId));
|
||||
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||
db.setGroups(txn, contactId1, Collections.singletonList(group), 1);
|
||||
|
||||
// The group should be available
|
||||
assertEquals(Collections.emptyList(), db.getGroups(txn, clientId));
|
||||
assertEquals(Collections.singletonList(group),
|
||||
db.getAvailableGroups(txn, clientId));
|
||||
|
||||
// Subscribe to the group - it should no longer be available
|
||||
db.addGroup(txn, group);
|
||||
assertEquals(Collections.singletonList(group),
|
||||
db.getGroups(txn, clientId));
|
||||
assertEquals(Collections.emptyList(),
|
||||
db.getAvailableGroups(txn, clientId));
|
||||
|
||||
// Unsubscribe from the group - it should be available again
|
||||
db.removeGroup(txn, groupId);
|
||||
assertEquals(Collections.emptyList(), db.getGroups(txn, clientId));
|
||||
assertEquals(Collections.singletonList(group),
|
||||
db.getAvailableGroups(txn, clientId));
|
||||
|
||||
// The first contact unsubscribes - it should still be available
|
||||
db.setGroups(txn, contactId, Collections.<Group>emptyList(), 2);
|
||||
assertEquals(Collections.emptyList(), db.getGroups(txn, clientId));
|
||||
assertEquals(Collections.singletonList(group),
|
||||
db.getAvailableGroups(txn, clientId));
|
||||
|
||||
// The second contact unsubscribes - it should no longer be available
|
||||
db.setGroups(txn, contactId1, Collections.<Group>emptyList(), 2);
|
||||
assertEquals(Collections.emptyList(), db.getGroups(txn, clientId));
|
||||
assertEquals(Collections.emptyList(),
|
||||
db.getAvailableGroups(txn, clientId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetContactsByLocalAuthorId() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
@@ -943,46 +843,6 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContactUnsubscribingResetsMessageStatus() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact who subscribes to a group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||
|
||||
// Subscribe to the group and make it visible to the contact
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
|
||||
// Add a message - it should be sendable to the contact
|
||||
db.addMessage(txn, message, VALID, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
Collection<MessageId> sendable = db.getMessagesToSend(txn, contactId,
|
||||
ONE_MEGABYTE);
|
||||
assertEquals(Collections.singletonList(messageId), sendable);
|
||||
|
||||
// Mark the message as seen - it should no longer be sendable
|
||||
db.raiseSeenFlag(txn, contactId, messageId);
|
||||
sendable = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||
assertEquals(Collections.emptyList(), sendable);
|
||||
|
||||
// The contact unsubscribes - the message should not be sendable
|
||||
db.setGroups(txn, contactId, Collections.<Group>emptyList(), 2);
|
||||
sendable = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||
assertEquals(Collections.emptyList(), sendable);
|
||||
|
||||
// The contact resubscribes - the message should be sendable again
|
||||
db.setGroups(txn, contactId, Collections.singletonList(group), 3);
|
||||
sendable = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||
assertEquals(Collections.singletonList(messageId), sendable);
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroupMetadata() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
@@ -1086,12 +946,11 @@ public class H2DatabaseTest extends BriarTestCase {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact who subscribes to a group
|
||||
// Add a contact
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||
|
||||
// Subscribe to the group and make it visible to the contact
|
||||
// Add a group and make it visible to the contact
|
||||
db.addGroup(txn, group);
|
||||
db.addVisibility(txn, contactId, groupId);
|
||||
|
||||
@@ -1158,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
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,6 @@ public class PluginManagerImplTest extends BriarTestCase {
|
||||
oneOf(simplexPlugin).getMaxLatency();
|
||||
will(returnValue(simplexLatency));
|
||||
oneOf(db).addTransport(simplexId, simplexLatency);
|
||||
will(returnValue(true));
|
||||
oneOf(simplexPlugin).start();
|
||||
will(returnValue(true)); // Started
|
||||
oneOf(simplexPlugin).shouldPoll();
|
||||
@@ -98,7 +97,6 @@ public class PluginManagerImplTest extends BriarTestCase {
|
||||
oneOf(simplexFailPlugin).getMaxLatency();
|
||||
will(returnValue(simplexFailLatency));
|
||||
oneOf(db).addTransport(simplexFailId, simplexFailLatency);
|
||||
will(returnValue(true));
|
||||
oneOf(simplexFailPlugin).start();
|
||||
will(returnValue(false)); // Failed to start
|
||||
// First duplex plugin
|
||||
@@ -112,7 +110,6 @@ public class PluginManagerImplTest extends BriarTestCase {
|
||||
oneOf(duplexPlugin).getMaxLatency();
|
||||
will(returnValue(duplexLatency));
|
||||
oneOf(db).addTransport(duplexId, duplexLatency);
|
||||
will(returnValue(true));
|
||||
oneOf(duplexPlugin).start();
|
||||
will(returnValue(true)); // Started
|
||||
oneOf(duplexPlugin).shouldPoll();
|
||||
|
||||
@@ -22,16 +22,12 @@ import org.briarproject.api.messaging.MessagingConstants;
|
||||
import org.briarproject.api.messaging.PrivateMessage;
|
||||
import org.briarproject.api.messaging.PrivateMessageFactory;
|
||||
import org.briarproject.api.sync.Ack;
|
||||
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.api.sync.Offer;
|
||||
import org.briarproject.api.sync.PacketWriter;
|
||||
import org.briarproject.api.sync.PacketWriterFactory;
|
||||
import org.briarproject.api.sync.Request;
|
||||
import org.briarproject.api.sync.SubscriptionUpdate;
|
||||
import org.briarproject.contact.ContactModule;
|
||||
import org.briarproject.crypto.CryptoModule;
|
||||
import org.briarproject.data.DataModule;
|
||||
@@ -52,9 +48,7 @@ import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENG
|
||||
import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MAX_SUBSCRIPTIONS;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ConstantsTest extends BriarTestCase {
|
||||
@@ -62,7 +56,6 @@ public class ConstantsTest extends BriarTestCase {
|
||||
// TODO: Break this up into tests that are relevant for each package
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final GroupFactory groupFactory;
|
||||
private final AuthorFactory authorFactory;
|
||||
private final PrivateMessageFactory privateMessageFactory;
|
||||
private final ForumPostFactory forumPostFactory;
|
||||
@@ -75,7 +68,6 @@ public class ConstantsTest extends BriarTestCase {
|
||||
new DataModule(), new EventModule(), new ForumModule(),
|
||||
new IdentityModule(), new MessagingModule(), new SyncModule());
|
||||
crypto = i.getInstance(CryptoComponent.class);
|
||||
groupFactory = i.getInstance(GroupFactory.class);
|
||||
authorFactory = i.getInstance(AuthorFactory.class);
|
||||
privateMessageFactory = i.getInstance(PrivateMessageFactory.class);
|
||||
forumPostFactory = i.getInstance(ForumPostFactory.class);
|
||||
@@ -188,27 +180,6 @@ public class ConstantsTest extends BriarTestCase {
|
||||
testMessageIdsFitIntoRequest(1000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroupsFitIntoSubscriptionUpdate() throws Exception {
|
||||
// Create the maximum number of maximum-length groups
|
||||
Random random = new Random();
|
||||
ClientId clientId = new ClientId(TestUtils.getRandomId());
|
||||
Collection<Group> groups = new ArrayList<Group>();
|
||||
for (int i = 0; i < MAX_SUBSCRIPTIONS; i++) {
|
||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
||||
random.nextBytes(descriptor);
|
||||
groups.add(groupFactory.createGroup(clientId, descriptor));
|
||||
}
|
||||
// Create a maximum-length subscription update
|
||||
SubscriptionUpdate u = new SubscriptionUpdate(groups, Long.MAX_VALUE);
|
||||
// Serialise the update
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
PacketWriter writer = packetWriterFactory.createPacketWriter(out);
|
||||
writer.writeSubscriptionUpdate(u);
|
||||
// Check the size of the serialised subscription update
|
||||
assertTrue(out.size() <= MAX_PACKET_PAYLOAD_LENGTH);
|
||||
}
|
||||
|
||||
private void testMessageIdsFitIntoAck(int length) throws Exception {
|
||||
// Create an ack with as many message IDs as possible
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(length);
|
||||
|
||||
@@ -23,7 +23,7 @@ public class PacketReaderImplTest extends BriarTestCase {
|
||||
public void testFormatExceptionIfAckIsTooLarge() throws Exception {
|
||||
byte[] b = createAck(true);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, null, null, in);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readAck();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public class PacketReaderImplTest extends BriarTestCase {
|
||||
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
|
||||
byte[] b = createAck(false);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, null, null, in);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readAck();
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public class PacketReaderImplTest extends BriarTestCase {
|
||||
public void testEmptyAck() throws Exception {
|
||||
byte[] b = createEmptyAck();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, null, null, in);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readAck();
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class PacketReaderImplTest extends BriarTestCase {
|
||||
public void testFormatExceptionIfOfferIsTooLarge() throws Exception {
|
||||
byte[] b = createOffer(true);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, null, null, in);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readOffer();
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public class PacketReaderImplTest extends BriarTestCase {
|
||||
public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception {
|
||||
byte[] b = createOffer(false);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, null, null, in);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readOffer();
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class PacketReaderImplTest extends BriarTestCase {
|
||||
public void testEmptyOffer() throws Exception {
|
||||
byte[] b = createEmptyOffer();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, null, null, in);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readOffer();
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ public class PacketReaderImplTest extends BriarTestCase {
|
||||
public void testFormatExceptionIfRequestIsTooLarge() throws Exception {
|
||||
byte[] b = createRequest(true);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, null, null, in);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readRequest();
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public class PacketReaderImplTest extends BriarTestCase {
|
||||
public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
|
||||
byte[] b = createRequest(false);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, null, null, in);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readRequest();
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ public class PacketReaderImplTest extends BriarTestCase {
|
||||
public void testEmptyRequest() throws Exception {
|
||||
byte[] b = createEmptyRequest();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, null, null, in);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readRequest();
|
||||
}
|
||||
|
||||
|
||||
@@ -52,12 +52,6 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
// Add listener
|
||||
oneOf(eventBus).addListener(session);
|
||||
// No subscription ack to send
|
||||
oneOf(db).generateSubscriptionAck(contactId);
|
||||
will(returnValue(null));
|
||||
// No subscription update to send
|
||||
oneOf(db).generateSubscriptionUpdate(contactId, maxLatency);
|
||||
will(returnValue(null));
|
||||
// No acks to send
|
||||
oneOf(packetWriter).getMaxMessagesForAck(with(any(long.class)));
|
||||
will(returnValue(MAX_MESSAGES_PER_ACK));
|
||||
@@ -86,12 +80,6 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
// Add listener
|
||||
oneOf(eventBus).addListener(session);
|
||||
// No subscription ack to send
|
||||
oneOf(db).generateSubscriptionAck(contactId);
|
||||
will(returnValue(null));
|
||||
// No subscription update to send
|
||||
oneOf(db).generateSubscriptionUpdate(contactId, maxLatency);
|
||||
will(returnValue(null));
|
||||
// One ack to send
|
||||
oneOf(packetWriter).getMaxMessagesForAck(with(any(long.class)));
|
||||
will(returnValue(MAX_MESSAGES_PER_ACK));
|
||||
|
||||
Reference in New Issue
Block a user