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:
akwizgran
2016-02-10 14:02:15 +00:00
64 changed files with 1515 additions and 2388 deletions

View File

@@ -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);

View File

@@ -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");

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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");

View File

@@ -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,

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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 {
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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();

View File

@@ -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. */

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -7,6 +7,4 @@ public interface PacketTypes {
byte MESSAGE = 1;
byte OFFER = 2;
byte REQUEST = 3;
byte SUBSCRIPTION_ACK = 6;
byte SUBSCRIPTION_UPDATE = 7;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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)

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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;
}
}
}

View File

@@ -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);

View File

@@ -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();

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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];

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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))

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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));