Compare commits

...

31 Commits

Author SHA1 Message Date
str4d
0e32139a89 Second part of BQP UI improvements: zxing viewfinder. 2016-05-20 15:01:48 +12:00
str4d
0d16dd0358 Merge branch '382-message-dependencies' into 'master'
Introduce a MessageContext class to be used by all validators

This change will allow to pass message dependencies from the client validators to the `ValidationManager`.

Please see my thoughts in #382 for more details.

See merge request !197
2016-05-20 02:49:04 +00:00
Torsten Grote
3f2b85ac0d Introduce a MessageContext class for more flexibility
This change will allow to pass message dependencies from the client
validators to the ValidationManager.
2016-05-20 02:46:37 +00:00
Torsten Grote
d0852d2265 Sorry for breaking master. Don't know how that happened :( 2016-05-19 21:33:38 -03:00
Torsten Grote
9149b82cc4 Merge branch '398-forum-sharing-status' into 'master'
Forum Sharing Status

The new activity shows who you are sharing a forum with and who shares a
forum with you. It is accessible from the overflow menu when in a forum.

![device-2016-05-13-153259](/uploads/2e1c651bc72094018dd0f97a37453d1b/device-2016-05-13-153259.png)

Closes #398

See merge request !191
2016-05-19 14:44:05 +00:00
Torsten Grote
b13bf66165 Add a new activity that shows sharing status of forum
The new activity shows who you are sharing a forum with and who shares a
forum with you. It is accessible from the overflow menu when in a forum.

Closes #398
2016-05-19 11:42:05 -03:00
Torsten Grote
8c2fd905a1 Merge branch '392-use-new-metadata-queries-for-forum-sharing' into 'master'
Handle invitations to the same forum by multiple contacts

This also uses the new metadata queries in the forum sharing client.

Please note that this is based on !184.

See merge request !188
2016-05-19 14:35:58 +00:00
Torsten Grote
38a4f73cdc Handle invitations to the same forum by multiple contacts
Closes #391
2016-05-19 11:34:28 -03:00
Torsten Grote
1a175beac9 Use new metadata queries in Forum Sharing Client
Closes #392
2016-05-19 11:34:28 -03:00
Torsten Grote
7e9ff40837 Merge branch '378-replace-bdf-with-classes-for-forum-sharing-client' into 'master'
Replace BDF data structures with classes in forum sharing client

This introduces two new classes for protocol session states: One for the sharer and one for the invitee.
The respective classes for protocol state machines and actions have been moved into these classes as inner classes.
The two new classes replace the `BdfDictionary` that was used before to represent the local state information of a forum sharing session.

A similar technique is used for local actions and protocol messages.

Local actions are just represented by one Enum and protocol messages have their own classes now that also handle serialization into BdfDictionaries and BdfLists.

Closes #378 

See merge request !184
2016-05-19 14:32:41 +00:00
Torsten Grote
bd01c3732e Also use dedicated classes to represent messages instead of BdfDictionary 2016-05-19 11:32:09 -03:00
Torsten Grote
9532a60f43 Use dedicated Enum for protocol Actions 2016-05-19 11:30:23 -03:00
Torsten Grote
d2722eed92 Turn local session state into its own class instead of BdfDictionary 2016-05-19 11:30:19 -03:00
Torsten Grote
886c8feb34 Merge branch '395-convert-createidentityactivity-to-xml' into 'master'
Turn the UI of `CreateIdentityActivity` into XML

Closes #395

See merge request !194
2016-05-19 14:24:35 +00:00
str4d
c7f73f9247 Merge branch '399-contact-list-unread-messages' into 'master'
Contact List Unread Messages

Adds unread count as bubble to avatar image in contact list.

Please note that this is based on !190.

![device-2016-05-18-124534](/uploads/647dbdcff73a69eb6cf1814f85cbffe6/device-2016-05-18-124534.png)![device-2016-05-18-124339](/uploads/fa56a44f229234bb86fdebf8f9060927/device-2016-05-18-124339.png)

Closes #399

See merge request !192
2016-05-19 03:45:13 +00:00
str4d
35156d698f Merge branch '337-avatars' into 'master'
Increase Avatar border and change shadow color

![device-2016-05-16-123230](/uploads/8000fb506ee91e61149f838b3f661b46/device-2016-05-16-123230.png)
![device-2016-05-16-123251](/uploads/723bf452704f18b519c3fbdc469595a7/device-2016-05-16-123251.png)

See merge request !190
2016-05-19 03:38:09 +00:00
str4d
1366972449 Merge branch '396-convert-expiredactivity-to-xml' into 'master'
Convert ExpiredActivity to XML

Closes #396

See merge request !196
2016-05-19 03:31:06 +00:00
Torsten Grote
3d25c41e7a Add unread count as bubble to avatar image in contact list
Closes #399
2016-05-18 12:46:38 -03:00
Torsten Grote
ba928875df Merge branch '375-extract-forumfactory-from-forummanager' into 'master'
Extract ForumFactory from ForumManager

The code for creating forums in ForumManager was used by
ForumSharingManager and also needed by InviteeEngine.
This extracts it into its own class.

Closes #375

See merge request !195
2016-05-18 14:59:29 +00:00
Torsten Grote
cac0f30816 Convert ExpiredActivity to XML
Closes #396
2016-05-16 17:08:48 -03:00
Torsten Grote
aad9f5142b Extract ForumFactory from ForumManager
The code for creating forums in ForumManager was used by
ForumSharingManager and also needed by InviteeEngine.
This extracts it into its own class.

Closes #375
2016-05-16 16:56:44 -03:00
Torsten Grote
9d686e16e0 Increase Avatar border and change shadow color 2016-05-16 12:36:59 -03:00
Torsten Grote
2880043c07 Turn the UI of CreateIdentityActivity into XML
Closes #395
2016-05-16 12:19:47 -03:00
Torsten Grote
fb85345392 Merge branch '318-conversation-avatar-animation' into 'master'
Disable Conversation Exit Transition and Animate Bulb

Works-around #318

See merge request !193
2016-05-16 13:22:04 +00:00
Torsten Grote
0b67ec9201 Merge branch '372-clean-up-introduction-session-states-when-removing-contact' into 'master'
Clean up Introduction Session States

...for introducer when both introducees have been deleted.

It can't be deleted when only one introducee is removed, because then all messages in the private conversation with the other introducee will disappear, because their corresponding session state can't be found anymore.

Closes #372

See merge request !189
2016-05-16 13:18:57 +00:00
Ernir Erlingsson
fcf21b7ed7 Merge branch 'gradle-upgrade' into 'master'
Upgrade Gradle to 2.13, add local Maven repo



See merge request !186
2016-05-14 19:22:42 +00:00
Torsten Grote
6e545d0100 disable exit transition until we have a fix for it 2016-05-13 17:25:14 -03:00
str4d
69026054cd Add bulb to scene transition, make transitionName unique for each contact 2016-05-13 16:51:24 -03:00
Torsten Grote
db4b79fcae Clean up Introduction Session States
for introducer when both introducees have been deleted.

Closes #372
2016-05-12 18:21:18 -03:00
akwizgran
7412ca59ac Looks like we don't need Maven Central. 2016-05-12 14:25:00 +01:00
akwizgran
ec083d617e Upgraded Gradle to 2.13, added local Maven repo. 2016-05-12 13:36:34 +01:00
83 changed files with 2554 additions and 1004 deletions

View File

@@ -2,10 +2,6 @@ apply plugin: 'com.android.library'
apply plugin: 'witness' apply plugin: 'witness'
apply plugin: 'com.neenbedankt.android-apt' apply plugin: 'com.neenbedankt.android-apt'
repositories {
maven { url 'http://repo1.maven.org/maven2' }
}
android { android {
compileSdkVersion 23 compileSdkVersion 23
buildToolsVersion "23.0.3" buildToolsVersion "23.0.3"

View File

@@ -49,6 +49,7 @@ import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -65,15 +66,15 @@ import static org.junit.Assert.assertTrue;
public class ForumSharingIntegrationTest extends BriarTestCase { public class ForumSharingIntegrationTest extends BriarTestCase {
LifecycleManager lifecycleManager0, lifecycleManager1; LifecycleManager lifecycleManager0, lifecycleManager1, lifecycleManager2;
SyncSessionFactory sync0, sync1; SyncSessionFactory sync0, sync1, sync2;
ForumManager forumManager0, forumManager1; ForumManager forumManager0, forumManager1, forumManager2;
ContactManager contactManager0, contactManager1; ContactManager contactManager0, contactManager1, contactManager2;
ContactId contactId0, contactId1; ContactId contactId0, contactId2, contactId1, contactId21;
IdentityManager identityManager0, identityManager1; IdentityManager identityManager0, identityManager1, identityManager2;
LocalAuthor author0, author1; LocalAuthor author0, author1, author2;
Forum forum0; Forum forum0;
SharerListener listener0; SharerListener listener0, listener2;
InviteeListener listener1; InviteeListener listener1;
@Inject @Inject
@@ -84,6 +85,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
// objects accessed from background threads need to be volatile // objects accessed from background threads need to be volatile
private volatile ForumSharingManager forumSharingManager0; private volatile ForumSharingManager forumSharingManager0;
private volatile ForumSharingManager forumSharingManager1; private volatile ForumSharingManager forumSharingManager1;
private volatile ForumSharingManager forumSharingManager2;
private volatile Waiter eventWaiter; private volatile Waiter eventWaiter;
private volatile Waiter msgWaiter; private volatile Waiter msgWaiter;
@@ -92,11 +94,12 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
private final int TIMEOUT = 15000; private final int TIMEOUT = 15000;
private final String SHARER = "Sharer"; private final String SHARER = "Sharer";
private final String INVITEE = "Invitee"; private final String INVITEE = "Invitee";
private final String SHARER2 = "Sharer2";
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(ForumSharingIntegrationTest.class.getName()); Logger.getLogger(ForumSharingIntegrationTest.class.getName());
private ForumSharingIntegrationTestComponent t0, t1; private ForumSharingIntegrationTestComponent t0, t1, t2;
@Rule @Rule
public ExpectedException thrown=ExpectedException.none(); public ExpectedException thrown=ExpectedException.none();
@@ -117,17 +120,26 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
t1 = DaggerForumSharingIntegrationTestComponent.builder() t1 = DaggerForumSharingIntegrationTestComponent.builder()
.testDatabaseModule(new TestDatabaseModule(t1Dir)).build(); .testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
injectEagerSingletons(t1); injectEagerSingletons(t1);
File t2Dir = new File(testDir, SHARER2);
t2 = DaggerForumSharingIntegrationTestComponent.builder()
.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
injectEagerSingletons(t2);
identityManager0 = t0.getIdentityManager(); identityManager0 = t0.getIdentityManager();
identityManager1 = t1.getIdentityManager(); identityManager1 = t1.getIdentityManager();
identityManager2 = t2.getIdentityManager();
contactManager0 = t0.getContactManager(); contactManager0 = t0.getContactManager();
contactManager1 = t1.getContactManager(); contactManager1 = t1.getContactManager();
contactManager2 = t2.getContactManager();
forumManager0 = t0.getForumManager(); forumManager0 = t0.getForumManager();
forumManager1 = t1.getForumManager(); forumManager1 = t1.getForumManager();
forumManager2 = t2.getForumManager();
forumSharingManager0 = t0.getForumSharingManager(); forumSharingManager0 = t0.getForumSharingManager();
forumSharingManager1 = t1.getForumSharingManager(); forumSharingManager1 = t1.getForumSharingManager();
forumSharingManager2 = t2.getForumSharingManager();
sync0 = t0.getSyncSessionFactory(); sync0 = t0.getSyncSessionFactory();
sync1 = t1.getSyncSessionFactory(); sync1 = t1.getSyncSessionFactory();
sync2 = t2.getSyncSessionFactory();
// initialize waiters fresh for each test // initialize waiters fresh for each test
eventWaiter = new Waiter(); eventWaiter = new Waiter();
@@ -261,8 +273,9 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
assertTrue(forumManager1.getForums().contains(forum0)); assertTrue(forumManager1.getForums().contains(forum0));
// sharer shares forum with invitee // sharer shares forum with invitee
Contact c1 = contactManager0.getContact(contactId1);
assertTrue(forumSharingManager0.getSharedWith(forum0.getId()) assertTrue(forumSharingManager0.getSharedWith(forum0.getId())
.contains(contactId1)); .contains(c1));
// invitee gets forum shared by sharer // invitee gets forum shared by sharer
Contact contact0 = contactManager1.getContact(contactId1); Contact contact0 = contactManager1.getContact(contactId1);
assertTrue(forumSharingManager1.getSharedBy(forum0.getId()) assertTrue(forumSharingManager1.getSharedBy(forum0.getId())
@@ -280,12 +293,11 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
// sharer no longer shares forum with invitee // sharer no longer shares forum with invitee
assertFalse(forumSharingManager0.getSharedWith(forum0.getId()) assertFalse(forumSharingManager0.getSharedWith(forum0.getId())
.contains(contactId1)); .contains(c1));
// invitee no longer gets forum shared by sharer // invitee no longer gets forum shared by sharer
assertFalse(forumSharingManager1.getSharedBy(forum0.getId()) assertFalse(forumSharingManager1.getSharedBy(forum0.getId())
.contains(contact0)); .contains(contact0));
// forum can be shared again // forum can be shared again
Contact c1 = contactManager0.getContact(contactId1);
assertTrue(forumSharingManager0.canBeShared(forum0.getId(), c1)); assertTrue(forumSharingManager0.canBeShared(forum0.getId(), c1));
Contact c0 = contactManager1.getContact(contactId0); Contact c0 = contactManager1.getContact(contactId0);
assertTrue(forumSharingManager1.canBeShared(forum0.getId(), c0)); assertTrue(forumSharingManager1.canBeShared(forum0.getId(), c0));
@@ -321,8 +333,9 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
assertTrue(forumManager1.getForums().contains(forum0)); assertTrue(forumManager1.getForums().contains(forum0));
// sharer shares forum with invitee // sharer shares forum with invitee
Contact c1 = contactManager0.getContact(contactId1);
assertTrue(forumSharingManager0.getSharedWith(forum0.getId()) assertTrue(forumSharingManager0.getSharedWith(forum0.getId())
.contains(contactId1)); .contains(c1));
// invitee gets forum shared by sharer // invitee gets forum shared by sharer
Contact contact0 = contactManager1.getContact(contactId1); Contact contact0 = contactManager1.getContact(contactId1);
assertTrue(forumSharingManager1.getSharedBy(forum0.getId()) assertTrue(forumSharingManager1.getSharedBy(forum0.getId())
@@ -339,13 +352,13 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
assertEquals(1, forumManager1.getForums().size()); assertEquals(1, forumManager1.getForums().size());
// invitee no longer shares forum with sharer // invitee no longer shares forum with sharer
Contact c0 = contactManager1.getContact(contactId0);
assertFalse(forumSharingManager1.getSharedWith(forum0.getId()) assertFalse(forumSharingManager1.getSharedWith(forum0.getId())
.contains(contactId0)); .contains(c0));
// sharer no longer gets forum shared by invitee // sharer no longer gets forum shared by invitee
assertFalse(forumSharingManager1.getSharedBy(forum0.getId()) assertFalse(forumSharingManager1.getSharedBy(forum0.getId())
.contains(contact0)); .contains(contact0));
// forum can be shared again // forum can be shared again
Contact c0 = contactManager1.getContact(contactId0);
assertTrue(forumSharingManager1.canBeShared(forum0.getId(), c0)); assertTrue(forumSharingManager1.canBeShared(forum0.getId(), c0));
} finally { } finally {
stopLifecycles(); stopLifecycles();
@@ -474,7 +487,8 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
assertEquals(1, forumManager1.getForums().size()); assertEquals(1, forumManager1.getForums().size());
// invitee now shares same forum back // invitee now shares same forum back
forumSharingManager1.sendForumInvitation(forum0.getId(), contactId0, forumSharingManager1.sendForumInvitation(forum0.getId(),
contactId0,
"I am re-sharing this forum with you."); "I am re-sharing this forum with you.");
// sync re-share invitation // sync re-share invitation
@@ -526,7 +540,9 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
// contacts now remove each other // contacts now remove each other
contactManager0.removeContact(contactId1); contactManager0.removeContact(contactId1);
contactManager2.removeContact(contactId21);
contactManager1.removeContact(contactId0); contactManager1.removeContact(contactId0);
contactManager1.removeContact(contactId2);
// make sure sharer does share the forum with nobody now // make sure sharer does share the forum with nobody now
assertEquals(0, assertEquals(0,
@@ -572,6 +588,75 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
} }
} }
@Test
public void testTwoContactsShareSameForum() throws Exception {
startLifecycles();
try {
// initialize
addDefaultIdentities();
addDefaultContacts();
addForumForSharer();
// second sharer adds the same forum
DatabaseComponent db2 = t2.getDatabaseComponent();
Transaction txn = db2.startTransaction(false);
db2.addGroup(txn, forum0.getGroup());
txn.setComplete();
db2.endTransaction(txn);
// add listeners
listener0 = new SharerListener();
t0.getEventBus().addListener(listener0);
listener1 = new InviteeListener(true, false);
t1.getEventBus().addListener(listener1);
listener2 = new SharerListener();
t2.getEventBus().addListener(listener2);
// send invitation
forumSharingManager0
.sendForumInvitation(forum0.getId(), contactId1, "Hi!");
// sync first request message
syncToInvitee();
// second sharer sends invitation for same forum
forumSharingManager2
.sendForumInvitation(forum0.getId(), contactId1, null);
// sync second request message
deliverMessage(sync2, contactId2, sync1, contactId1,
"Sharer2 to Invitee");
// make sure we have only one forum available
Collection<Forum> forums =
forumSharingManager1.getAvailableForums();
assertEquals(1, forums.size());
// make sure both sharers actually share the forum
Collection<Contact> contacts =
forumSharingManager1.getSharedBy(forum0.getId());
assertEquals(2, contacts.size());
// answer second request
Contact c2 = contactManager1.getContact(contactId2);
forumSharingManager1.respondToInvitation(forum0, c2, true);
// sync response
deliverMessage(sync1, contactId21, sync2, contactId2,
"Invitee to Sharer2");
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener2.responseReceived);
// answer first request
Contact c0 =
contactManager1.getContact(contactId0);
forumSharingManager1.respondToInvitation(forum0, c0, true);
// sync response
syncToSharer();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
} finally {
stopLifecycles();
}
}
@After @After
public void tearDown() throws InterruptedException { public void tearDown() throws InterruptedException {
@@ -609,7 +694,8 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
requestReceived = true; requestReceived = true;
Forum f = event.getForum(); Forum f = event.getForum();
try { try {
forumSharingManager0.respondToInvitation(f, true); Contact c = contactManager0.getContact(contactId1);
forumSharingManager0.respondToInvitation(f, c, true);
} catch (DbException ex) { } catch (DbException ex) {
eventWaiter.rethrow(ex); eventWaiter.rethrow(ex);
} finally { } finally {
@@ -624,10 +710,15 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
public volatile boolean requestReceived = false; public volatile boolean requestReceived = false;
public volatile boolean responseReceived = false; public volatile boolean responseReceived = false;
private final boolean accept; private final boolean accept, answer;
InviteeListener(boolean accept, boolean answer) {
this.accept = accept;
this.answer = answer;
}
InviteeListener(boolean accept) { InviteeListener(boolean accept) {
this.accept = accept; this(accept, true);
} }
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
@@ -644,13 +735,13 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
} else if (e instanceof ForumInvitationReceivedEvent) { } else if (e instanceof ForumInvitationReceivedEvent) {
ForumInvitationReceivedEvent event = ForumInvitationReceivedEvent event =
(ForumInvitationReceivedEvent) e; (ForumInvitationReceivedEvent) e;
eventWaiter.assertEquals(contactId0, event.getContactId());
requestReceived = true; requestReceived = true;
if (!answer) return;
Forum f = event.getForum(); Forum f = event.getForum();
// work-around because the forum does not contain the group
f = forumManager1.createForum(f.getName(), f.getSalt());
try { try {
forumSharingManager1.respondToInvitation(f, accept); Contact c =
contactManager1.getContact(event.getContactId());
forumSharingManager1.respondToInvitation(f, c, accept);
} catch (DbException ex) { } catch (DbException ex) {
eventWaiter.rethrow(ex); eventWaiter.rethrow(ex);
} finally { } finally {
@@ -672,18 +763,23 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
// Start the lifecycle manager and wait for it to finish // Start the lifecycle manager and wait for it to finish
lifecycleManager0 = t0.getLifecycleManager(); lifecycleManager0 = t0.getLifecycleManager();
lifecycleManager1 = t1.getLifecycleManager(); lifecycleManager1 = t1.getLifecycleManager();
lifecycleManager2 = t2.getLifecycleManager();
lifecycleManager0.startServices(); lifecycleManager0.startServices();
lifecycleManager1.startServices(); lifecycleManager1.startServices();
lifecycleManager2.startServices();
lifecycleManager0.waitForStartup(); lifecycleManager0.waitForStartup();
lifecycleManager1.waitForStartup(); lifecycleManager1.waitForStartup();
lifecycleManager2.waitForStartup();
} }
private void stopLifecycles() throws InterruptedException { private void stopLifecycles() throws InterruptedException {
// Clean up // Clean up
lifecycleManager0.stopServices(); lifecycleManager0.stopServices();
lifecycleManager1.stopServices(); lifecycleManager1.stopServices();
lifecycleManager2.stopServices();
lifecycleManager0.waitForShutdown(); lifecycleManager0.waitForShutdown();
lifecycleManager1.waitForShutdown(); lifecycleManager1.waitForShutdown();
lifecycleManager2.waitForShutdown();
} }
private void defaultInit(boolean accept) throws DbException { private void defaultInit(boolean accept) throws DbException {
@@ -702,6 +798,10 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH), TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
TestUtils.getRandomBytes(123)); TestUtils.getRandomBytes(123));
identityManager1.addLocalAuthor(author1); identityManager1.addLocalAuthor(author1);
author2 = authorFactory.createLocalAuthor(SHARER2,
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
TestUtils.getRandomBytes(123));
identityManager2.addLocalAuthor(author2);
} }
private void addDefaultContacts() throws DbException { private void addDefaultContacts() throws DbException {
@@ -710,17 +810,25 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
author0.getId(), master, clock.currentTimeMillis(), true, author0.getId(), master, clock.currentTimeMillis(), true,
true true
); );
// invitee adds sharer back // second sharer does the same
contactId21 = contactManager2.addContact(author1,
author2.getId(), master, clock.currentTimeMillis(), true,
true
);
// invitee adds sharers back
contactId0 = contactManager1.addContact(author0, contactId0 = contactManager1.addContact(author0,
author1.getId(), master, clock.currentTimeMillis(), true, author1.getId(), master, clock.currentTimeMillis(), true,
true true
); );
contactId2 = contactManager1.addContact(author2,
author1.getId(), master, clock.currentTimeMillis(), true,
true
);
} }
private void addForumForSharer() throws DbException { private void addForumForSharer() throws DbException {
// sharer creates forum // sharer creates forum
forum0 = forumManager0.createForum("Test Forum"); forum0 = forumManager0.addForum("Test Forum");
forumManager0.addForum(forum0);
} }
private void listenToEvents(boolean accept) { private void listenToEvents(boolean accept) {
@@ -728,6 +836,8 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
t0.getEventBus().addListener(listener0); t0.getEventBus().addListener(listener0);
listener1 = new InviteeListener(accept); listener1 = new InviteeListener(accept);
t1.getEventBus().addListener(listener1); t1.getEventBus().addListener(listener1);
listener2 = new SharerListener();
t2.getEventBus().addListener(listener2);
} }
private void syncToInvitee() throws IOException, TimeoutException { private void syncToInvitee() throws IOException, TimeoutException {

View File

@@ -832,6 +832,106 @@ public class IntroductionIntegrationTest extends BriarTestCase {
} }
} }
@Test
public void testIntroduceesRemovedCleanup() throws Exception {
startLifecycles();
try {
// Add Identities
addDefaultIdentities();
// Add Transport Properties
addTransportProperties();
// Add introducees as contacts
contactId1 = contactManager0.addContact(author1,
author0.getId(), master, clock.currentTimeMillis(), true,
true
);
contactId2 = contactManager0.addContact(author2,
author0.getId(), master, clock.currentTimeMillis(), true,
true
);
// Add introducer back
contactId0 = contactManager1.addContact(author0,
author1.getId(), master, clock.currentTimeMillis(), true,
true
);
ContactId contactId02 = contactManager2.addContact(author0,
author2.getId(), master, clock.currentTimeMillis(), true,
true
);
assertTrue(contactId0.equals(contactId02));
// listen to events
IntroducerListener listener0 = new IntroducerListener();
t0.getEventBus().addListener(listener0);
IntroduceeListener listener1 = new IntroduceeListener(1, true);
t1.getEventBus().addListener(listener1);
IntroduceeListener listener2 = new IntroduceeListener(2, true);
t2.getEventBus().addListener(listener2);
// make introduction
long time = clock.currentTimeMillis();
Contact introducee1 = contactManager0.getContact(contactId1);
Contact introducee2 = contactManager0.getContact(contactId2);
introductionManager0
.makeIntroduction(introducee1, introducee2, "Hi!", time);
// sync first request message
deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// get database and local group for introducee
DatabaseComponent db0 = t0.getDatabaseComponent();
IntroductionGroupFactory groupFactory0 =
t0.getIntroductionGroupFactory();
Group group1 = groupFactory0.createLocalGroup();
// get local session state messages
Map<MessageId, Metadata> map;
Transaction txn = db0.startTransaction(false);
try {
map = db0.getMessageMetadata(txn, group1.getId());
txn.setComplete();
} finally {
db0.endTransaction(txn);
}
// check that we have one session state
assertEquals(1, map.size());
// introducer removes introducee1
contactManager0.removeContact(contactId1);
// get local session state messages again
txn = db0.startTransaction(false);
try {
map = db0.getMessageMetadata(txn, group1.getId());
txn.setComplete();
} finally {
db0.endTransaction(txn);
}
// make sure local state is still there
assertEquals(1, map.size());
// introducer removes other introducee
contactManager0.removeContact(contactId2);
// get local session state messages again
txn = db0.startTransaction(false);
try {
map = db0.getMessageMetadata(txn, group1.getId());
txn.setComplete();
} finally {
db0.endTransaction(txn);
}
// make sure local state is gone now
assertEquals(0, map.size());
} finally {
stopLifecycles();
}
}
// TODO add a test for faking responses when #256 is implemented // TODO add a test for faking responses when #256 is implemented
@After @After

View File

@@ -149,6 +149,16 @@
/> />
</activity> </activity>
<activity
android:name=".android.forum.ForumSharingStatusActivity"
android:label="@string/forum_sharing_status"
android:parentActivityName=".android.forum.ForumActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.forum.ForumActivity"
/>
</activity>
<activity <activity
android:name=".android.forum.WriteForumPostActivity" android:name=".android.forum.WriteForumPostActivity"
android:label="@string/app_name" android:label="@string/app_name"

View File

@@ -6,11 +6,6 @@ apply plugin: 'witness'
apply plugin: 'com.neenbedankt.android-apt' apply plugin: 'com.neenbedankt.android-apt'
apply plugin: 'de.undercouch.download' apply plugin: 'de.undercouch.download'
repositories {
jcenter()
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
}
dependencies { dependencies {
def supportVersion = '23.2.1' def supportVersion = '23.2.1'
compile project(':briar-api') compile project(':briar-api')

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:radius="@dimen/unread_bubble_size"/>
<padding
android:left="@dimen/unread_bubble_padding_horizontal"
android:right="@dimen/unread_bubble_padding_horizontal"/>
<solid
android:color="@color/briar_primary"/>
<stroke
android:color="@color/briar_text_primary_inverse"
android:width="@dimen/avatar_border_width"/>
</shape>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="@dimen/margin_activity_horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/choose_nickname"
android:textSize="@dimen/text_size_large"/>
<android.support.design.widget.TextInputLayout
android:id="@+id/nicknameInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true">
<EditText
android:id="@+id/nicknameEntry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:ems="10"
android:inputType="textPersonName"
android:maxLines="1"/>
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/createIdentityButton"
style="@style/BriarButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:enabled="false"
android:text="@string/create_identity_button"/>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:indeterminate="true"
android:visibility="gone"/>
</LinearLayout>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:text="@string/expiry_warning"
android:textSize="@dimen/text_size_large"/>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/default_separator_inverted"
android:padding="@dimen/margin_medium"
android:text="@string/forum_shared_by"
android:textSize="@dimen/text_size_large"/>
<View style="@style/Divider.ForumList"/>
<org.briarproject.android.util.BriarRecyclerView
android:id="@+id/sharedByView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/margin_medium"
android:paddingTop="@dimen/margin_medium"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/default_separator_inverted"
android:padding="@dimen/margin_medium"
android:text="@string/forum_shared_with"
android:textSize="@dimen/text_size_large"/>
<View style="@style/Divider.ForumList"/>
<org.briarproject.android.util.BriarRecyclerView
android:id="@+id/sharedWithView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/margin_medium"
android:paddingTop="@dimen/margin_medium"/>
</LinearLayout>
</ScrollView>

View File

@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout <RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<de.hdodenhof.circleimageview.CircleImageView <de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarView" android:id="@+id/avatarView"
style="@style/BriarAvatar"
android:layout_width="@dimen/listitem_picture_size" android:layout_width="@dimen/listitem_picture_size"
android:layout_height="@dimen/listitem_picture_size" android:layout_height="@dimen/listitem_picture_size"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginLeft="@dimen/listitem_horizontal_margin" android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin" android:layout_marginStart="@dimen/listitem_horizontal_margin"
app:civ_border_width="@dimen/avatar_border_width" tools:src="@drawable/ic_launcher"/>
app:civ_border_color="@color/briar_primary"/>
<ImageView <ImageView
android:id="@+id/statusView" android:id="@+id/statusView"

View File

@@ -9,11 +9,10 @@
<de.hdodenhof.circleimageview.CircleImageView <de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/contactAvatar" android:id="@+id/contactAvatar"
style="@style/BriarAvatar"
android:layout_width="30dp" android:layout_width="30dp"
android:layout_height="30dp" android:layout_height="30dp"
android:transitionName="avatar"
app:civ_border_color="@color/action_bar_text" app:civ_border_color="@color/action_bar_text"
app:civ_border_width="@dimen/avatar_border_width"
tools:src="@drawable/ic_launcher"/> tools:src="@drawable/ic_launcher"/>
<ImageView <ImageView
@@ -22,7 +21,7 @@
android:layout_height="15dp" android:layout_height="15dp"
android:layout_gravity="bottom|right" android:layout_gravity="bottom|right"
android:scaleType="fitCenter" android:scaleType="fitCenter"
tools:src="@drawable/contact_online" tools:ignore="ContentDescription"
tools:ignore="ContentDescription"/> tools:src="@drawable/contact_online"/>
</FrameLayout> </FrameLayout>

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@@ -9,11 +8,11 @@
<de.hdodenhof.circleimageview.CircleImageView <de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarView" android:id="@+id/avatarView"
style="@style/BriarAvatar"
android:layout_width="@dimen/dropdown_picture_size" android:layout_width="@dimen/dropdown_picture_size"
android:layout_height="@dimen/dropdown_picture_size" android:layout_height="@dimen/dropdown_picture_size"
android:layout_margin="@dimen/margin_small" android:layout_margin="@dimen/margin_small"
app:civ_border_color="@color/briar_primary" tools:src="@drawable/ic_launcher"/>
app:civ_border_width="@dimen/avatar_border_width"/>
<TextView <TextView
android:id="@+id/nameView" android:id="@+id/nameView"

View File

@@ -10,6 +10,11 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="match_parent"/>
<org.briarproject.android.util.ViewfinderView
android:id="@+id/viewfinder_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"

View File

@@ -24,6 +24,7 @@
<de.hdodenhof.circleimageview.CircleImageView <de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarContact1" android:id="@+id/avatarContact1"
style="@style/BriarAvatar"
android:layout_width="@dimen/listitem_picture_size" android:layout_width="@dimen/listitem_picture_size"
android:layout_height="@dimen/listitem_picture_size" android:layout_height="@dimen/listitem_picture_size"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
@@ -31,8 +32,6 @@
android:layout_marginRight="@dimen/listitem_horizontal_margin" android:layout_marginRight="@dimen/listitem_horizontal_margin"
android:layout_toLeftOf="@+id/introductionIcon" android:layout_toLeftOf="@+id/introductionIcon"
android:layout_toStartOf="@+id/introductionIcon" android:layout_toStartOf="@+id/introductionIcon"
app:civ_border_color="@color/briar_primary"
app:civ_border_width="@dimen/avatar_border_width"
tools:src="@drawable/ic_launcher"/> tools:src="@drawable/ic_launcher"/>
<ImageView <ImageView
@@ -45,6 +44,7 @@
<de.hdodenhof.circleimageview.CircleImageView <de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarContact2" android:id="@+id/avatarContact2"
style="@style/BriarAvatar"
android:layout_width="@dimen/listitem_picture_size" android:layout_width="@dimen/listitem_picture_size"
android:layout_height="@dimen/listitem_picture_size" android:layout_height="@dimen/listitem_picture_size"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
@@ -53,8 +53,6 @@
android:layout_toEndOf="@+id/introductionIcon" android:layout_toEndOf="@+id/introductionIcon"
android:layout_toRightOf="@+id/introductionIcon" android:layout_toRightOf="@+id/introductionIcon"
android:transitionName="avatar" android:transitionName="avatar"
app:civ_border_color="@color/briar_primary"
app:civ_border_width="@dimen/avatar_border_width"
tools:src="@drawable/ic_launcher"/> tools:src="@drawable/ic_launcher"/>
</RelativeLayout> </RelativeLayout>

View File

@@ -10,36 +10,55 @@
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="@dimen/listitem_horizontal_margin"
android:paddingBottom="@dimen/listitem_horizontal_margin"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:paddingBottom="@dimen/listitem_horizontal_margin"
android:paddingTop="@dimen/listitem_horizontal_margin"
> >
<de.hdodenhof.circleimageview.CircleImageView <FrameLayout
android:id="@+id/avatarView" android:id="@+id/avatarFrameView"
android:layout_width="@dimen/listitem_picture_size" android:layout_width="@dimen/listitem_picture_frame_size"
android:layout_height="@dimen/listitem_picture_size" android:layout_height="@dimen/listitem_picture_frame_size"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginLeft="@dimen/listitem_horizontal_margin" android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin" android:layout_marginStart="@dimen/listitem_horizontal_margin">
android:transitionName="avatar"
app:civ_border_color="@color/briar_primary" <de.hdodenhof.circleimageview.CircleImageView
app:civ_border_width="@dimen/avatar_border_width" android:id="@+id/avatarView"
tools:src="@drawable/ic_launcher"/> style="@style/BriarAvatar"
android:layout_width="@dimen/listitem_picture_size"
android:layout_height="@dimen/listitem_picture_size"
android:layout_gravity="bottom|left"
tools:src="@drawable/ic_launcher"/>
<TextView
android:id="@+id/unreadCountView"
android:layout_width="wrap_content"
android:layout_height="@dimen/unread_bubble_size"
android:layout_gravity="right|top"
android:background="@drawable/bubble"
android:gravity="center"
android:minWidth="@dimen/unread_bubble_size"
android:textColor="@color/briar_text_primary_inverse"
android:textSize="@dimen/unread_bubble_text_size"
android:textStyle="bold"
tools:text="123"/>
</FrameLayout>
<LinearLayout <LinearLayout
android:id="@+id/textViews" android:id="@+id/textViews"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginLeft="@dimen/listitem_horizontal_margin" android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin" android:layout_marginStart="@dimen/listitem_horizontal_margin"
android:layout_toEndOf="@+id/avatarFrameView"
android:layout_toLeftOf="@+id/bulbView" android:layout_toLeftOf="@+id/bulbView"
android:layout_toRightOf="@+id/avatarView" android:layout_toRightOf="@+id/avatarFrameView"
android:layout_toEndOf="@+id/avatarView"> android:orientation="vertical">
<TextView <TextView
android:id="@+id/nameView" android:id="@+id/nameView"

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="@dimen/margin_small"
android:paddingTop="@dimen/margin_small">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarView"
style="@style/BriarAvatar"
android:layout_width="@dimen/listitem_picture_size_small"
android:layout_height="@dimen/listitem_picture_size_small"
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin"
tools:src="@drawable/ic_launcher"/>
<TextView
android:id="@+id/nameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin"
android:maxLines="2"
android:textColor="@color/briar_text_primary"
android:textSize="@dimen/text_size_medium"
tools:text="This is a name of a contact"/>
</LinearLayout>

View File

@@ -14,6 +14,7 @@
<de.hdodenhof.circleimageview.CircleImageView <de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarView" android:id="@+id/avatarView"
style="@style/BriarAvatar"
android:layout_width="@dimen/listitem_selectable_picture_size" android:layout_width="@dimen/listitem_selectable_picture_size"
android:layout_height="@dimen/listitem_selectable_picture_size" android:layout_height="@dimen/listitem_selectable_picture_size"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
@@ -22,8 +23,6 @@
android:layout_marginLeft="@dimen/listitem_horizontal_margin" android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin" android:layout_marginStart="@dimen/listitem_horizontal_margin"
android:transitionName="avatar" android:transitionName="avatar"
app:civ_border_color="@color/briar_primary"
app:civ_border_width="@dimen/avatar_border_width"
tools:src="@drawable/ic_launcher"/> tools:src="@drawable/ic_launcher"/>
<TextView <TextView

View File

@@ -1,18 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<merge <merge
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:tools="http://schemas.android.com/tools">
<de.hdodenhof.circleimageview.CircleImageView <de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarBackground" android:id="@+id/avatarBackground"
style="@style/BriarAvatar"
android:layout_width="@dimen/avatar_forum_size" android:layout_width="@dimen/avatar_forum_size"
android:layout_height="@dimen/avatar_forum_size" android:layout_height="@dimen/avatar_forum_size"
android:layout_gravity="center" android:layout_gravity="center"
android:src="@android:color/transparent" android:src="@android:color/transparent"
app:civ_fill_color="@color/briar_button_positive" app:civ_fill_color="@color/briar_button_positive"/>
app:civ_border_color="@color/briar_primary"
app:civ_border_width="@dimen/avatar_border_width"/>
<android.support.v7.widget.AppCompatTextView <android.support.v7.widget.AppCompatTextView
android:id="@+id/textAvatarView" android:id="@+id/textAvatarView"
@@ -29,4 +28,3 @@
tools:text="T"/> tools:text="T"/>
</merge> </merge>

View File

@@ -15,6 +15,11 @@
android:title="@string/forum_share_button" android:title="@string/forum_share_button"
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_forum_sharing_status"
android:title="@string/forum_sharing_status"
app:showAsAction="never"/>
<item <item
android:id="@+id/action_forum_delete" android:id="@+id/action_forum_delete"
android:icon="@drawable/action_delete_white" android:icon="@drawable/action_delete_white"

View File

@@ -18,7 +18,7 @@
<color name="horizontal_border">#CCCCCC</color> <color name="horizontal_border">#CCCCCC</color>
<color name="forums_available_background">@color/briar_gold</color> <color name="forums_available_background">@color/briar_gold</color>
<color name="no_private_messages">#AAAAAA</color> <color name="no_private_messages">#AAAAAA</color>
<color name="forum_avatar_shadow">#b3b3b3</color> <color name="forum_avatar_shadow">#99000000</color>
<color name="briar_primary">@color/briar_blue</color> <color name="briar_primary">@color/briar_blue</color>
<color name="briar_primary_dark">@color/briar_blue_dark</color> <color name="briar_primary_dark">@color/briar_blue_dark</color>
@@ -36,10 +36,17 @@
<!-- this is needed as preference_category_material layout uses this color as the text color --> <!-- this is needed as preference_category_material layout uses this color as the text color -->
<color name="preference_fallback_accent_color">@color/briar_accent</color> <color name="preference_fallback_accent_color">@color/briar_accent</color>
<color name="divider">#c1c1c1</color>
<color name="default_separator">#000000</color> <color name="default_separator">#000000</color>
<color name="default_separator_inverted">#ffffff</color> <color name="default_separator_inverted">#ffffff</color>
<color name="menu_background">#FFFFFF</color> <color name="menu_background">#FFFFFF</color>
<color name="spinner_border">#61000000</color> <!-- 38% Black --> <color name="spinner_border">#61000000</color> <!-- 38% Black -->
<color name="spinner_arrow">@color/briar_blue_dark</color> <color name="spinner_arrow">@color/briar_blue_dark</color>
<!-- ViewfinderView -->
<color name="possible_result_points">#c0ffbd21</color> <!-- Material Yellow 700 with alpha -->
<color name="result_view">#b0000000</color>
<color name="viewfinder_laser">#d50000</color> <!-- Red accent 700 -->
<color name="viewfinder_mask">#60000000</color>
</resources> </resources>

View File

@@ -26,10 +26,17 @@
<dimen name="listitem_height_one_line_avatar">56dp</dimen> <dimen name="listitem_height_one_line_avatar">56dp</dimen>
<dimen name="listitem_height_contact_selector">68dp</dimen> <dimen name="listitem_height_contact_selector">68dp</dimen>
<dimen name="listitem_picture_size">48dp</dimen> <dimen name="listitem_picture_size">48dp</dimen>
<dimen name="listitem_picture_size_small">23dp</dimen>
<dimen name="listitem_picture_frame_size">50dp</dimen>
<dimen name="listitem_selectable_picture_size">40dp</dimen> <dimen name="listitem_selectable_picture_size">40dp</dimen>
<dimen name="dropdown_picture_size">32dp</dimen> <dimen name="dropdown_picture_size">32dp</dimen>
<dimen name="avatar_forum_size">48dp</dimen> <dimen name="avatar_forum_size">48dp</dimen>
<dimen name="avatar_border_width">1dp</dimen> <dimen name="avatar_border_width">2dp</dimen>
<dimen name="unread_bubble_text_size">12sp</dimen>
<dimen name="unread_bubble_border_width">2dp</dimen>
<dimen name="unread_bubble_padding_horizontal">6dp</dimen>
<dimen name="unread_bubble_size">19dp</dimen>
<dimen name="message_bubble_margin_tail">14dp</dimen> <dimen name="message_bubble_margin_tail">14dp</dimen>
<dimen name="message_bubble_margin_non_tail">51dp</dimen> <dimen name="message_bubble_margin_non_tail">51dp</dimen>

View File

@@ -80,6 +80,7 @@
<string name="show_forums">Show</string> <string name="show_forums">Show</string>
<string name="forum_leave">Leave Forum</string> <string name="forum_leave">Leave Forum</string>
<string name="forum_left_toast">Left Forum</string> <string name="forum_left_toast">Left Forum</string>
<string name="forum_sharing_status">Sharing Status</string>
<string name="no_forum_posts">No posts</string> <string name="no_forum_posts">No posts</string>
<string name="no_unread_posts">no unread posts</string> <string name="no_unread_posts">no unread posts</string>
<plurals name="unread_posts"> <plurals name="unread_posts">
@@ -109,6 +110,9 @@
<string name="forum_joined_toast">Joined Forum</string> <string name="forum_joined_toast">Joined Forum</string>
<string name="forum_declined_toast">Forum Invitation Declined</string> <string name="forum_declined_toast">Forum Invitation Declined</string>
<string name="shared_by_format">Shared by %s</string> <string name="shared_by_format">Shared by %s</string>
<string name="forum_shared_by">Shared by</string>
<string name="forum_shared_with">Shared with</string>
<string name="nobody">Nobody</string>
<string name="no_contacts_prompt">You don\'t have any contacts. Add a contact now?</string> <string name="no_contacts_prompt">You don\'t have any contacts. Add a contact now?</string>
<string name="add_button">Add</string> <string name="add_button">Add</string>
<string name="cancel_button">Cancel</string> <string name="cancel_button">Cancel</string>

View File

@@ -93,7 +93,7 @@
</style> </style>
<style name="Divider"> <style name="Divider">
<item name="android:background">?android:attr/listDivider</item> <item name="android:background">@color/divider</item>
</style> </style>
<style name="Divider.Horizontal" parent="Divider"> <style name="Divider.Horizontal" parent="Divider">
@@ -112,6 +112,14 @@
<item name="android:layout_height">1dp</item> <item name="android:layout_height">1dp</item>
</style> </style>
<style name="BriarAvatar">
<item name="civ_border_width">@dimen/avatar_border_width</item>
<item name="civ_border_color">@color/briar_primary</item>
<!-- Remove when we are using 'de.hdodenhof:circleimageview:2.1.0' -->
<item name="civ_border_overlay">true</item>
</style>
<style name="NavMenuButton" parent="Widget.AppCompat.Button.Borderless.Colored"> <style name="NavMenuButton" parent="Widget.AppCompat.Button.Borderless.Colored">
<item name="android:textSize">@dimen/text_size_medium</item> <item name="android:textSize">@dimen/text_size_medium</item>
<item name="android:textColor">@android:color/tertiary_text_light</item> <item name="android:textColor">@android:color/tertiary_text_light</item>

View File

@@ -7,6 +7,7 @@ import org.briarproject.android.forum.AvailableForumsActivity;
import org.briarproject.android.forum.ContactSelectorFragment; import org.briarproject.android.forum.ContactSelectorFragment;
import org.briarproject.android.forum.CreateForumActivity; import org.briarproject.android.forum.CreateForumActivity;
import org.briarproject.android.forum.ForumActivity; import org.briarproject.android.forum.ForumActivity;
import org.briarproject.android.forum.ForumSharingStatusActivity;
import org.briarproject.android.forum.ReadForumPostActivity; import org.briarproject.android.forum.ReadForumPostActivity;
import org.briarproject.android.forum.ShareForumActivity; import org.briarproject.android.forum.ShareForumActivity;
import org.briarproject.android.forum.ShareForumMessageFragment; import org.briarproject.android.forum.ShareForumMessageFragment;
@@ -59,6 +60,8 @@ public interface ActivityComponent {
void inject(ShareForumActivity activity); void inject(ShareForumActivity activity);
void inject(ForumSharingStatusActivity activity);
void inject(ReadForumPostActivity activity); void inject(ReadForumPostActivity activity);
void inject(ForumActivity activity); void inject(ForumActivity activity);

View File

@@ -1,17 +1,12 @@
package org.briarproject.android; package org.briarproject.android;
import static android.view.Gravity.CENTER;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static org.briarproject.android.TestingConstants.PREVENT_SCREENSHOTS;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
import org.briarproject.R;
import org.briarproject.android.util.LayoutUtils;
import android.app.Activity; import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.widget.LinearLayout;
import android.widget.TextView; import org.briarproject.R;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static org.briarproject.android.TestingConstants.PREVENT_SCREENSHOTS;
public class ExpiredActivity extends Activity { public class ExpiredActivity extends Activity {
@@ -21,19 +16,6 @@ public class ExpiredActivity extends Activity {
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE); if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
LinearLayout layout = new LinearLayout(this); setContentView(R.layout.activity_expired);
layout.setLayoutParams(MATCH_MATCH);
layout.setGravity(CENTER);
int pad = LayoutUtils.getPadding(this);
TextView warning = new TextView(this);
warning.setGravity(CENTER);
warning.setTextSize(18);
warning.setPadding(pad, pad, pad, pad);
warning.setText(R.string.expiry_warning);
layout.addView(warning);
setContentView(layout);
} }
} }

View File

@@ -1,6 +1,7 @@
package org.briarproject.android.contact; package org.briarproject.android.contact;
import android.content.Context; import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v7.util.SortedList; import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.View; import android.view.View;
@@ -11,6 +12,7 @@ import android.widget.TextView;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
import org.briarproject.util.StringUtils;
import java.util.List; import java.util.List;
@@ -48,6 +50,9 @@ public abstract class BaseContactListAdapter<VH extends BaseContactListAdapter.B
if (listener != null) listener.onItemClick(ui.avatar, item); if (listener != null) listener.onItemClick(ui.avatar, item);
} }
}); });
ViewCompat.setTransitionName(ui.avatar, "avatar" +
StringUtils.toHexString(item.getGroupId().getBytes()));
} }
@Override @Override

View File

@@ -2,6 +2,7 @@ package org.briarproject.android.contact;
import android.content.Context; import android.content.Context;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewCompat;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -10,6 +11,7 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.util.StringUtils;
public class ContactListAdapter public class ContactListAdapter
extends BaseContactListAdapter<ContactListAdapter.ContactHolder> { extends BaseContactListAdapter<ContactListAdapter.ContactHolder> {
@@ -32,18 +34,13 @@ public class ContactListAdapter
ContactListItem item = getItem(position); ContactListItem item = getItem(position);
// name and unread count // unread count
String contactName = item.getContact().getAuthor().getName();
int unread = item.getUnreadCount(); int unread = item.getUnreadCount();
if (unread > 0) { if (unread > 0) {
// TODO show these in a bubble on top of the avatar ui.unread.setText(String.valueOf(unread));
ui.name.setText(contactName + " (" + unread + ")"); ui.unread.setVisibility(View.VISIBLE);
// different background for contacts with unread messages
ui.layout.setBackgroundColor(
ContextCompat.getColor(ctx, R.color.unread_background));
} else { } else {
ui.name.setText(contactName); ui.unread.setVisibility(View.INVISIBLE);
} }
// date of last message // date of last message
@@ -62,12 +59,16 @@ public class ContactListAdapter
} else { } else {
ui.bulb.setImageResource(R.drawable.contact_disconnected); ui.bulb.setImageResource(R.drawable.contact_disconnected);
} }
ViewCompat.setTransitionName(ui.bulb,
"bulb" + StringUtils.toHexString(item.getGroupId().getBytes()));
} }
protected static class ContactHolder protected static class ContactHolder
extends BaseContactListAdapter.BaseContactHolder { extends BaseContactListAdapter.BaseContactHolder {
public final ImageView bulb; public final ImageView bulb;
public final TextView unread;
public final TextView date; public final TextView date;
public final TextView identity; public final TextView identity;
@@ -75,6 +76,7 @@ public class ContactListAdapter
super(v); super(v);
bulb = (ImageView) v.findViewById(R.id.bulbView); bulb = (ImageView) v.findViewById(R.id.bulbView);
unread = (TextView) v.findViewById(R.id.unreadCountView);
date = (TextView) v.findViewById(R.id.dateView); date = (TextView) v.findViewById(R.id.dateView);
identity = (TextView) v.findViewById(R.id.identityView); identity = (TextView) v.findViewById(R.id.identityView);
} }

View File

@@ -1,11 +1,13 @@
package org.briarproject.android.contact; package org.briarproject.android.contact;
import android.content.Intent; import android.content.Intent;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat; import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.util.Pair;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -48,6 +50,7 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.android.BriarActivity.GROUP_ID; import static org.briarproject.android.BriarActivity.GROUP_ID;
@@ -107,16 +110,22 @@ public class ContactListFragment extends BaseFragment implements EventListener {
ConversationActivity.class); ConversationActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes()); i.putExtra(GROUP_ID, groupId.getBytes());
if (Build.VERSION.SDK_INT >= 16) { ContactListAdapter.ContactHolder holder =
ActivityOptionsCompat options = (ContactListAdapter.ContactHolder) list
ActivityOptionsCompat. .getRecyclerView()
makeSceneTransitionAnimation( .findViewHolderForAdapterPosition(
getActivity(), adapter.findItemPosition(item));
view, "avatar"); Pair<View, String> avatar =
getActivity().startActivity(i, options.toBundle()); Pair.create((View) holder.avatar, ViewCompat
} else { .getTransitionName(holder.avatar));
startActivity(i); Pair<View, String> bulb =
} Pair.create((View) holder.bulb, ViewCompat
.getTransitionName(holder.bulb));
ActivityOptionsCompat options =
makeSceneTransitionAnimation(getActivity(),
avatar, bulb);
ActivityCompat.startActivity(getActivity(), i,
options.toBundle());
} }
}; };

View File

@@ -30,7 +30,7 @@ public class ContactListItem {
} }
void setMessages(Collection<ConversationItem> messages) { void setMessages(Collection<ConversationItem> messages) {
empty = messages.isEmpty(); empty = messages == null || messages.isEmpty();
timestamp = 0; timestamp = 0;
unread = 0; unread = 0;
if (!empty) { if (!empty) {

View File

@@ -6,6 +6,7 @@ import android.os.Bundle;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat; import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewCompat;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
@@ -156,6 +157,10 @@ public class ConversationActivity extends BriarActivity
ab.setDisplayShowTitleEnabled(false); ab.setDisplayShowTitleEnabled(false);
} }
String hexGroupId = StringUtils.toHexString(b);
ViewCompat.setTransitionName(toolbarAvatar, "avatar" + hexGroupId);
ViewCompat.setTransitionName(toolbarStatus, "bulb" + hexGroupId);
adapter = new ConversationAdapter(this, this); adapter = new ConversationAdapter(this, this);
list = (BriarRecyclerView) findViewById(R.id.conversationView); list = (BriarRecyclerView) findViewById(R.id.conversationView);
list.setLayoutManager(new LinearLayoutManager(this)); list.setLayoutManager(new LinearLayoutManager(this));
@@ -207,7 +212,7 @@ public class ConversationActivity extends BriarActivity
// Handle presses on the action bar items // Handle presses on the action bar items
switch (item.getItemId()) { switch (item.getItemId()) {
case android.R.id.home: case android.R.id.home:
supportFinishAfterTransition(); onBackPressed();
return true; return true;
case R.id.action_introduction: case R.id.action_introduction:
if (contactId == null) return false; if (contactId == null) return false;
@@ -227,6 +232,13 @@ public class ConversationActivity extends BriarActivity
} }
} }
@Override
public void onBackPressed() {
// FIXME disabled exit transition, because it doesn't work for some reason
//supportFinishAfterTransition();
finish();
}
private void loadData() { private void loadData() {
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override

View File

@@ -153,7 +153,7 @@ public class AvailableForumsActivity extends BriarActivity
@Override @Override
public void onItemClick(AvailableForumsItem item, boolean accept) { public void onItemClick(AvailableForumsItem item, boolean accept) {
respondToInvitation(item.getForum(), accept); respondToInvitation(item, accept);
// show toast // show toast
int res = R.string.forum_declined_toast; int res = R.string.forum_declined_toast;
@@ -161,12 +161,16 @@ public class AvailableForumsActivity extends BriarActivity
Toast.makeText(this, res, LENGTH_SHORT).show(); Toast.makeText(this, res, LENGTH_SHORT).show();
} }
private void respondToInvitation(final Forum f, final boolean accept) { private void respondToInvitation(final AvailableForumsItem item,
final boolean accept) {
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
forumSharingManager.respondToInvitation(f, accept); Forum f = item.getForum();
for (Contact c : item.getContacts()) {
forumSharingManager.respondToInvitation(f, c, accept);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);

View File

@@ -129,8 +129,7 @@ public class CreateForumActivity extends BriarActivity
public void run() { public void run() {
try { try {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
Forum f = forumManager.createForum(name); Forum f = forumManager.addForum(name);
forumManager.addForum(f);
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Storing forum took " + duration + " ms"); LOG.info("Storing forum took " + duration + " ms");

View File

@@ -153,6 +153,9 @@ public class ForumActivity extends BriarActivity implements EventListener,
@Override @Override
public boolean onOptionsItemSelected(final MenuItem item) { public boolean onOptionsItemSelected(final MenuItem item) {
ActivityOptionsCompat options = ActivityOptionsCompat
.makeCustomAnimation(this, android.R.anim.slide_in_left,
android.R.anim.slide_out_right);
// Handle presses on the action bar items // Handle presses on the action bar items
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_forum_compose_post: case R.id.action_forum_compose_post:
@@ -166,13 +169,16 @@ public class ForumActivity extends BriarActivity implements EventListener,
Intent i2 = new Intent(this, ShareForumActivity.class); Intent i2 = new Intent(this, ShareForumActivity.class);
i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP); i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i2.putExtra(GROUP_ID, groupId.getBytes()); i2.putExtra(GROUP_ID, groupId.getBytes());
ActivityOptionsCompat options = ActivityOptionsCompat
.makeCustomAnimation(this, android.R.anim.slide_in_left,
android.R.anim.slide_out_right);
ActivityCompat ActivityCompat
.startActivityForResult(this, i2, REQUEST_FORUM_SHARED, .startActivityForResult(this, i2, REQUEST_FORUM_SHARED,
options.toBundle()); options.toBundle());
return true; return true;
case R.id.action_forum_sharing_status:
Intent i3 = new Intent(this, ForumSharingStatusActivity.class);
i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i3.putExtra(GROUP_ID, groupId.getBytes());
ActivityCompat.startActivity(this, i3, options.toBundle());
return true;
case R.id.action_forum_delete: case R.id.action_forum_delete:
showUnsubscribeDialog(); showUnsubscribeDialog();
return true; return true;

View File

@@ -0,0 +1,169 @@
package org.briarproject.android.forum;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.view.MenuItem;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.contact.ContactListItem;
import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.GroupId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
public class ForumSharingStatusActivity extends BriarActivity {
private GroupId groupId;
private BriarRecyclerView sharedByList, sharedWithList;
private ForumSharingStatusAdapter sharedByAdapter, sharedWithAdapter;
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile ForumSharingManager forumSharingManager;
@Inject
protected volatile IdentityManager identityManager;
public final static String TAG = "ForumSharingStatusActivity";
private static final Logger LOG = Logger.getLogger(TAG);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_forum_sharing_status);
Intent i = getIntent();
byte[] b = i.getByteArrayExtra(GROUP_ID);
if (b == null) throw new IllegalStateException("No GroupId");
groupId = new GroupId(b);
sharedByList = (BriarRecyclerView) findViewById(R.id.sharedByView);
sharedByAdapter = new ForumSharingStatusAdapter(this);
sharedByList.setLayoutManager(new LinearLayoutManager(this));
sharedByList.setAdapter(sharedByAdapter);
sharedByList.setEmptyText(getString(R.string.nobody));
sharedWithList = (BriarRecyclerView) findViewById(R.id.sharedWithView);
sharedWithAdapter = new ForumSharingStatusAdapter(this);
sharedWithList.setLayoutManager(new LinearLayoutManager(this));
sharedWithList.setAdapter(sharedWithAdapter);
sharedWithList.setEmptyText(getString(R.string.nobody));
}
@Override
public void onResume() {
super.onResume();
loadSharedBy();
loadSharedWith();
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
private void loadSharedBy() {
dbController.runOnDbThread(new Runnable() {
@Override
public void run() {
List<ContactListItem> contactItems = new ArrayList<>();
try {
Collection<Contact> contacts =
forumSharingManager.getSharedBy(groupId);
for (Contact c : contacts) {
LocalAuthor localAuthor = identityManager
.getLocalAuthor(c.getLocalAuthorId());
ContactListItem item =
new ContactListItem(c, localAuthor, false, null,
null);
contactItems.add(item);
}
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
displaySharedBy(contactItems);
}
});
}
private void displaySharedBy(final List<ContactListItem> contacts) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (contacts.isEmpty()) {
sharedByList.showData();
} else {
sharedByAdapter.addAll(contacts);
}
}
});
}
private void loadSharedWith() {
dbController.runOnDbThread(new Runnable() {
@Override
public void run() {
List<ContactListItem> contactItems = new ArrayList<>();
try {
Collection<Contact> contacts =
forumSharingManager.getSharedWith(groupId);
for (Contact c : contacts) {
LocalAuthor localAuthor = identityManager
.getLocalAuthor(c.getLocalAuthorId());
ContactListItem item =
new ContactListItem(c, localAuthor, false, null,
null);
contactItems.add(item);
}
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
displaySharedWith(contactItems);
}
});
}
private void displaySharedWith(final List<ContactListItem> contacts) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (contacts.isEmpty()) {
sharedWithList.showData();
} else {
sharedWithAdapter.addAll(contacts);
}
}
});
}
}

View File

@@ -0,0 +1,36 @@
package org.briarproject.android.forum;
import android.content.Context;
import android.support.v4.content.ContextCompat;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.contact.BaseContactListAdapter;
import org.briarproject.android.contact.ContactListItem;
public class ForumSharingStatusAdapter
extends BaseContactListAdapter<BaseContactListAdapter.BaseContactHolder> {
public ForumSharingStatusAdapter(Context context) {
super(context, null);
}
@Override
public BaseContactHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_contact_small, viewGroup, false);
return new BaseContactHolder(v);
}
@Override
public int compareContactListItems(ContactListItem c1, ContactListItem c2) {
return compareByName(c1, c2);
}
}

View File

@@ -2,12 +2,14 @@ package org.briarproject.android.identity;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.design.widget.TextInputLayout;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener; import android.widget.TextView.OnEditorActionListener;
@@ -16,7 +18,7 @@ import android.widget.Toast;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.android.ActivityComponent; import org.briarproject.android.ActivityComponent;
import org.briarproject.android.BriarActivity; import org.briarproject.android.BriarActivity;
import org.briarproject.android.util.LayoutUtils; import org.briarproject.android.util.AndroidUtils;
import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.CryptoExecutor; import org.briarproject.api.crypto.CryptoExecutor;
import org.briarproject.api.crypto.KeyPair; import org.briarproject.api.crypto.KeyPair;
@@ -31,18 +33,11 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static android.text.InputType.TYPE_CLASS_TEXT;
import static android.text.InputType.TYPE_TEXT_FLAG_CAP_WORDS;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.widget.LinearLayout.VERTICAL;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP;
import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
public class CreateIdentityActivity extends BriarActivity public class CreateIdentityActivity extends BriarActivity
@@ -55,10 +50,10 @@ public class CreateIdentityActivity extends BriarActivity
@CryptoExecutor @CryptoExecutor
protected Executor cryptoExecutor; protected Executor cryptoExecutor;
private TextInputLayout nicknameInput;
private EditText nicknameEntry; private EditText nicknameEntry;
private Button createIdentityButton; private Button createIdentityButton;
private ProgressBar progress; private ProgressBar progress;
private TextView feedback;
// Fields that are accessed from background threads must be volatile // Fields that are accessed from background threads must be volatile
@Inject @Inject
@@ -71,52 +66,32 @@ public class CreateIdentityActivity extends BriarActivity
@Override @Override
public void onCreate(Bundle state) { public void onCreate(Bundle state) {
super.onCreate(state); super.onCreate(state);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(MATCH_MATCH);
layout.setOrientation(VERTICAL);
layout.setGravity(CENTER_HORIZONTAL);
int pad = LayoutUtils.getPadding(this);
layout.setPadding(pad, pad, pad, pad);
TextView chooseNickname = new TextView(this); setContentView(R.layout.activity_create_identity);
chooseNickname.setGravity(CENTER);
chooseNickname.setTextSize(18);
chooseNickname.setText(R.string.choose_nickname);
layout.addView(chooseNickname);
nicknameEntry = new EditText(this) { nicknameInput = (TextInputLayout) findViewById(R.id.nicknameInputLayout);
nicknameEntry = (EditText) findViewById(R.id.nicknameEntry);
nicknameEntry.addTextChangedListener(new TextWatcher() {
@Override @Override
protected void onTextChanged(CharSequence text, int start, public void beforeTextChanged(CharSequence s, int start, int count,
int lengthBefore, int lengthAfter) { int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
enableOrDisableCreateButton(); enableOrDisableCreateButton();
} }
};
nicknameEntry.setId(1); @Override
nicknameEntry.setMaxLines(1); public void afterTextChanged(Editable s) {}
int inputType = TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_WORDS; });
nicknameEntry.setInputType(inputType);
nicknameEntry.setOnEditorActionListener(this); nicknameEntry.setOnEditorActionListener(this);
layout.addView(nicknameEntry);
feedback = new TextView(this); createIdentityButton = (Button) findViewById(R.id.createIdentityButton);
feedback.setGravity(CENTER); if (createIdentityButton != null)
feedback.setPadding(0, pad, 0, pad); createIdentityButton.setOnClickListener(this);
layout.addView(feedback);
createIdentityButton = new Button(this); progress = (ProgressBar) findViewById(R.id.progressBar);
createIdentityButton.setLayoutParams(WRAP_WRAP);
createIdentityButton.setText(R.string.create_identity_button);
createIdentityButton.setEnabled(false);
createIdentityButton.setOnClickListener(this);
layout.addView(createIdentityButton);
progress = new ProgressBar(this);
progress.setLayoutParams(WRAP_WRAP);
progress.setIndeterminate(true);
progress.setVisibility(GONE);
layout.addView(progress);
setContentView(layout);
} }
@Override @Override
@@ -139,10 +114,11 @@ public class CreateIdentityActivity extends BriarActivity
String nickname = nicknameEntry.getText().toString(); String nickname = nicknameEntry.getText().toString();
int length = StringUtils.toUtf8(nickname).length; int length = StringUtils.toUtf8(nickname).length;
if (length > MAX_AUTHOR_NAME_LENGTH) { if (length > MAX_AUTHOR_NAME_LENGTH) {
feedback.setText(R.string.name_too_long); String str = getString(R.string.name_too_long);
AndroidUtils.setError(nicknameInput, str, true);
return false; return false;
} }
feedback.setText(""); AndroidUtils.setError(nicknameInput, null, false);
return length > 0; return length > 0;
} }
@@ -169,7 +145,7 @@ public class CreateIdentityActivity extends BriarActivity
} }
private void storeLocalAuthor(final LocalAuthor a) { private void storeLocalAuthor(final LocalAuthor a) {
runOnDbThread(new Runnable() { dbController.runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {

View File

@@ -19,6 +19,8 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.google.zxing.Result; import com.google.zxing.Result;
import com.google.zxing.ResultPoint;
import com.google.zxing.ResultPointCallback;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.android.AndroidComponent; import org.briarproject.android.AndroidComponent;
@@ -27,6 +29,7 @@ import org.briarproject.android.fragment.BaseEventFragment;
import org.briarproject.android.util.CameraView; import org.briarproject.android.util.CameraView;
import org.briarproject.android.util.QrCodeDecoder; import org.briarproject.android.util.QrCodeDecoder;
import org.briarproject.android.util.QrCodeUtils; import org.briarproject.android.util.QrCodeUtils;
import org.briarproject.android.util.ViewfinderView;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.KeyAgreementAbortedEvent; import org.briarproject.api.event.KeyAgreementAbortedEvent;
import org.briarproject.api.event.KeyAgreementFailedEvent; import org.briarproject.api.event.KeyAgreementFailedEvent;
@@ -55,7 +58,7 @@ import static java.util.logging.Level.WARNING;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class ShowQrCodeFragment extends BaseEventFragment public class ShowQrCodeFragment extends BaseEventFragment
implements QrCodeDecoder.ResultCallback { implements QrCodeDecoder.ResultCallback, ResultPointCallback {
public static final String TAG = "ShowQrCodeFragment"; public static final String TAG = "ShowQrCodeFragment";
@@ -75,6 +78,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
protected Executor ioExecutor; protected Executor ioExecutor;
private CameraView cameraView; private CameraView cameraView;
private ViewfinderView viewfinderView;
private View statusView; private View statusView;
private TextView status; private TextView status;
private ImageView qrCode; private ImageView qrCode;
@@ -109,9 +113,13 @@ public class ShowQrCodeFragment extends BaseEventFragment
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
cameraView = (CameraView) view.findViewById(R.id.camera_view); cameraView = (CameraView) view.findViewById(R.id.camera_view);
viewfinderView =
(ViewfinderView) view.findViewById(R.id.viewfinder_view);
statusView = view.findViewById(R.id.status_container); statusView = view.findViewById(R.id.status_container);
status = (TextView) view.findViewById(R.id.connect_status); status = (TextView) view.findViewById(R.id.connect_status);
qrCode = (ImageView) view.findViewById(R.id.qr_code); qrCode = (ImageView) view.findViewById(R.id.qr_code);
viewfinderView.setFrameProvider(cameraView);
} }
@Override @Override
@@ -120,7 +128,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR); getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
decoder = new QrCodeDecoder(this); decoder = new QrCodeDecoder(this, this);
} }
@Override @Override
@@ -219,6 +227,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
getActivity().finish(); getActivity().finish();
} else { } else {
cameraView.start(camera, decoder, 0); cameraView.start(camera, decoder, 0);
viewfinderView.drawViewfinder();
} }
} }
}; };
@@ -355,6 +364,11 @@ public class ShowQrCodeFragment extends BaseEventFragment
}); });
} }
@Override
public void foundPossibleResultPoint(ResultPoint point) {
viewfinderView.addPossibleResultPoint(point);
}
private class BluetoothStateReceiver extends BroadcastReceiver { private class BluetoothStateReceiver extends BroadcastReceiver {
@Override @Override

View File

@@ -120,10 +120,11 @@ public class BriarRecyclerView extends FrameLayout {
emptyView.setVisibility(VISIBLE); emptyView.setVisibility(VISIBLE);
recyclerView.setVisibility(INVISIBLE); recyclerView.setVisibility(INVISIBLE);
} else { } else {
emptyView.setVisibility(INVISIBLE); // use GONE here so empty view doesn't use space on small lists
emptyView.setVisibility(GONE);
recyclerView.setVisibility(VISIBLE); recyclerView.setVisibility(VISIBLE);
} }
progressBar.setVisibility(INVISIBLE); progressBar.setVisibility(GONE);
} }
} }
@@ -131,4 +132,9 @@ public class BriarRecyclerView extends FrameLayout {
if (recyclerView == null) initViews(); if (recyclerView == null) initViews();
recyclerView.scrollToPosition(position); recyclerView.scrollToPosition(position);
} }
public RecyclerView getRecyclerView() {
return this.recyclerView;
}
} }

View File

@@ -1,6 +1,8 @@
package org.briarproject.android.util; package org.briarproject.android.util;
import android.content.Context; import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.Camera; import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback; import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.CameraInfo; import android.hardware.Camera.CameraInfo;
@@ -13,6 +15,7 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView; import android.view.SurfaceView;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -31,17 +34,25 @@ import static java.util.logging.Level.WARNING;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class CameraView extends SurfaceView implements SurfaceHolder.Callback, public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
AutoFocusCallback { AutoFocusCallback, ViewfinderView.FrameProvider {
private static final int AUTO_FOCUS_RETRY_DELAY = 5000; // Milliseconds private static final int AUTO_FOCUS_RETRY_DELAY = 5000; // Milliseconds
private static final int MIN_FRAME_SIZE = 240;
private static final int MAX_FRAME_SIZE = 675; // = 5/8 * 1080
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(CameraView.class.getName()); Logger.getLogger(CameraView.class.getName());
private Camera camera = null; private Camera camera = null;
private Rect framingRect;
private Rect framingRectInPreview;
private Rect framingRectInSensor;
private PreviewConsumer previewConsumer = null; private PreviewConsumer previewConsumer = null;
private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0; private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0;
private boolean autoFocus = false, surfaceExists = false; private boolean autoFocus = false, surfaceExists = false;
private Point cameraResolution;
private final Object cameraResolutionLock = new Object();
public CameraView(Context context) { public CameraView(Context context) {
super(context); super(context);
} }
@@ -184,6 +195,24 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
LOG.info("No suitable focus mode"); LOG.info("No suitable focus mode");
} }
params.setZoom(0); params.setZoom(0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
List<Camera.Area> areas = new ArrayList<>();
areas.add(new Camera.Area(getFramingRectInSensor(), 1000));
if (params.getMaxNumFocusAreas() > 0) {
if (LOG.isLoggable(INFO)) {
LOG.info("Focus areas supported: " +
params.getMaxNumFocusAreas());
}
params.setFocusAreas(areas);
}
if (params.getMaxNumMeteringAreas() > 0) {
if (LOG.isLoggable(INFO)) {
LOG.info("Metering areas supported: " +
params.getMaxNumMeteringAreas());
}
params.setMeteringAreas(areas);
}
}
} }
private void setPreviewSize(Parameters params) { private void setPreviewSize(Parameters params) {
@@ -222,6 +251,13 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Best size " + bestSize.width + "x" + bestSize.height); LOG.info("Best size " + bestSize.width + "x" + bestSize.height);
params.setPreviewSize(bestSize.width, bestSize.height); params.setPreviewSize(bestSize.width, bestSize.height);
synchronized (cameraResolutionLock) {
cameraResolution = new Point(bestSize.width, bestSize.height);
}
} else {
synchronized (cameraResolutionLock) {
cameraResolution = null;
}
} }
} }
@@ -276,4 +312,152 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
LOG.log(WARNING, "Error retrying auto focus", e); LOG.log(WARNING, "Error retrying auto focus", e);
} }
} }
/**
* Calculates the framing rect which the UI should draw to show the user where to place the
* barcode. This target helps with alignment as well as forces the user to hold the device
* far enough away to ensure the image will be in focus.
*
* @return The rectangle to draw on screen in window coordinates.
*/
@Override
public Rect getFramingRect() {
if (framingRect == null) {
framingRect = calculateFramingRect(true);
if (LOG.isLoggable(INFO))
LOG.info("Calculated framing rect: " + framingRect);
}
return framingRect;
}
/**
* Calculates the framing rect which the UI should draw to show the user where to place the
* barcode. This target helps with alignment as well as forces the user to hold the device
* far enough away to ensure the image will be in focus.
* <p/>
* Adapted from the Zxing Barcode Scanner.
*
* @return The rectangle to draw on screen in window coordinates.
*/
private Rect calculateFramingRect(boolean withOrientation) {
if (camera == null) {
return null;
}
if (surfaceWidth == 0 || surfaceHeight == 0) {
// Called early, before the surface is ready
return null;
}
boolean portrait =
withOrientation && displayOrientation % 180 == 90;
int size = findDesiredDimensionInRange(
portrait ? surfaceWidth : surfaceHeight,
portrait ? surfaceHeight / 2 : surfaceWidth / 2,
MIN_FRAME_SIZE, MAX_FRAME_SIZE);
int leftOffset = portrait ?
(surfaceWidth - size) / 2 :
((surfaceWidth / 2) - size) / 2;
int topOffset = portrait ?
((surfaceHeight / 2) - size) / 2 :
(surfaceHeight - size) / 2;
return new Rect(leftOffset, topOffset, leftOffset + size,
topOffset + size);
}
/**
* Calculates the square that fits best inside the given region.
*/
private static int findDesiredDimensionInRange(int side1, int side2,
int hardMin, int hardMax) {
if (LOG.isLoggable(INFO))
LOG.info("Finding framing dimension, side1 = " + side1 +
", side2 = " + side2);
int minSide = Math.min(side1, side2);
int dim = 5 * minSide / 8; // Target 5/8 of smallest side
if (dim < hardMin) {
if (hardMin > minSide) {
if (LOG.isLoggable(INFO))
LOG.info("Returning minimum side length: " + minSide);
return minSide;
} else {
if (LOG.isLoggable(INFO))
LOG.info("Returning hard minimum: " + hardMin);
return hardMin;
}
}
if (dim > hardMax) {
if (LOG.isLoggable(INFO))
LOG.info("Returning hard maximum: " + hardMax);
return hardMax;
}
if (LOG.isLoggable(INFO))
LOG.info("Returning desired dimension: " + dim);
return dim;
}
/**
* Like {@link #getFramingRect} but coordinates are in terms of the preview
* frame, not UI / screen.
* <p/>
* Adapted from the Zxing Barcode Scanner.
*
* @return {@link Rect} expressing QR code scan area in terms of the preview size
*/
@Override
public Rect getFramingRectInPreview() {
if (framingRectInPreview == null) {
Rect framingRect = getFramingRect();
if (framingRect == null) {
return null;
}
Rect rect = new Rect(framingRect);
Point cameraResolution = getCameraResolution();
if (cameraResolution == null || surfaceWidth == 0 ||
surfaceHeight == 0) {
// Called early, before the surface is ready
return null;
}
rect.left = rect.left * cameraResolution.x / surfaceWidth;
rect.right = rect.right * cameraResolution.x / surfaceWidth;
rect.top = rect.top * cameraResolution.y / surfaceHeight;
rect.bottom = rect.bottom * cameraResolution.y / surfaceHeight;
framingRectInPreview = rect;
}
return framingRectInPreview;
}
private Point getCameraResolution() {
Point ret;
synchronized (cameraResolutionLock) {
ret = new Point(cameraResolution);
}
return ret;
}
/**
* Like {@link #getFramingRect} but coordinates are in terms of the sensor,
* not UI / screen (ie. it is independent of orientation)
*
* @return {@link Rect} expressing QR code scan area in terms of the sensor
*/
private Rect getFramingRectInSensor() {
if (framingRectInSensor == null) {
Rect framingRect = calculateFramingRect(false);
if (framingRect == null) {
return null;
}
Rect rect = new Rect(framingRect);
if (surfaceWidth == 0 || surfaceHeight == 0) {
// Called early, before the surface is ready
return null;
}
rect.left = (rect.left * 2000 / surfaceWidth) - 1000;
rect.right = (rect.right * 2000 / surfaceWidth) - 1000;
rect.top = (rect.top * 2000 / surfaceHeight) - 1000;
rect.bottom = (rect.bottom * 2000 / surfaceHeight) - 1000;
framingRectInSensor = rect;
}
return framingRectInSensor;
}
} }

View File

@@ -6,14 +6,18 @@ import android.hardware.Camera.Size;
import android.os.AsyncTask; import android.os.AsyncTask;
import com.google.zxing.BinaryBitmap; import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource; import com.google.zxing.LuminanceSource;
import com.google.zxing.PlanarYUVLuminanceSource; import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.Reader; import com.google.zxing.Reader;
import com.google.zxing.ReaderException; import com.google.zxing.ReaderException;
import com.google.zxing.Result; import com.google.zxing.Result;
import com.google.zxing.ResultPointCallback;
import com.google.zxing.common.HybridBinarizer; import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader; import com.google.zxing.qrcode.QRCodeReader;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger; import java.util.logging.Logger;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
@@ -26,11 +30,14 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
private final Reader reader = new QRCodeReader(); private final Reader reader = new QRCodeReader();
private final ResultCallback callback; private final ResultCallback callback;
private final ResultPointCallback pointCallback;
private boolean stopped = false; private boolean stopped = false;
public QrCodeDecoder(ResultCallback callback) { public QrCodeDecoder(ResultCallback callback,
ResultPointCallback pointCallback) {
this.callback = callback; this.callback = callback;
this.pointCallback = pointCallback;
} }
public void start(Camera camera) { public void start(Camera camera) {
@@ -72,9 +79,11 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
LuminanceSource src = new PlanarYUVLuminanceSource(data, width, LuminanceSource src = new PlanarYUVLuminanceSource(data, width,
height, 0, 0, width, height, false); height, 0, 0, width, height, false);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(src)); BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(src));
Map<DecodeHintType, Object> hints = new HashMap<>();
hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, pointCallback);
Result result = null; Result result = null;
try { try {
result = reader.decode(bitmap); result = reader.decode(bitmap, hints);
} catch (ReaderException e) { } catch (ReaderException e) {
return null; // No barcode found return null; // No barcode found
} catch (RuntimeException e) { } catch (RuntimeException e) {

View File

@@ -0,0 +1,214 @@
/*
* Copyright (C) 2008 ZXing authors
* Copyright (C) 2016 Sublime Software Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.briarproject.android.util;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import com.google.zxing.ResultPoint;
import org.briarproject.R;
import java.util.ArrayList;
import java.util.List;
/**
* This view is overlaid on top of the camera preview. It adds the viewfinder
* rectangle and partial transparency outside it, as well as the laser scanner
* animation and result points.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class ViewfinderView extends View {
private static final int[] SCANNER_ALPHA =
{0, 64, 128, 192, 255, 192, 128, 64};
private static final long ANIMATION_DELAY = 80L;
private static final int CURRENT_POINT_OPACITY = 0xA0;
private static final int MAX_RESULT_POINTS = 20;
private static final int POINT_SIZE = 6;
private FrameProvider frameProvider;
private final Paint paint;
private Bitmap resultBitmap;
private final int maskColor;
private final int resultColor;
private final int laserColor;
private final int resultPointColor;
private int scannerAlpha;
private List<ResultPoint> possibleResultPoints;
private List<ResultPoint> lastPossibleResultPoints;
// This constructor is used when the class is built from an XML resource.
public ViewfinderView(Context context, AttributeSet attrs) {
super(context, attrs);
if (isInEditMode()) {
paint = null;
maskColor = 0;
resultColor = 0;
laserColor = 0;
resultPointColor = 0;
return;
}
// Initialize these once for performance rather than calling them every
// time in onDraw().
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Resources resources = getResources();
maskColor = resources.getColor(R.color.viewfinder_mask);
resultColor = resources.getColor(R.color.result_view);
laserColor = resources.getColor(R.color.viewfinder_laser);
resultPointColor = resources.getColor(R.color.possible_result_points);
scannerAlpha = 0;
possibleResultPoints = new ArrayList<>(5);
lastPossibleResultPoints = null;
}
public void setFrameProvider(FrameProvider frameProvider) {
this.frameProvider = frameProvider;
}
@SuppressLint("DrawAllocation")
@Override
public void onDraw(Canvas canvas) {
if (frameProvider == null) {
return; // not ready yet, early draw before done configuring
}
Rect frame = this.frameProvider.getFramingRect();
Rect previewFrame = this.frameProvider.getFramingRectInPreview();
if (frame == null || previewFrame == null) {
return;
}
int width = canvas.getWidth();
int height = canvas.getHeight();
// Draw the exterior (i.e. outside the framing rect) darkened
paint.setColor(resultBitmap != null ? resultColor : maskColor);
canvas.drawRect(0, 0, width, frame.top, paint);
canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1,
paint);
canvas.drawRect(0, frame.bottom + 1, width, height, paint);
if (resultBitmap != null) {
// Draw the opaque result bitmap over the scanning rectangle
paint.setAlpha(CURRENT_POINT_OPACITY);
canvas.drawBitmap(resultBitmap, null, frame, paint);
} else {
// Draw a red "laser scanner" line through the middle to show
// decoding is active
paint.setColor(laserColor);
paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
int middle = frame.height() / 2 + frame.top;
canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1,
middle + 2, paint);
float scaleX = frame.width() / (float) previewFrame.width();
float scaleY = frame.height() / (float) previewFrame.height();
List<ResultPoint> currentPossible = possibleResultPoints;
List<ResultPoint> currentLast = lastPossibleResultPoints;
int frameLeft = frame.left;
int frameTop = frame.top;
if (currentPossible.isEmpty()) {
lastPossibleResultPoints = null;
} else {
possibleResultPoints = new ArrayList<>(5);
lastPossibleResultPoints = currentPossible;
paint.setAlpha(CURRENT_POINT_OPACITY);
paint.setColor(resultPointColor);
synchronized (currentPossible) {
for (ResultPoint point : currentPossible) {
canvas.drawCircle(
frameLeft + (int) (point.getX() * scaleX),
frameTop + (int) (point.getY() * scaleY),
POINT_SIZE, paint);
}
}
}
if (currentLast != null) {
paint.setAlpha(CURRENT_POINT_OPACITY / 2);
paint.setColor(resultPointColor);
synchronized (currentLast) {
float radius = POINT_SIZE / 2.0f;
for (ResultPoint point : currentLast) {
canvas.drawCircle(
frameLeft + (int) (point.getX() * scaleX),
frameTop + (int) (point.getY() * scaleY),
radius, paint);
}
}
}
// Request another update at the animation interval, but only
// repaint the laser line, not the entire viewfinder mask.
postInvalidateDelayed(ANIMATION_DELAY,
frame.left - POINT_SIZE,
frame.top - POINT_SIZE,
frame.right + POINT_SIZE,
frame.bottom + POINT_SIZE);
}
}
public void drawViewfinder() {
Bitmap resultBitmap = this.resultBitmap;
this.resultBitmap = null;
if (resultBitmap != null) {
resultBitmap.recycle();
}
invalidate();
}
/**
* Draw a bitmap with the result points highlighted instead of the live
* scanning display.
*
* @param barcode An image of the decoded barcode.
*/
public void drawResultBitmap(Bitmap barcode) {
resultBitmap = barcode;
invalidate();
}
public void addPossibleResultPoint(ResultPoint point) {
List<ResultPoint> points = possibleResultPoints;
synchronized (points) {
points.add(point);
int size = points.size();
if (size > MAX_RESULT_POINTS) {
// trim it
points.subList(0, size - MAX_RESULT_POINTS / 2).clear();
}
}
}
public interface FrameProvider {
Rect getFramingRect();
Rect getFramingRectInPreview();
}
}

View File

@@ -4,10 +4,6 @@ targetCompatibility = 1.6
apply plugin: 'witness' apply plugin: 'witness'
repositories {
jcenter()
}
dependencies { dependencies {
compile "com.google.dagger:dagger:2.0.2" compile "com.google.dagger:dagger:2.0.2"
compile 'com.google.dagger:dagger-compiler:2.0.2' compile 'com.google.dagger:dagger-compiler:2.0.2'

View File

@@ -0,0 +1,28 @@
package org.briarproject.api.clients;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.sync.BaseMessageContext;
import org.briarproject.api.sync.MessageId;
import java.util.Collection;
public class BdfMessageContext extends BaseMessageContext {
private final BdfDictionary dictionary;
public BdfMessageContext(BdfDictionary dictionary,
Collection<MessageId> dependencies) {
super(dependencies);
this.dictionary = dictionary;
}
public BdfMessageContext(BdfDictionary dictionary) {
this(dictionary, null);
}
public BdfDictionary getDictionary() {
return dictionary;
}
}

View File

@@ -5,6 +5,8 @@ import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.Transaction; import org.briarproject.api.db.Transaction;
import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.MessageContext;
public interface MessageQueueManager { public interface MessageQueueManager {
@@ -34,10 +36,11 @@ public interface MessageQueueManager {
interface QueueMessageValidator { interface QueueMessageValidator {
/** /**
* Validates the given message and returns its metadata if the message * Validates the given message and returns its metadata and
* is valid, or null if the message is invalid. * dependencies.
*/ */
Metadata validateMessage(QueueMessage q, Group g); MessageContext validateMessage(QueueMessage q, Group g)
throws InvalidMessageException;
} }
interface IncomingQueueMessageHook { interface IncomingQueueMessageHook {

View File

@@ -0,0 +1,11 @@
package org.briarproject.api.forum;
public interface ForumFactory {
/** Creates a forum with the given name. */
Forum createForum(String name);
/** Creates a forum with the given name and salt. */
Forum createForum(String name, byte[] salt);
}

View File

@@ -13,14 +13,8 @@ public interface ForumManager {
/** Returns the unique ID of the forum client. */ /** Returns the unique ID of the forum client. */
ClientId getClientId(); ClientId getClientId();
/** Creates a forum with the given name. */
Forum createForum(String name);
/** Creates a forum with the given name and salt. */
Forum createForum(String name, byte[] salt);
/** Subscribes to a forum. */ /** Subscribes to a forum. */
void addForum(Forum f) throws DbException; Forum addForum(String name) throws DbException;
/** Unsubscribes from a forum. */ /** Unsubscribes from a forum. */
void removeForum(Forum f) throws DbException; void removeForum(Forum f) throws DbException;

View File

@@ -24,7 +24,8 @@ public interface ForumSharingManager {
/** /**
* Responds to a pending forum invitation * Responds to a pending forum invitation
*/ */
void respondToInvitation(Forum f, boolean accept) throws DbException; void respondToInvitation(Forum f, Contact c, boolean accept)
throws DbException;
/** /**
* Returns all forum sharing messages sent by the Contact * Returns all forum sharing messages sent by the Contact
@@ -40,7 +41,7 @@ public interface ForumSharingManager {
Collection<Contact> getSharedBy(GroupId g) throws DbException; Collection<Contact> getSharedBy(GroupId g) throws DbException;
/** Returns the IDs of all contacts with whom the given forum is shared. */ /** Returns the IDs of all contacts with whom the given forum is shared. */
Collection<ContactId> getSharedWith(GroupId g) throws DbException; Collection<Contact> getSharedWith(GroupId g) throws DbException;
/** Returns true if the forum not already shared and no invitation is open */ /** Returns true if the forum not already shared and no invitation is open */
boolean canBeShared(GroupId g, Contact c) throws DbException; boolean canBeShared(GroupId g, Contact c) throws DbException;

View File

@@ -0,0 +1,166 @@
package org.briarproject.api.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfEntry;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.sync.GroupId;
import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
import static org.briarproject.api.forum.ForumConstants.INVITATION_MSG;
import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
import static org.briarproject.api.forum.ForumConstants.TYPE;
public interface ForumSharingMessage {
abstract class BaseMessage {
private final GroupId groupId;
private final SessionId sessionId;
public BaseMessage(GroupId groupId, SessionId sessionId) {
this.groupId = groupId;
this.sessionId = sessionId;
}
public BdfList toBdfList() {
return BdfList.of(getType(), getSessionId());
}
public abstract BdfDictionary toBdfDictionary();
protected BdfDictionary toBdfDictionaryHelper() {
return BdfDictionary.of(
new BdfEntry(TYPE, getType()),
new BdfEntry(GROUP_ID, groupId),
new BdfEntry(SESSION_ID, sessionId)
);
}
public static BaseMessage from(GroupId groupId, BdfDictionary d)
throws FormatException {
long type = d.getLong(TYPE);
if (type == SHARE_MSG_TYPE_INVITATION)
return Invitation.from(groupId, d);
else
return SimpleMessage.from(type, groupId, d);
}
public abstract long getType();
public GroupId getGroupId() {
return groupId;
}
public SessionId getSessionId() {
return sessionId;
}
}
class Invitation extends BaseMessage {
private final String forumName;
private final byte[] forumSalt;
private final String message;
public Invitation(GroupId groupId, SessionId sessionId,
String forumName, byte[] forumSalt, String message) {
super(groupId, sessionId);
this.forumName = forumName;
this.forumSalt = forumSalt;
this.message = message;
}
@Override
public long getType() {
return SHARE_MSG_TYPE_INVITATION;
}
@Override
public BdfList toBdfList() {
BdfList list = super.toBdfList();
list.add(forumName);
list.add(forumSalt);
if (message != null) list.add(message);
return list;
}
@Override
public BdfDictionary toBdfDictionary() {
BdfDictionary d = toBdfDictionaryHelper();
d.put(FORUM_NAME, forumName);
d.put(FORUM_SALT, forumSalt);
if (message != null) d.put(INVITATION_MSG, message);
return d;
}
public static Invitation from(GroupId groupId, BdfDictionary d)
throws FormatException {
SessionId sessionId = new SessionId(d.getRaw(SESSION_ID));
String forumName = d.getString(FORUM_NAME);
byte[] forumSalt = d.getRaw(FORUM_SALT);
String message = d.getOptionalString(INVITATION_MSG);
return new Invitation(groupId, sessionId, forumName, forumSalt,
message);
}
public String getForumName() {
return forumName;
}
public byte[] getForumSalt() {
return forumSalt;
}
public String getMessage() {
return message;
}
}
class SimpleMessage extends BaseMessage {
private final long type;
public SimpleMessage(long type, GroupId groupId, SessionId sessionId) {
super(groupId, sessionId);
this.type = type;
}
@Override
public long getType() {
return type;
}
@Override
public BdfDictionary toBdfDictionary() {
return toBdfDictionaryHelper();
}
public static SimpleMessage from(long type, GroupId groupId,
BdfDictionary d) throws FormatException {
if (type != SHARE_MSG_TYPE_ACCEPT &&
type != SHARE_MSG_TYPE_DECLINE &&
type != SHARE_MSG_TYPE_LEAVE &&
type != SHARE_MSG_TYPE_ABORT) throw new FormatException();
SessionId sessionId = new SessionId(d.getRaw(SESSION_ID));
return new SimpleMessage(type, groupId, sessionId);
}
}
}

View File

@@ -1,34 +0,0 @@
package org.briarproject.api.forum;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
public enum InviteeAction {
LOCAL_ACCEPT,
LOCAL_DECLINE,
LOCAL_LEAVE,
LOCAL_ABORT,
REMOTE_INVITATION,
REMOTE_LEAVE,
REMOTE_ABORT;
public static InviteeAction getLocal(long type) {
if (type == SHARE_MSG_TYPE_ACCEPT) return LOCAL_ACCEPT;
if (type == SHARE_MSG_TYPE_DECLINE) return LOCAL_DECLINE;
if (type == SHARE_MSG_TYPE_LEAVE) return LOCAL_LEAVE;
if (type == SHARE_MSG_TYPE_ABORT) return LOCAL_ABORT;
return null;
}
public static InviteeAction getRemote(long type) {
if (type == SHARE_MSG_TYPE_INVITATION) return REMOTE_INVITATION;
if (type == SHARE_MSG_TYPE_LEAVE) return REMOTE_LEAVE;
if (type == SHARE_MSG_TYPE_ABORT) return REMOTE_ABORT;
return null;
}
}

View File

@@ -1,62 +0,0 @@
package org.briarproject.api.forum;
import static org.briarproject.api.forum.InviteeAction.LOCAL_ACCEPT;
import static org.briarproject.api.forum.InviteeAction.LOCAL_DECLINE;
import static org.briarproject.api.forum.InviteeAction.LOCAL_LEAVE;
import static org.briarproject.api.forum.InviteeAction.REMOTE_INVITATION;
import static org.briarproject.api.forum.InviteeAction.REMOTE_LEAVE;
public enum InviteeProtocolState {
ERROR(0),
AWAIT_INVITATION(1) {
@Override
public InviteeProtocolState next(InviteeAction a) {
if (a == REMOTE_INVITATION) return AWAIT_LOCAL_RESPONSE;
return ERROR;
}
},
AWAIT_LOCAL_RESPONSE(2) {
@Override
public InviteeProtocolState next(InviteeAction a) {
if (a == LOCAL_ACCEPT || a == LOCAL_DECLINE) return FINISHED;
if (a == REMOTE_LEAVE) return LEFT;
return ERROR;
}
},
FINISHED(3) {
@Override
public InviteeProtocolState next(InviteeAction a) {
if (a == LOCAL_LEAVE || a == REMOTE_LEAVE) return LEFT;
return FINISHED;
}
},
LEFT(4) {
@Override
public InviteeProtocolState next(InviteeAction a) {
if (a == LOCAL_LEAVE) return ERROR;
return LEFT;
}
};
private final int value;
InviteeProtocolState(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static InviteeProtocolState fromValue(int value) {
for (InviteeProtocolState s : values()) {
if (s.value == value) return s;
}
throw new IllegalArgumentException();
}
public InviteeProtocolState next(InviteeAction a) {
return this;
}
}

View File

@@ -1,34 +0,0 @@
package org.briarproject.api.forum;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
public enum SharerAction {
LOCAL_INVITATION,
LOCAL_LEAVE,
LOCAL_ABORT,
REMOTE_ACCEPT,
REMOTE_DECLINE,
REMOTE_LEAVE,
REMOTE_ABORT;
public static SharerAction getLocal(long type) {
if (type == SHARE_MSG_TYPE_INVITATION) return LOCAL_INVITATION;
if (type == SHARE_MSG_TYPE_LEAVE) return LOCAL_LEAVE;
if (type == SHARE_MSG_TYPE_ABORT) return LOCAL_ABORT;
return null;
}
public static SharerAction getRemote(long type) {
if (type == SHARE_MSG_TYPE_ACCEPT) return REMOTE_ACCEPT;
if (type == SHARE_MSG_TYPE_DECLINE) return REMOTE_DECLINE;
if (type == SHARE_MSG_TYPE_LEAVE) return REMOTE_LEAVE;
if (type == SHARE_MSG_TYPE_ABORT) return REMOTE_ABORT;
return null;
}
}

View File

@@ -1,62 +0,0 @@
package org.briarproject.api.forum;
import static org.briarproject.api.forum.SharerAction.LOCAL_INVITATION;
import static org.briarproject.api.forum.SharerAction.LOCAL_LEAVE;
import static org.briarproject.api.forum.SharerAction.REMOTE_ACCEPT;
import static org.briarproject.api.forum.SharerAction.REMOTE_DECLINE;
import static org.briarproject.api.forum.SharerAction.REMOTE_LEAVE;
public enum SharerProtocolState {
ERROR(0),
PREPARE_INVITATION(1) {
@Override
public SharerProtocolState next(SharerAction a) {
if (a == LOCAL_INVITATION) return AWAIT_RESPONSE;
return ERROR;
}
},
AWAIT_RESPONSE(2) {
@Override
public SharerProtocolState next(SharerAction a) {
if (a == REMOTE_ACCEPT || a == REMOTE_DECLINE) return FINISHED;
if (a == LOCAL_LEAVE) return LEFT;
return ERROR;
}
},
FINISHED(3) {
@Override
public SharerProtocolState next(SharerAction a) {
if (a == LOCAL_LEAVE || a == REMOTE_LEAVE) return LEFT;
return FINISHED;
}
},
LEFT(4) {
@Override
public SharerProtocolState next(SharerAction a) {
if (a == LOCAL_LEAVE) return ERROR;
return LEFT;
}
};
private final int value;
SharerProtocolState(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static SharerProtocolState fromValue(int value) {
for (SharerProtocolState s : values()) {
if (s.value == value) return s;
}
throw new IllegalArgumentException();
}
public SharerProtocolState next(SharerAction a) {
return this;
}
}

View File

@@ -0,0 +1,17 @@
package org.briarproject.api.sync;
import java.util.Collection;
public abstract class BaseMessageContext {
private final Collection<MessageId> dependencies;
public BaseMessageContext(Collection<MessageId> dependencies) {
this.dependencies = dependencies;
}
public Collection<MessageId> getDependencies() {
return dependencies;
}
}

View File

@@ -0,0 +1,20 @@
package org.briarproject.api.sync;
import java.io.IOException;
/** An exception that indicates an invalid message. */
public class InvalidMessageException extends IOException {
public InvalidMessageException() {
super();
}
public InvalidMessageException(String str) {
super(str);
}
public InvalidMessageException(Throwable t) {
super(t);
}
}

View File

@@ -0,0 +1,26 @@
package org.briarproject.api.sync;
import org.briarproject.api.db.Metadata;
import java.util.Collection;
public class MessageContext extends BaseMessageContext {
private final Metadata metadata;
public MessageContext(Metadata metadata,
Collection<MessageId> dependencies) {
super(dependencies);
this.metadata = metadata;
}
public MessageContext(Metadata metadata) {
this(metadata, null);
}
public Metadata getMetadata() {
return metadata;
}
}

View File

@@ -44,10 +44,11 @@ public interface ValidationManager {
interface MessageValidator { interface MessageValidator {
/** /**
* Validates the given message and returns its metadata if the message * Validates the given message and returns its metadata and
* is valid, or null if the message is invalid. * dependencies.
*/ */
Metadata validateMessage(Message m, Group g); MessageContext validateMessage(Message m, Group g)
throws InvalidMessageException;
} }
interface IncomingMessageHook { interface IncomingMessageHook {

View File

@@ -4,10 +4,6 @@ targetCompatibility = 1.6
apply plugin: 'witness' apply plugin: 'witness'
repositories {
jcenter()
}
dependencies { dependencies {
compile project(':briar-api') compile project(':briar-api')
compile fileTree(dir: 'libs', include: '*.jar') compile fileTree(dir: 'libs', include: '*.jar')

View File

@@ -4,13 +4,16 @@ import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.MessageQueueManager.QueueMessageValidator; import org.briarproject.api.clients.MessageQueueManager.QueueMessageValidator;
import org.briarproject.api.clients.QueueMessage; import org.briarproject.api.clients.QueueMessage;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList; import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder; import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message; import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.ValidationManager.MessageValidator; import org.briarproject.api.sync.ValidationManager.MessageValidator;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.api.system.Clock; import org.briarproject.api.system.Clock;
import org.briarproject.util.StringUtils; import org.briarproject.util.StringUtils;
@@ -37,43 +40,41 @@ public abstract class BdfMessageValidator implements MessageValidator,
this.clock = clock; this.clock = clock;
} }
protected abstract BdfDictionary validateMessage(Message m, Group g, protected abstract BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException; BdfList body) throws InvalidMessageException, FormatException;
@Override @Override
public Metadata validateMessage(Message m, Group g) { public MessageContext validateMessage(Message m, Group g)
throws InvalidMessageException {
return validateMessage(m, g, MESSAGE_HEADER_LENGTH); return validateMessage(m, g, MESSAGE_HEADER_LENGTH);
} }
@Override @Override
public Metadata validateMessage(QueueMessage q, Group g) { public MessageContext validateMessage(QueueMessage q, Group g)
throws InvalidMessageException {
return validateMessage(q, g, QUEUE_MESSAGE_HEADER_LENGTH); return validateMessage(q, g, QUEUE_MESSAGE_HEADER_LENGTH);
} }
private Metadata validateMessage(Message m, Group g, int headerLength) { private MessageContext validateMessage(Message m, Group g, int headerLength)
throws InvalidMessageException {
// Reject the message if it's too far in the future // Reject the message if it's too far in the future
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) { if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
LOG.info("Timestamp is too far in the future"); throw new InvalidMessageException(
return null; "Timestamp is too far in the future");
} }
byte[] raw = m.getRaw(); byte[] raw = m.getRaw();
if (raw.length <= headerLength) { if (raw.length <= headerLength) {
LOG.info("Message is too short"); throw new InvalidMessageException("Message is too short");
return null;
} }
try { try {
BdfList body = clientHelper.toList(raw, headerLength, BdfList body = clientHelper.toList(raw, headerLength,
raw.length - headerLength); raw.length - headerLength);
BdfDictionary meta = validateMessage(m, g, body); BdfMessageContext result = validateMessage(m, g, body);
if (meta == null) { Metadata meta = metadataEncoder.encode(result.getDictionary());
LOG.info("Invalid message"); return new MessageContext(meta, result.getDependencies());
return null;
}
return metadataEncoder.encode(meta);
} catch (FormatException e) { } catch (FormatException e) {
LOG.info("Invalid message"); throw new InvalidMessageException(e);
return null;
} }
} }

View File

@@ -14,10 +14,12 @@ import org.briarproject.api.db.Transaction;
import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message; import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.ValidationManager; import org.briarproject.api.sync.ValidationManager;
import org.briarproject.api.sync.ValidationManager.IncomingMessageHook; import org.briarproject.api.sync.ValidationManager.IncomingMessageHook;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.util.ByteUtils; import org.briarproject.util.ByteUtils;
import java.util.ArrayList; import java.util.ArrayList;
@@ -169,7 +171,8 @@ class MessageQueueManagerImpl implements MessageQueueManager {
} }
@Override @Override
public Metadata validateMessage(Message m, Group g) { public MessageContext validateMessage(Message m, Group g)
throws InvalidMessageException {
byte[] raw = m.getRaw(); byte[] raw = m.getRaw();
if (raw.length < QUEUE_MESSAGE_HEADER_LENGTH) return null; if (raw.length < QUEUE_MESSAGE_HEADER_LENGTH) return null;
long queuePosition = ByteUtils.readUint64(raw, long queuePosition = ByteUtils.readUint64(raw,

View File

@@ -0,0 +1,58 @@
package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumFactory;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupFactory;
import org.briarproject.util.StringUtils;
import java.security.SecureRandom;
import javax.inject.Inject;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
class ForumFactoryImpl implements ForumFactory {
private final GroupFactory groupFactory;
private final ClientHelper clientHelper;
private final SecureRandom random;
@Inject
ForumFactoryImpl(GroupFactory groupFactory, ClientHelper clientHelper,
SecureRandom random) {
this.groupFactory = groupFactory;
this.clientHelper = clientHelper;
this.random = random;
}
@Override
public Forum createForum(String name) {
int length = StringUtils.toUtf8(name).length;
if (length == 0) throw new IllegalArgumentException();
if (length > MAX_FORUM_NAME_LENGTH)
throw new IllegalArgumentException();
byte[] salt = new byte[FORUM_SALT_LENGTH];
random.nextBytes(salt);
return createForum(name, salt);
}
@Override
public Forum createForum(String name, byte[] salt) {
try {
BdfList forum = BdfList.of(name, salt);
byte[] descriptor = clientHelper.toByteArray(forum);
Group g = groupFactory
.createGroup(ForumManagerImpl.CLIENT_ID, descriptor);
return new Forum(g, name, salt);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -9,6 +9,7 @@ import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction; import org.briarproject.api.db.Transaction;
import org.briarproject.api.forum.Forum; import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumFactory;
import org.briarproject.api.forum.ForumManager; import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumPost; import org.briarproject.api.forum.ForumPost;
import org.briarproject.api.forum.ForumPostHeader; import org.briarproject.api.forum.ForumPostHeader;
@@ -17,12 +18,10 @@ import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupFactory;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils; import org.briarproject.util.StringUtils;
import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@@ -35,8 +34,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
import static org.briarproject.api.identity.Author.Status.ANONYMOUS; import static org.briarproject.api.identity.Author.Status.ANONYMOUS;
import static org.briarproject.api.identity.Author.Status.UNKNOWN; import static org.briarproject.api.identity.Author.Status.UNKNOWN;
import static org.briarproject.api.identity.Author.Status.VERIFIED; import static org.briarproject.api.identity.Author.Status.VERIFIED;
@@ -49,18 +46,16 @@ class ForumManagerImpl implements ForumManager {
private final DatabaseComponent db; private final DatabaseComponent db;
private final ClientHelper clientHelper; private final ClientHelper clientHelper;
private final GroupFactory groupFactory; private final ForumFactory forumFactory;
private final SecureRandom random;
private final List<RemoveForumHook> removeHooks; private final List<RemoveForumHook> removeHooks;
@Inject @Inject
ForumManagerImpl(DatabaseComponent db, ClientHelper clientHelper, ForumManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
GroupFactory groupFactory, SecureRandom random) { ForumFactory forumFactory) {
this.db = db; this.db = db;
this.clientHelper = clientHelper; this.clientHelper = clientHelper;
this.groupFactory = groupFactory; this.forumFactory = forumFactory;
this.random = random;
removeHooks = new CopyOnWriteArrayList<RemoveForumHook>(); removeHooks = new CopyOnWriteArrayList<RemoveForumHook>();
} }
@@ -70,30 +65,9 @@ class ForumManagerImpl implements ForumManager {
} }
@Override @Override
public Forum createForum(String name) { public Forum addForum(String name) throws DbException {
int length = StringUtils.toUtf8(name).length; Forum f = forumFactory.createForum(name);
if (length == 0) throw new IllegalArgumentException();
if (length > MAX_FORUM_NAME_LENGTH)
throw new IllegalArgumentException();
byte[] salt = new byte[FORUM_SALT_LENGTH];
random.nextBytes(salt);
return createForum(name, salt);
}
@Override
public Forum createForum(String name, byte[] salt) {
try {
BdfList forum = BdfList.of(name, salt);
byte[] descriptor = clientHelper.toByteArray(forum);
Group g = groupFactory.createGroup(getClientId(), descriptor);
return new Forum(g, name, salt);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
@Override
public void addForum(Forum f) throws DbException {
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
db.addGroup(txn, f.getGroup()); db.addGroup(txn, f.getGroup());
@@ -101,6 +75,7 @@ class ForumManagerImpl implements ForumManager {
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
} }
return f;
} }
@Override @Override

View File

@@ -6,6 +6,7 @@ import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.data.MetadataEncoder; import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.forum.ForumFactory;
import org.briarproject.api.forum.ForumManager; import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumPostFactory; import org.briarproject.api.forum.ForumPostFactory;
import org.briarproject.api.forum.ForumSharingManager; import org.briarproject.api.forum.ForumSharingManager;
@@ -38,9 +39,8 @@ public class ForumModule {
@Provides @Provides
@Singleton @Singleton
ForumManager provideForumManager(DatabaseComponent db, ForumManager provideForumManager(DatabaseComponent db,
ClientHelper clientHelper, ClientHelper clientHelper, ForumFactory forumFactory) {
GroupFactory groupFactory, SecureRandom random) { return new ForumManagerImpl(db, clientHelper, forumFactory);
return new ForumManagerImpl(db, clientHelper, groupFactory, random);
} }
@Provides @Provides
@@ -49,6 +49,12 @@ public class ForumModule {
return new ForumPostFactoryImpl(crypto, clientHelper); return new ForumPostFactoryImpl(crypto, clientHelper);
} }
@Provides
ForumFactory provideForumFactory(GroupFactory groupFactory,
ClientHelper clientHelper, SecureRandom random) {
return new ForumFactoryImpl(groupFactory, clientHelper, random);
}
@Provides @Provides
@Singleton @Singleton
ForumPostValidator provideForumPostValidator( ForumPostValidator provideForumPostValidator(
@@ -82,7 +88,7 @@ public class ForumModule {
LifecycleManager lifecycleManager, LifecycleManager lifecycleManager,
ContactManager contactManager, ContactManager contactManager,
MessageQueueManager messageQueueManager, MessageQueueManager messageQueueManager,
ForumManager forumManager, ForumManager forumManager, ForumFactory forumFactory,
ForumSharingManagerImpl forumSharingManager) { ForumSharingManagerImpl forumSharingManager) {
lifecycleManager.registerClient(forumSharingManager); lifecycleManager.registerClient(forumSharingManager);

View File

@@ -3,6 +3,7 @@ package org.briarproject.forum;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.UniqueId; import org.briarproject.api.UniqueId;
import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyParser; import org.briarproject.api.crypto.KeyParser;
import org.briarproject.api.crypto.PublicKey; import org.briarproject.api.crypto.PublicKey;
@@ -13,6 +14,7 @@ import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message; import org.briarproject.api.sync.Message;
import org.briarproject.api.system.Clock; import org.briarproject.api.system.Clock;
import org.briarproject.clients.BdfMessageValidator; import org.briarproject.clients.BdfMessageValidator;
@@ -39,8 +41,8 @@ class ForumPostValidator extends BdfMessageValidator {
} }
@Override @Override
protected BdfDictionary validateMessage(Message m, Group g, protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException { BdfList body) throws InvalidMessageException, FormatException {
// Parent ID, author, content type, forum post body, signature // Parent ID, author, content type, forum post body, signature
checkSize(body, 5); checkSize(body, 5);
// Parent ID is optional // Parent ID is optional
@@ -69,12 +71,10 @@ class ForumPostValidator extends BdfMessageValidator {
checkLength(sig, 0, MAX_SIGNATURE_LENGTH); checkLength(sig, 0, MAX_SIGNATURE_LENGTH);
// If there's an author there must be a signature and vice versa // If there's an author there must be a signature and vice versa
if (author != null && sig == null) { if (author != null && sig == null) {
LOG.info("Author without signature"); throw new InvalidMessageException("Author without signature");
return null;
} }
if (author == null && sig != null) { if (author == null && sig != null) {
LOG.info("Signature without author"); throw new InvalidMessageException("Signature without author");
return null;
} }
// Verify the signature, if any // Verify the signature, if any
if (author != null) { if (author != null) {
@@ -90,12 +90,10 @@ class ForumPostValidator extends BdfMessageValidator {
signature.initVerify(key); signature.initVerify(key);
signature.update(clientHelper.toByteArray(signed)); signature.update(clientHelper.toByteArray(signed));
if (!signature.verify(sig)) { if (!signature.verify(sig)) {
LOG.info("Invalid signature"); throw new InvalidMessageException("Invalid signature");
return null;
} }
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
LOG.info("Invalid public key"); throw new InvalidMessageException("Invalid public key");
return null;
} }
} }
// Return the metadata // Return the metadata
@@ -111,6 +109,6 @@ class ForumPostValidator extends BdfMessageValidator {
} }
meta.put("contentType", contentType); meta.put("contentType", contentType);
meta.put("read", false); meta.put("read", false);
return meta; return new BdfMessageContext(meta);
} }
} }

View File

@@ -23,13 +23,10 @@ import org.briarproject.api.db.NoSuchMessageException;
import org.briarproject.api.db.Transaction; import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.forum.Forum; import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumFactory;
import org.briarproject.api.forum.ForumInvitationMessage; import org.briarproject.api.forum.ForumInvitationMessage;
import org.briarproject.api.forum.ForumManager; import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumSharingManager; import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.forum.InviteeAction;
import org.briarproject.api.forum.InviteeProtocolState;
import org.briarproject.api.forum.SharerAction;
import org.briarproject.api.forum.SharerProtocolState;
import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
@@ -59,11 +56,7 @@ import static java.util.logging.Level.WARNING;
import static org.briarproject.api.clients.ProtocolEngine.StateUpdate; import static org.briarproject.api.clients.ProtocolEngine.StateUpdate;
import static org.briarproject.api.forum.ForumConstants.CONTACT_ID; import static org.briarproject.api.forum.ForumConstants.CONTACT_ID;
import static org.briarproject.api.forum.ForumConstants.FORUM_ID; import static org.briarproject.api.forum.ForumConstants.FORUM_ID;
import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH; import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
import static org.briarproject.api.forum.ForumConstants.INVITATION_MSG;
import static org.briarproject.api.forum.ForumConstants.IS_SHARER; import static org.briarproject.api.forum.ForumConstants.IS_SHARER;
import static org.briarproject.api.forum.ForumConstants.LOCAL; import static org.briarproject.api.forum.ForumConstants.LOCAL;
import static org.briarproject.api.forum.ForumConstants.READ; import static org.briarproject.api.forum.ForumConstants.READ;
@@ -76,10 +69,8 @@ import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION; import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE; import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
import static org.briarproject.api.forum.ForumConstants.STATE; import static org.briarproject.api.forum.ForumConstants.STATE;
import static org.briarproject.api.forum.ForumConstants.STORAGE_ID;
import static org.briarproject.api.forum.ForumConstants.TASK;
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US; import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US;
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_SHARED_FORUM; import static org.briarproject.api.forum.ForumConstants.TASK_ADD_SHARED_FORUM;
import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US; import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US;
import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US; import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US;
@@ -90,9 +81,10 @@ import static org.briarproject.api.forum.ForumConstants.TIME;
import static org.briarproject.api.forum.ForumConstants.TO_BE_SHARED_BY_US; import static org.briarproject.api.forum.ForumConstants.TO_BE_SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.TYPE; import static org.briarproject.api.forum.ForumConstants.TYPE;
import static org.briarproject.api.forum.ForumManager.RemoveForumHook; import static org.briarproject.api.forum.ForumManager.RemoveForumHook;
import static org.briarproject.api.forum.InviteeProtocolState.AWAIT_INVITATION; import static org.briarproject.api.forum.ForumSharingMessage.BaseMessage;
import static org.briarproject.api.forum.InviteeProtocolState.AWAIT_LOCAL_RESPONSE; import static org.briarproject.api.forum.ForumSharingMessage.Invitation;
import static org.briarproject.api.forum.SharerProtocolState.PREPARE_INVITATION; import static org.briarproject.forum.ForumSharingSessionState.fromBdfDictionary;
import static org.briarproject.forum.SharerSessionState.Action;
class ForumSharingManagerImpl extends BdfIncomingMessageHook class ForumSharingManagerImpl extends BdfIncomingMessageHook
implements ForumSharingManager, Client, RemoveForumHook, implements ForumSharingManager, Client, RemoveForumHook,
@@ -111,6 +103,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
private final MetadataEncoder metadataEncoder; private final MetadataEncoder metadataEncoder;
private final SecureRandom random; private final SecureRandom random;
private final PrivateGroupFactory privateGroupFactory; private final PrivateGroupFactory privateGroupFactory;
private final ForumFactory forumFactory;
private final Clock clock; private final Clock clock;
private final Group localGroup; private final Group localGroup;
@@ -119,7 +112,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
MessageQueueManager messageQueueManager, ClientHelper clientHelper, MessageQueueManager messageQueueManager, ClientHelper clientHelper,
MetadataParser metadataParser, MetadataEncoder metadataEncoder, MetadataParser metadataParser, MetadataEncoder metadataEncoder,
SecureRandom random, PrivateGroupFactory privateGroupFactory, SecureRandom random, PrivateGroupFactory privateGroupFactory,
Clock clock) { ForumFactory forumFactory, Clock clock) {
super(clientHelper, metadataParser); super(clientHelper, metadataParser);
this.db = db; this.db = db;
@@ -128,6 +121,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
this.metadataEncoder = metadataEncoder; this.metadataEncoder = metadataEncoder;
this.random = random; this.random = random;
this.privateGroupFactory = privateGroupFactory; this.privateGroupFactory = privateGroupFactory;
this.forumFactory = forumFactory;
this.clock = clock; this.clock = clock;
localGroup = privateGroupFactory.createLocalGroup(getClientId()); localGroup = privateGroupFactory.createLocalGroup(getClientId());
} }
@@ -163,16 +157,18 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
@Override @Override
public void removingContact(Transaction txn, Contact c) throws DbException { public void removingContact(Transaction txn, Contact c) throws DbException {
// query for this contact c
BdfDictionary query = BdfDictionary.of(
new BdfEntry(CONTACT_ID, c.getId().getInt())
);
// clean up session states with that contact from localGroup // clean up session states with that contact from localGroup
Long id = (long) c.getId().getInt();
try { try {
Map<MessageId, BdfDictionary> map = clientHelper Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, localGroup.getId()); .getMessageMetadataAsDictionary(txn, localGroup.getId(),
query);
for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) { for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
BdfDictionary d = entry.getValue(); deleteMessage(txn, entry.getKey());
if (id.equals(d.getLong(CONTACT_ID))) {
deleteMessage(txn, entry.getKey());
}
} }
} catch (FormatException e) { } catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -184,11 +180,12 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
@Override @Override
protected void incomingMessage(Transaction txn, Message m, BdfList body, protected void incomingMessage(Transaction txn, Message m, BdfList body,
BdfDictionary msg) throws DbException, FormatException { BdfDictionary d) throws DbException, FormatException {
SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID)); BaseMessage msg = BaseMessage.from(m.getGroupId(), d);
long type = msg.getLong(TYPE); SessionId sessionId = msg.getSessionId();
if (type == SHARE_MSG_TYPE_INVITATION) {
if (msg.getType() == SHARE_MSG_TYPE_INVITATION) {
// we are an invitee who just received a new invitation // we are an invitee who just received a new invitation
boolean stateExists = true; boolean stateExists = true;
try { try {
@@ -203,43 +200,46 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
if (stateExists) throw new FormatException(); if (stateExists) throw new FormatException();
// check if forum can be shared // check if forum can be shared
Forum f = forumManager.createForum(msg.getString(FORUM_NAME), Invitation invitation = (Invitation) msg;
msg.getRaw(FORUM_SALT)); Forum f = forumFactory.createForum(invitation.getForumName(),
invitation.getForumSalt());
ContactId contactId = getContactId(txn, m.getGroupId()); ContactId contactId = getContactId(txn, m.getGroupId());
Contact contact = db.getContact(txn, contactId); Contact contact = db.getContact(txn, contactId);
if (!canBeShared(txn, f.getId(), contact)) if (!canBeShared(txn, f.getId(), contact))
throw new FormatException(); throw new FormatException();
// initialize state and process invitation // initialize state and process invitation
BdfDictionary state = InviteeSessionState state =
initializeInviteeState(txn, contactId, msg); initializeInviteeState(txn, contactId, invitation);
InviteeEngine engine = new InviteeEngine(); InviteeEngine engine = new InviteeEngine(forumFactory);
processStateUpdate(txn, m.getId(), processInviteeStateUpdate(txn, m.getId(),
engine.onMessageReceived(state, msg)); engine.onMessageReceived(state, msg));
} catch (FormatException e) { } catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
deleteMessage(txn, m.getId()); deleteMessage(txn, m.getId());
} }
} else if (type == SHARE_MSG_TYPE_ACCEPT || } else if (msg.getType() == SHARE_MSG_TYPE_ACCEPT ||
type == SHARE_MSG_TYPE_DECLINE) { msg.getType() == SHARE_MSG_TYPE_DECLINE) {
// we are a sharer who just received a response // we are a sharer who just received a response
BdfDictionary state = getSessionState(txn, sessionId, true); SharerSessionState state = getSessionStateForSharer(txn, sessionId);
SharerEngine engine = new SharerEngine(); SharerEngine engine = new SharerEngine();
processStateUpdate(txn, m.getId(), processSharerStateUpdate(txn, m.getId(),
engine.onMessageReceived(state, msg)); engine.onMessageReceived(state, msg));
} else if (type == SHARE_MSG_TYPE_LEAVE || } else if (msg.getType() == SHARE_MSG_TYPE_LEAVE ||
type == SHARE_MSG_TYPE_ABORT) { msg.getType() == SHARE_MSG_TYPE_ABORT) {
// we don't know who we are, so figure it out // we don't know who we are, so figure it out
BdfDictionary state = getSessionState(txn, sessionId, true); ForumSharingSessionState s = getSessionState(txn, sessionId, true);
if (state.getBoolean(IS_SHARER)) { if (s instanceof SharerSessionState) {
// we are a sharer and the invitee wants to leave or abort // we are a sharer and the invitee wants to leave or abort
SharerSessionState state = (SharerSessionState) s;
SharerEngine engine = new SharerEngine(); SharerEngine engine = new SharerEngine();
processStateUpdate(txn, m.getId(), processSharerStateUpdate(txn, m.getId(),
engine.onMessageReceived(state, msg)); engine.onMessageReceived(state, msg));
} else { } else {
// we are an invitee and the sharer wants to leave or abort // we are an invitee and the sharer wants to leave or abort
InviteeEngine engine = new InviteeEngine(); InviteeSessionState state = (InviteeSessionState) s;
processStateUpdate(txn, m.getId(), InviteeEngine engine = new InviteeEngine(forumFactory);
processInviteeStateUpdate(txn, m.getId(),
engine.onMessageReceived(state, msg)); engine.onMessageReceived(state, msg));
} }
} else { } else {
@@ -261,19 +261,18 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
try { try {
// initialize local state for sharer // initialize local state for sharer
Forum f = forumManager.getForum(txn, groupId); Forum f = forumManager.getForum(txn, groupId);
BdfDictionary localState = initializeSharerState(txn, f, contactId); SharerSessionState localState =
initializeSharerState(txn, f, contactId);
// define action // add invitation message to local state to be available for engine
BdfDictionary localAction = new BdfDictionary();
localAction.put(TYPE, SHARE_MSG_TYPE_INVITATION);
if (!StringUtils.isNullOrEmpty(msg)) { if (!StringUtils.isNullOrEmpty(msg)) {
localAction.put(INVITATION_MSG, msg); localState.setMessage(msg);
} }
// start engine and process its state update // start engine and process its state update
SharerEngine engine = new SharerEngine(); SharerEngine engine = new SharerEngine();
processStateUpdate(txn, null, processSharerStateUpdate(txn, null,
engine.onLocalAction(localState, localAction)); engine.onLocalAction(localState, Action.LOCAL_INVITATION));
txn.setComplete(); txn.setComplete();
} catch (FormatException e) { } catch (FormatException e) {
@@ -284,25 +283,26 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
} }
@Override @Override
public void respondToInvitation(Forum f, boolean accept) public void respondToInvitation(Forum f, Contact c, boolean accept)
throws DbException { throws DbException {
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
// find session state based on forum // find session state based on forum
BdfDictionary localState = getSessionStateForResponse(txn, f); InviteeSessionState localState =
getSessionStateForResponse(txn, f, c);
// define action // define action
BdfDictionary localAction = new BdfDictionary(); InviteeSessionState.Action localAction;
if (accept) { if (accept) {
localAction.put(TYPE, SHARE_MSG_TYPE_ACCEPT); localAction = InviteeSessionState.Action.LOCAL_ACCEPT;
} else { } else {
localAction.put(TYPE, SHARE_MSG_TYPE_DECLINE); localAction = InviteeSessionState.Action.LOCAL_DECLINE;
} }
// start engine and process its state update // start engine and process its state update
InviteeEngine engine = new InviteeEngine(); InviteeEngine engine = new InviteeEngine(forumFactory);
processStateUpdate(txn, null, processInviteeStateUpdate(txn, null,
engine.onLocalAction(localState, localAction)); engine.onLocalAction(localState, localAction));
txn.setComplete(); txn.setComplete();
@@ -317,6 +317,11 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
public Collection<ForumInvitationMessage> getForumInvitationMessages( public Collection<ForumInvitationMessage> getForumInvitationMessages(
ContactId contactId) throws DbException { ContactId contactId) throws DbException {
// query for all invitations
BdfDictionary query = BdfDictionary.of(
new BdfEntry(TYPE, SHARE_MSG_TYPE_INVITATION)
);
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
Contact contact = db.getContact(txn, contactId); Contact contact = db.getContact(txn, contactId);
@@ -325,36 +330,32 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
Collection<ForumInvitationMessage> list = Collection<ForumInvitationMessage> list =
new ArrayList<ForumInvitationMessage>(); new ArrayList<ForumInvitationMessage>();
Map<MessageId, BdfDictionary> map = clientHelper Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, group.getId()); .getMessageMetadataAsDictionary(txn, group.getId(), query);
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) { for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
BdfDictionary msg = m.getValue(); BdfDictionary d = m.getValue();
try { try {
if (msg.getLong(TYPE) != SHARE_MSG_TYPE_INVITATION) Invitation msg = Invitation.from(group.getId(), d);
continue;
MessageStatus status = MessageStatus status =
db.getMessageStatus(txn, contactId, m.getKey()); db.getMessageStatus(txn, contactId, m.getKey());
SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID)); long time = d.getLong(TIME);
String name = msg.getString(FORUM_NAME); boolean local = d.getBoolean(LOCAL);
String message = msg.getOptionalString(INVITATION_MSG); boolean read = d.getBoolean(READ, false);
long time = msg.getLong(TIME);
boolean local = msg.getBoolean(LOCAL);
boolean read = msg.getBoolean(READ, false);
boolean available = false; boolean available = false;
if (!local) { if (!local) {
// figure out whether the forum is still available // figure out whether the forum is still available
BdfDictionary sessionState = ForumSharingSessionState s =
getSessionState(txn, sessionId, true); getSessionState(txn, msg.getSessionId(), true);
InviteeProtocolState state = InviteeProtocolState if (!(s instanceof InviteeSessionState))
.fromValue( continue;
sessionState.getLong(STATE).intValue()); available = ((InviteeSessionState) s).getState() ==
available = state == AWAIT_LOCAL_RESPONSE; InviteeSessionState.State.AWAIT_LOCAL_RESPONSE;
} }
ForumInvitationMessage im = ForumInvitationMessage im =
new ForumInvitationMessage(m.getKey(), sessionId, new ForumInvitationMessage(m.getKey(),
contactId, name, message, available, time, msg.getSessionId(), contactId,
local, status.isSent(), status.isSeen(), msg.getForumName(), msg.getMessage(),
read); available, time, local, status.isSent(),
status.isSeen(), read);
list.add(im); list.add(im);
} catch (FormatException e) { } catch (FormatException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
@@ -441,15 +442,15 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
} }
@Override @Override
public Collection<ContactId> getSharedWith(GroupId g) throws DbException { public Collection<Contact> getSharedWith(GroupId g) throws DbException {
try { try {
List<ContactId> shared = new ArrayList<ContactId>(); List<Contact> shared = new ArrayList<Contact>();
Transaction txn = db.startTransaction(true); Transaction txn = db.startTransaction(true);
try { try {
for (Contact c : db.getContacts(txn)) { for (Contact c : db.getContacts(txn)) {
GroupId contactGroup = getContactGroup(c).getId(); GroupId contactGroup = getContactGroup(c).getId();
if (listContains(txn, contactGroup, g, SHARED_BY_US)) if (listContains(txn, contactGroup, g, SHARED_BY_US))
shared.add(c.getId()); shared.add(c);
} }
txn.setComplete(); txn.setComplete();
} finally { } finally {
@@ -487,7 +488,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
} }
} }
private BdfDictionary initializeSharerState(Transaction txn, Forum f, private SharerSessionState initializeSharerState(Transaction txn, Forum f,
ContactId contactId) throws FormatException, DbException { ContactId contactId) throws FormatException, DbException {
Contact c = db.getContact(txn, contactId); Contact c = db.getContact(txn, contactId);
@@ -499,34 +500,28 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
random.nextBytes(salt.getBytes()); random.nextBytes(salt.getBytes());
Message m = clientHelper.createMessage(localGroup.getId(), now, Message m = clientHelper.createMessage(localGroup.getId(), now,
BdfList.of(salt)); BdfList.of(salt));
MessageId sessionId = m.getId(); SessionId sessionId = new SessionId(m.getId().getBytes());
BdfDictionary d = new BdfDictionary(); SharerSessionState s = new SharerSessionState(sessionId, sessionId,
d.put(SESSION_ID, sessionId); group.getId(), SharerSessionState.State.PREPARE_INVITATION,
d.put(STORAGE_ID, sessionId); contactId, f.getId(), f.getName(), f.getSalt());
d.put(GROUP_ID, group.getId());
d.put(IS_SHARER, true);
d.put(STATE, PREPARE_INVITATION.getValue());
d.put(CONTACT_ID, contactId.getInt());
d.put(FORUM_ID, f.getId());
d.put(FORUM_NAME, f.getName());
d.put(FORUM_SALT, f.getSalt());
// save local state to database // save local state to database
BdfDictionary d = s.toBdfDictionary();
clientHelper.addLocalMessage(txn, m, getClientId(), d, false); clientHelper.addLocalMessage(txn, m, getClientId(), d, false);
return d; return s;
} }
private BdfDictionary initializeInviteeState(Transaction txn, private InviteeSessionState initializeInviteeState(Transaction txn,
ContactId contactId, BdfDictionary msg) ContactId contactId, Invitation msg)
throws FormatException, DbException { throws FormatException, DbException {
Contact c = db.getContact(txn, contactId); Contact c = db.getContact(txn, contactId);
Group group = getContactGroup(c); Group group = getContactGroup(c);
String name = msg.getString(FORUM_NAME); String name = msg.getForumName();
byte[] salt = msg.getRaw(FORUM_SALT); byte[] salt = msg.getForumSalt();
Forum f = forumManager.createForum(name, salt); Forum f = forumFactory.createForum(name, salt);
// create local message to keep engine state // create local message to keep engine state
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
@@ -535,105 +530,129 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
Message m = clientHelper.createMessage(localGroup.getId(), now, Message m = clientHelper.createMessage(localGroup.getId(), now,
BdfList.of(mSalt)); BdfList.of(mSalt));
BdfDictionary d = new BdfDictionary(); InviteeSessionState s = new InviteeSessionState(msg.getSessionId(),
d.put(SESSION_ID, msg.getRaw(SESSION_ID)); m.getId(), group.getId(),
d.put(STORAGE_ID, m.getId()); InviteeSessionState.State.AWAIT_INVITATION, contactId,
d.put(GROUP_ID, group.getId()); f.getId(), f.getName(), f.getSalt());
d.put(IS_SHARER, false);
d.put(STATE, AWAIT_INVITATION.getValue());
d.put(CONTACT_ID, contactId.getInt());
d.put(FORUM_ID, f.getId());
d.put(FORUM_NAME, name);
d.put(FORUM_SALT, salt);
// save local state to database // save local state to database
BdfDictionary d = s.toBdfDictionary();
clientHelper.addLocalMessage(txn, m, getClientId(), d, false); clientHelper.addLocalMessage(txn, m, getClientId(), d, false);
return d; return s;
} }
private BdfDictionary getSessionState(Transaction txn, SessionId sessionId, private ForumSharingSessionState getSessionState(Transaction txn,
boolean warn) throws DbException, FormatException { SessionId sessionId, boolean warn)
try {
// we should be able to get the sharer state directly from sessionId
return clientHelper.getMessageMetadataAsDictionary(txn, sessionId);
} catch (NoSuchMessageException e) {
// State not found directly, so iterate over all states
// to find state for invitee
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, localGroup.getId());
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
BdfDictionary state = m.getValue();
if (Arrays.equals(state.getRaw(SESSION_ID),
sessionId.getBytes())) {
return state;
}
}
if (warn && LOG.isLoggable(WARNING)) {
LOG.warning(
"No session state found for message with session ID " +
Arrays.hashCode(sessionId.getBytes()));
}
throw new FormatException();
}
}
private BdfDictionary getSessionStateForResponse(Transaction txn, Forum f)
throws DbException, FormatException { throws DbException, FormatException {
Map<MessageId, BdfDictionary> map = clientHelper try {
.getMessageMetadataAsDictionary(txn, localGroup.getId()); return getSessionStateForSharer(txn, sessionId);
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) { } catch (NoSuchMessageException e) {
BdfDictionary d = m.getValue(); // State not found directly, so query for state for invitee
try { BdfDictionary query = BdfDictionary.of(
InviteeProtocolState state = InviteeProtocolState new BdfEntry(SESSION_ID, sessionId)
.fromValue(d.getLong(STATE).intValue()); );
if (state == AWAIT_LOCAL_RESPONSE) {
byte[] id = d.getRaw(FORUM_ID); Map<MessageId, BdfDictionary> map = clientHelper
if (Arrays.equals(f.getId().getBytes(), id)) { .getMessageMetadataAsDictionary(txn, localGroup.getId(),
// Note that there should always be only one session query);
// in this state for the same forum
return d; if (map.size() > 1 && LOG.isLoggable(WARNING)) {
} LOG.warning(
} "More than one session state found for message with session ID " +
} catch (FormatException e) { Arrays.hashCode(sessionId.getBytes()));
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} }
if (map.isEmpty()) {
if (warn && LOG.isLoggable(WARNING)) {
LOG.warning(
"No session state found for message with session ID " +
Arrays.hashCode(sessionId.getBytes()));
}
throw new FormatException();
}
return fromBdfDictionary(map.values().iterator().next());
} }
throw new DbException();
} }
private BdfDictionary getSessionStateForLeaving(Transaction txn, Forum f, private SharerSessionState getSessionStateForSharer(Transaction txn,
ContactId c) throws DbException, FormatException { SessionId sessionId)
throws DbException, FormatException {
// we should be able to get the sharer state directly from sessionId
BdfDictionary d =
clientHelper.getMessageMetadataAsDictionary(txn, sessionId);
if (!d.getBoolean(IS_SHARER)) throw new FormatException();
return (SharerSessionState) fromBdfDictionary(d);
}
private InviteeSessionState getSessionStateForResponse(Transaction txn,
Forum f, Contact c) throws DbException, FormatException {
// query for invitee states for that forum in state await response
BdfDictionary query = BdfDictionary.of(
new BdfEntry(IS_SHARER, false),
new BdfEntry(CONTACT_ID, c.getId().getInt()),
new BdfEntry(FORUM_ID, f.getId()),
new BdfEntry(STATE,
InviteeSessionState.State.AWAIT_LOCAL_RESPONSE
.getValue())
);
Map<MessageId, BdfDictionary> map = clientHelper Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, localGroup.getId()); .getMessageMetadataAsDictionary(txn, localGroup.getId(), query);
if (map.size() > 1 && LOG.isLoggable(WARNING)) {
LOG.warning(
"More than one session state found for forum with ID " +
Arrays.hashCode(f.getId().getBytes()) +
" in state AWAIT_LOCAL_RESPONSE for contact " +
c.getAuthor().getName());
}
if (map.isEmpty()) {
if (LOG.isLoggable(WARNING)) {
LOG.warning(
"No session state found for forum with ID " +
Arrays.hashCode(f.getId().getBytes()) +
" in state AWAIT_LOCAL_RESPONSE");
}
throw new DbException();
}
return (InviteeSessionState) fromBdfDictionary(
map.values().iterator().next());
}
private ForumSharingSessionState getSessionStateForLeaving(Transaction txn,
Forum f, ContactId c) throws DbException, FormatException {
BdfDictionary query = BdfDictionary.of(
new BdfEntry(CONTACT_ID, c.getInt()),
new BdfEntry(FORUM_ID, f.getId())
);
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, localGroup.getId(), query);
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) { for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
BdfDictionary d = m.getValue(); BdfDictionary d = m.getValue();
try { try {
// check that this session is with the right contact ForumSharingSessionState s = fromBdfDictionary(d);
if (c.getInt() != d.getLong(CONTACT_ID)) continue;
// check that a forum get be left in current session // check that a forum get be left in current session
int intState = d.getLong(STATE).intValue(); if (s instanceof SharerSessionState) {
if (d.getBoolean(IS_SHARER)) { SharerSessionState state = (SharerSessionState) s;
SharerProtocolState state = SharerSessionState.State nextState =
SharerProtocolState.fromValue(intState); state.getState().next(Action.LOCAL_LEAVE);
if (state.next(SharerAction.LOCAL_LEAVE) == if (nextState != SharerSessionState.State.ERROR) {
SharerProtocolState.ERROR) continue; return state;
}
} else { } else {
InviteeProtocolState state = InviteeProtocolState InviteeSessionState state = (InviteeSessionState) s;
.fromValue(intState); InviteeSessionState.State nextState = state.getState()
if (state.next(InviteeAction.LOCAL_LEAVE) == .next(InviteeSessionState.Action.LOCAL_LEAVE);
InviteeProtocolState.ERROR) continue; if (nextState != InviteeSessionState.State.ERROR) {
} return state;
// check that this state actually concerns this forum }
String name = d.getString(FORUM_NAME);
byte[] salt = d.getRaw(FORUM_SALT);
if (name.equals(f.getName()) &&
Arrays.equals(salt, f.getSalt())) {
// TODO what happens when there is more than one invitation?
return d;
} }
} catch (FormatException e) { } catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -643,20 +662,20 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
} }
private void processStateUpdate(Transaction txn, MessageId messageId, private void processStateUpdate(Transaction txn, MessageId messageId,
StateUpdate<BdfDictionary, BdfDictionary> result) StateUpdate<ForumSharingSessionState, BaseMessage> result)
throws DbException, FormatException { throws DbException, FormatException {
// perform actions based on new local state // perform actions based on new local state
performTasks(txn, result.localState); performTasks(txn, result.localState);
// save new local state // save new local state
MessageId storageId = MessageId storageId = result.localState.getStorageId();
new MessageId(result.localState.getRaw(STORAGE_ID)); clientHelper.mergeMessageMetadata(txn, storageId,
clientHelper.mergeMessageMetadata(txn, storageId, result.localState); result.localState.toBdfDictionary());
// send messages // send messages
for (BdfDictionary d : result.toSend) { for (BaseMessage msg : result.toSend) {
sendMessage(txn, d); sendMessage(txn, msg);
} }
// broadcast events // broadcast events
@@ -674,25 +693,48 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
} }
} }
private void performTasks(Transaction txn, BdfDictionary localState) private void processSharerStateUpdate(Transaction txn, MessageId messageId,
StateUpdate<SharerSessionState, BaseMessage> result)
throws DbException, FormatException {
StateUpdate<ForumSharingSessionState, BaseMessage> r =
new StateUpdate<ForumSharingSessionState, BaseMessage>(
result.deleteMessage, result.deleteState,
result.localState, result.toSend, result.toBroadcast);
processStateUpdate(txn, messageId, r);
}
private void processInviteeStateUpdate(Transaction txn, MessageId messageId,
StateUpdate<InviteeSessionState, BaseMessage> result)
throws DbException, FormatException {
StateUpdate<ForumSharingSessionState, BaseMessage> r =
new StateUpdate<ForumSharingSessionState, BaseMessage>(
result.deleteMessage, result.deleteState,
result.localState, result.toSend, result.toBroadcast);
processStateUpdate(txn, messageId, r);
}
private void performTasks(Transaction txn, ForumSharingSessionState localState)
throws FormatException, DbException { throws FormatException, DbException {
if (!localState.containsKey(TASK)) return; if (localState.getTask() == -1) return;
// remember task and remove it from localState // remember task and remove it from localState
long task = localState.getLong(TASK); long task = localState.getTask();
localState.put(TASK, BdfDictionary.NULL_VALUE); localState.setTask(-1);
// get group ID for later // get group ID for later
GroupId groupId = new GroupId(localState.getRaw(GROUP_ID)); GroupId groupId = localState.getGroupId();
// get contact ID for later // get contact ID for later
ContactId contactId = ContactId contactId = localState.getContactId();
new ContactId(localState.getLong(CONTACT_ID).intValue());
// get forum for later // get forum for later
String name = localState.getString(FORUM_NAME); String name = localState.getForumName();
byte[] salt = localState.getRaw(FORUM_SALT); byte[] salt = localState.getForumSalt();
Forum f = forumManager.createForum(name, salt); Forum f = forumFactory.createForum(name, salt);
// perform tasks // perform tasks
if (task == TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US) { if (task == TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US) {
@@ -725,50 +767,23 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
} }
} }
private void sendMessage(Transaction txn, BdfDictionary m) private void sendMessage(Transaction txn, BaseMessage m)
throws FormatException, DbException { throws FormatException, DbException {
BdfList list = encodeMessage(m); byte[] body = clientHelper.toByteArray(m.toBdfList());
byte[] body = clientHelper.toByteArray(list); Group group = db.getGroup(txn, m.getGroupId());
GroupId groupId = new GroupId(m.getRaw(GROUP_ID));
Group group = db.getGroup(txn, groupId);
long timestamp = clock.currentTimeMillis(); long timestamp = clock.currentTimeMillis();
// add message itself as metadata // add message itself as metadata
m.put(LOCAL, true); BdfDictionary d = m.toBdfDictionary();
m.put(TIME, timestamp); d.put(LOCAL, true);
Metadata meta = metadataEncoder.encode(m); d.put(TIME, timestamp);
Metadata meta = metadataEncoder.encode(d);
messageQueueManager messageQueueManager
.sendMessage(txn, group, timestamp, body, meta); .sendMessage(txn, group, timestamp, body, meta);
} }
private BdfList encodeMessage(BdfDictionary m) throws FormatException {
long type = m.getLong(TYPE);
BdfList list;
if (type == SHARE_MSG_TYPE_INVITATION) {
list = BdfList.of(type,
m.getRaw(SESSION_ID),
m.getString(FORUM_NAME),
m.getRaw(FORUM_SALT)
);
String msg = m.getOptionalString(INVITATION_MSG);
if (msg != null) list.add(msg);
} else if (type == SHARE_MSG_TYPE_ACCEPT) {
list = BdfList.of(type, m.getRaw(SESSION_ID));
} else if (type == SHARE_MSG_TYPE_DECLINE) {
list = BdfList.of(type, m.getRaw(SESSION_ID));
} else if (type == SHARE_MSG_TYPE_LEAVE) {
list = BdfList.of(type, m.getRaw(SESSION_ID));
} else if (type == SHARE_MSG_TYPE_ABORT) {
list = BdfList.of(type, m.getRaw(SESSION_ID));
} else {
throw new FormatException();
}
return list;
}
private Group getContactGroup(Contact c) { private Group getContactGroup(Contact c) {
return privateGroupFactory.createPrivateGroup(CLIENT_ID, c); return privateGroupFactory.createPrivateGroup(CLIENT_ID, c);
} }
@@ -783,17 +798,18 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
private void leaveForum(Transaction txn, ContactId c, Forum f) private void leaveForum(Transaction txn, ContactId c, Forum f)
throws DbException, FormatException { throws DbException, FormatException {
BdfDictionary state = getSessionStateForLeaving(txn, f, c); ForumSharingSessionState state = getSessionStateForLeaving(txn, f, c);
BdfDictionary action = new BdfDictionary(); if (state instanceof SharerSessionState) {
action.put(TYPE, SHARE_MSG_TYPE_LEAVE); Action action = Action.LOCAL_LEAVE;
if (state.getBoolean(IS_SHARER)) {
SharerEngine engine = new SharerEngine(); SharerEngine engine = new SharerEngine();
processStateUpdate(txn, null, processSharerStateUpdate(txn, null,
engine.onLocalAction(state, action)); engine.onLocalAction((SharerSessionState) state, action));
} else { } else {
InviteeEngine engine = new InviteeEngine(); InviteeSessionState.Action action =
processStateUpdate(txn, null, InviteeSessionState.Action.LOCAL_LEAVE;
engine.onLocalAction(state, action)); InviteeEngine engine = new InviteeEngine(forumFactory);
processInviteeStateUpdate(txn, null,
engine.onLocalAction((InviteeSessionState) state, action));
} }
} }
@@ -859,7 +875,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
List<Forum> forums = new ArrayList<Forum>(list.size()); List<Forum> forums = new ArrayList<Forum>(list.size());
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
BdfList forum = list.getList(i); BdfList forum = list.getList(i);
forums.add(forumManager forums.add(forumFactory
.createForum(forum.getString(0), forum.getRaw(1))); .createForum(forum.getString(0), forum.getRaw(1)));
} }
return forums; return forums;

View File

@@ -0,0 +1,119 @@
package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import static org.briarproject.api.forum.ForumConstants.CONTACT_ID;
import static org.briarproject.api.forum.ForumConstants.FORUM_ID;
import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
import static org.briarproject.api.forum.ForumConstants.IS_SHARER;
import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
import static org.briarproject.api.forum.ForumConstants.STATE;
import static org.briarproject.api.forum.ForumConstants.STORAGE_ID;
// This class is not thread-safe
public abstract class ForumSharingSessionState {
private final SessionId sessionId;
private final MessageId storageId;
private final GroupId groupId;
private final ContactId contactId;
private final GroupId forumId;
private final String forumName;
private final byte[] forumSalt;
private int task = -1; // TODO get rid of task, see #376
public ForumSharingSessionState(SessionId sessionId, MessageId storageId,
GroupId groupId, ContactId contactId, GroupId forumId,
String forumName, byte[] forumSalt) {
this.sessionId = sessionId;
this.storageId = storageId;
this.groupId = groupId;
this.contactId = contactId;
this.forumId = forumId;
this.forumName = forumName;
this.forumSalt = forumSalt;
}
public static ForumSharingSessionState fromBdfDictionary(BdfDictionary d)
throws FormatException{
SessionId sessionId = new SessionId(d.getRaw(SESSION_ID));
MessageId messageId = new MessageId(d.getRaw(STORAGE_ID));
GroupId groupId = new GroupId(d.getRaw(GROUP_ID));
ContactId contactId = new ContactId(d.getLong(CONTACT_ID).intValue());
GroupId forumId = new GroupId(d.getRaw(FORUM_ID));
String forumName = d.getString(FORUM_NAME);
byte[] forumSalt = d.getRaw(FORUM_SALT);
int intState = d.getLong(STATE).intValue();
if (d.getBoolean(IS_SHARER)) {
SharerSessionState.State state =
SharerSessionState.State.fromValue(intState);
return new SharerSessionState(sessionId, messageId, groupId, state,
contactId, forumId, forumName, forumSalt);
} else {
InviteeSessionState.State state =
InviteeSessionState.State.fromValue(intState);
return new InviteeSessionState(sessionId, messageId, groupId, state,
contactId, forumId, forumName, forumSalt);
}
}
public BdfDictionary toBdfDictionary() {
BdfDictionary d = new BdfDictionary();
d.put(SESSION_ID, getSessionId());
d.put(STORAGE_ID, getStorageId());
d.put(GROUP_ID, getGroupId());
d.put(CONTACT_ID, getContactId().getInt());
d.put(FORUM_ID, getForumId());
d.put(FORUM_NAME, getForumName());
d.put(FORUM_SALT, getForumSalt());
return d;
}
public SessionId getSessionId() {
return sessionId;
}
public MessageId getStorageId() {
return storageId;
}
public GroupId getGroupId() {
return groupId;
}
public ContactId getContactId() {
return contactId;
}
public GroupId getForumId() {
return forumId;
}
public String getForumName() {
return forumName;
}
public byte[] getForumSalt() {
return forumSalt;
}
public void setTask(int task) {
this.task = task;
}
public int getTask() {
return task;
}
}

View File

@@ -3,6 +3,7 @@ package org.briarproject.forum;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.SessionId; import org.briarproject.api.clients.SessionId;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList; import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder; import org.briarproject.api.data.MetadataEncoder;
@@ -36,7 +37,7 @@ class ForumSharingValidator extends BdfMessageValidator {
} }
@Override @Override
protected BdfDictionary validateMessage(Message m, Group g, protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException { BdfList body) throws FormatException {
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
@@ -73,9 +74,8 @@ class ForumSharingValidator extends BdfMessageValidator {
// Return the metadata // Return the metadata
d.put(TYPE, type); d.put(TYPE, type);
d.put(SESSION_ID, id); d.put(SESSION_ID, id);
d.put(GROUP_ID, m.getGroupId());
d.put(LOCAL, false); d.put(LOCAL, false);
d.put(TIME, m.getTimestamp()); d.put(TIME, m.getTimestamp());
return d; return new BdfMessageContext(d);
} }
} }

View File

@@ -3,65 +3,59 @@ package org.briarproject.forum;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ProtocolEngine; import org.briarproject.api.clients.ProtocolEngine;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfEntry;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.ForumInvitationReceivedEvent; import org.briarproject.api.event.ForumInvitationReceivedEvent;
import org.briarproject.api.forum.Forum; import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.InviteeAction; import org.briarproject.api.forum.ForumFactory;
import org.briarproject.api.forum.InviteeProtocolState;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.api.forum.ForumConstants.CONTACT_ID;
import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT; import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT; import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE; import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION; import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE; import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
import static org.briarproject.api.forum.ForumConstants.STATE;
import static org.briarproject.api.forum.ForumConstants.TASK;
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US; import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US;
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_SHARED_FORUM; import static org.briarproject.api.forum.ForumConstants.TASK_ADD_SHARED_FORUM;
import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US; import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US;
import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_WITH_US; import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_WITH_US;
import static org.briarproject.api.forum.ForumConstants.TYPE; import static org.briarproject.api.forum.ForumSharingMessage.SimpleMessage;
import static org.briarproject.api.forum.InviteeAction.LOCAL_ABORT; import static org.briarproject.api.forum.ForumSharingMessage.BaseMessage;
import static org.briarproject.api.forum.InviteeAction.LOCAL_ACCEPT; import static org.briarproject.forum.InviteeSessionState.Action;
import static org.briarproject.api.forum.InviteeAction.LOCAL_DECLINE; import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_ABORT;
import static org.briarproject.api.forum.InviteeAction.LOCAL_LEAVE; import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_ACCEPT;
import static org.briarproject.api.forum.InviteeAction.REMOTE_INVITATION; import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_DECLINE;
import static org.briarproject.api.forum.InviteeAction.REMOTE_LEAVE; import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_LEAVE;
import static org.briarproject.api.forum.InviteeProtocolState.ERROR; import static org.briarproject.forum.InviteeSessionState.Action.REMOTE_INVITATION;
import static org.briarproject.api.forum.InviteeProtocolState.FINISHED; import static org.briarproject.forum.InviteeSessionState.Action.REMOTE_LEAVE;
import static org.briarproject.api.forum.InviteeProtocolState.LEFT; import static org.briarproject.forum.InviteeSessionState.State;
import static org.briarproject.forum.InviteeSessionState.State.ERROR;
import static org.briarproject.forum.InviteeSessionState.State.FINISHED;
import static org.briarproject.forum.InviteeSessionState.State.LEFT;
public class InviteeEngine public class InviteeEngine
implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> { implements ProtocolEngine<Action, InviteeSessionState, BaseMessage> {
private final ForumFactory forumFactory;
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(SharerEngine.class.getName()); Logger.getLogger(SharerEngine.class.getName());
InviteeEngine(ForumFactory forumFactory) {
this.forumFactory = forumFactory;
}
@Override @Override
public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction( public StateUpdate<InviteeSessionState, BaseMessage> onLocalAction(
BdfDictionary localState, BdfDictionary localAction) { InviteeSessionState localState, Action action) {
try { try {
InviteeProtocolState currentState = State currentState = localState.getState();
getState(localState.getLong(STATE)); State nextState = currentState.next(action);
long type = localAction.getLong(TYPE); localState.setState(nextState);
InviteeAction action = InviteeAction.getLocal(type);
InviteeProtocolState nextState = currentState.next(action);
localState.put(STATE, nextState.getValue());
if (action == LOCAL_ABORT && currentState != ERROR) { if (action == LOCAL_ABORT && currentState != ERROR) {
return abortSession(currentState, localState); return abortSession(currentState, localState);
@@ -74,37 +68,34 @@ public class InviteeEngine
} }
return noUpdate(localState, true); return noUpdate(localState, true);
} }
List<BdfDictionary> messages; List<BaseMessage> messages;
List<Event> events = Collections.emptyList(); List<Event> events = Collections.emptyList();
if (action == LOCAL_ACCEPT || action == LOCAL_DECLINE) { if (action == LOCAL_ACCEPT || action == LOCAL_DECLINE) {
BdfDictionary msg = BdfDictionary.of( BaseMessage msg;
new BdfEntry(SESSION_ID, localState.getRaw(SESSION_ID)),
new BdfEntry(GROUP_ID, localState.getRaw(GROUP_ID))
);
if (action == LOCAL_ACCEPT) { if (action == LOCAL_ACCEPT) {
localState.put(TASK, TASK_ADD_SHARED_FORUM); localState.setTask(TASK_ADD_SHARED_FORUM);
msg.put(TYPE, SHARE_MSG_TYPE_ACCEPT); msg = new SimpleMessage(SHARE_MSG_TYPE_ACCEPT,
localState.getGroupId(), localState.getSessionId());
} else { } else {
localState.put(TASK, localState.setTask(
TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US); TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US);
msg.put(TYPE, SHARE_MSG_TYPE_DECLINE); msg = new SimpleMessage(SHARE_MSG_TYPE_DECLINE,
localState.getGroupId(), localState.getSessionId());
} }
messages = Collections.singletonList(msg); messages = Collections.singletonList(msg);
logLocalAction(currentState, localState, msg); logLocalAction(currentState, localState, msg);
} }
else if (action == LOCAL_LEAVE) { else if (action == LOCAL_LEAVE) {
BdfDictionary msg = new BdfDictionary(); BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_LEAVE,
msg.put(TYPE, SHARE_MSG_TYPE_LEAVE); localState.getGroupId(), localState.getSessionId());
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
messages = Collections.singletonList(msg); messages = Collections.singletonList(msg);
logLocalAction(currentState, localState, msg); logLocalAction(currentState, localState, msg);
} }
else { else {
throw new IllegalArgumentException("Unknown Local Action"); throw new IllegalArgumentException("Unknown Local Action");
} }
return new StateUpdate<BdfDictionary, BdfDictionary>(false, return new StateUpdate<InviteeSessionState, BaseMessage>(false,
false, localState, messages, events); false, localState, messages, events);
} catch (FormatException e) { } catch (FormatException e) {
throw new IllegalArgumentException(e); throw new IllegalArgumentException(e);
@@ -112,18 +103,16 @@ public class InviteeEngine
} }
@Override @Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived( public StateUpdate<InviteeSessionState, BaseMessage> onMessageReceived(
BdfDictionary localState, BdfDictionary msg) { InviteeSessionState localState, BaseMessage msg) {
try { try {
InviteeProtocolState currentState = State currentState = localState.getState();
getState(localState.getLong(STATE)); Action action = Action.getRemote(msg.getType());
long type = msg.getLong(TYPE); State nextState = currentState.next(action);
InviteeAction action = InviteeAction.getRemote(type); localState.setState(nextState);
InviteeProtocolState nextState = currentState.next(action);
localState.put(STATE, nextState.getValue());
logMessageReceived(currentState, nextState, type, msg); logMessageReceived(currentState, nextState, msg.getType(), msg);
if (nextState == ERROR) { if (nextState == ERROR) {
if (currentState != ERROR) { if (currentState != ERROR) {
@@ -133,7 +122,7 @@ public class InviteeEngine
} }
} }
List<BdfDictionary> messages = Collections.emptyList(); List<BaseMessage> messages = Collections.emptyList();
List<Event> events = Collections.emptyList(); List<Event> events = Collections.emptyList();
boolean deleteMsg = false; boolean deleteMsg = false;
@@ -143,7 +132,7 @@ public class InviteeEngine
} }
// the sharer left the forum she had shared with us // the sharer left the forum she had shared with us
else if (action == REMOTE_LEAVE && currentState == FINISHED) { else if (action == REMOTE_LEAVE && currentState == FINISHED) {
localState.put(TASK, TASK_UNSHARE_FORUM_SHARED_WITH_US); localState.setTask(TASK_UNSHARE_FORUM_SHARED_WITH_US);
} }
else if (currentState == FINISHED) { else if (currentState == FINISHED) {
// ignore and delete messages coming in while in that state // ignore and delete messages coming in while in that state
@@ -152,74 +141,65 @@ public class InviteeEngine
} }
// the sharer left the forum before we couldn't even respond // the sharer left the forum before we couldn't even respond
else if (action == REMOTE_LEAVE) { else if (action == REMOTE_LEAVE) {
localState.put(TASK, TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US); localState.setTask(TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US);
} }
// we have just received our invitation // we have just received our invitation
else if (action == REMOTE_INVITATION) { else if (action == REMOTE_INVITATION) {
localState.put(TASK, TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US); Forum forum = forumFactory
// TODO how to get the proper group here? .createForum(localState.getForumName(),
Forum forum = new Forum(null, localState.getString(FORUM_NAME), localState.getForumSalt());
localState.getRaw(FORUM_SALT)); localState.setTask(TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US);
ContactId contactId = new ContactId( ContactId contactId = localState.getContactId();
localState.getLong(CONTACT_ID).intValue());
Event event = new ForumInvitationReceivedEvent(forum, contactId); Event event = new ForumInvitationReceivedEvent(forum, contactId);
events = Collections.singletonList(event); events = Collections.singletonList(event);
} }
else { else {
throw new IllegalArgumentException("Bad state"); throw new IllegalArgumentException("Bad state");
} }
return new StateUpdate<BdfDictionary, BdfDictionary>(deleteMsg, return new StateUpdate<InviteeSessionState, BaseMessage>(deleteMsg,
false, localState, messages, events); false, localState, messages, events);
} catch (FormatException e) { } catch (FormatException e) {
throw new IllegalArgumentException(e); throw new IllegalArgumentException(e);
} }
} }
private void logLocalAction(InviteeProtocolState state, private void logLocalAction(State state,
BdfDictionary localState, BdfDictionary msg) { InviteeSessionState localState, BaseMessage msg) {
if (!LOG.isLoggable(INFO)) return; if (!LOG.isLoggable(INFO)) return;
String a = "response"; String a = "response";
if (msg.getLong(TYPE, -1L) == SHARE_MSG_TYPE_LEAVE) a = "leave"; if (msg.getType() == SHARE_MSG_TYPE_LEAVE) a = "leave";
try { LOG.info("Sending " + a + " in state " + state.name() +
LOG.info("Sending " + a + " in state " + state.name() + " with session ID " +
" with session ID " + msg.getSessionId().hashCode() + " in group " +
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " + msg.getGroupId().hashCode() + ". " +
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " + "Moving on to state " + localState.getState().name()
"Moving on to state " + );
getState(localState.getLong(STATE)).name()
);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
} }
private void logMessageReceived(InviteeProtocolState currentState, private void logMessageReceived(State currentState, State nextState,
InviteeProtocolState nextState, long type, BdfDictionary msg) { long type, BaseMessage msg) {
if (!LOG.isLoggable(INFO)) return; if (!LOG.isLoggable(INFO)) return;
try { String t = "unknown";
String t = "unknown"; if (type == SHARE_MSG_TYPE_INVITATION) t = "INVITE";
if (type == SHARE_MSG_TYPE_INVITATION) t = "INVITE"; else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE";
else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE"; else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT";
else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT";
LOG.info("Received " + t + " in state " + currentState.name() + LOG.info("Received " + t + " in state " + currentState.name() +
" with session ID " + " with session ID " +
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " + msg.getSessionId().hashCode() + " in group " +
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " + msg.getGroupId().hashCode() + ". " +
"Moving on to state " + nextState.name() "Moving on to state " + nextState.name()
); );
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
} }
@Override @Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered( public StateUpdate<InviteeSessionState, BaseMessage> onMessageDelivered(
BdfDictionary localState, BdfDictionary delivered) { InviteeSessionState localState, BaseMessage delivered) {
try { try {
return noUpdate(localState, false); return noUpdate(localState, false);
} catch (FormatException e) { } catch (FormatException e) {
@@ -228,38 +208,32 @@ public class InviteeEngine
} }
} }
private InviteeProtocolState getState(Long state) { private StateUpdate<InviteeSessionState, BaseMessage> abortSession(
return InviteeProtocolState.fromValue(state.intValue()); State currentState, InviteeSessionState localState)
}
private StateUpdate<BdfDictionary, BdfDictionary> abortSession(
InviteeProtocolState currentState, BdfDictionary localState)
throws FormatException { throws FormatException {
if (LOG.isLoggable(WARNING)) { if (LOG.isLoggable(WARNING)) {
LOG.warning("Aborting protocol session " + LOG.warning("Aborting protocol session " +
Arrays.hashCode(localState.getRaw(SESSION_ID)) + localState.getSessionId().hashCode() +
" in state " + currentState.name()); " in state " + currentState.name());
} }
localState.setState(ERROR);
localState.put(STATE, ERROR.getValue()); BaseMessage msg =
BdfDictionary msg = new BdfDictionary(); new SimpleMessage(SHARE_MSG_TYPE_ABORT, localState.getGroupId(),
msg.put(TYPE, SHARE_MSG_TYPE_ABORT); localState.getSessionId());
msg.put(SESSION_ID, localState.getRaw(SESSION_ID)); List<BaseMessage> messages = Collections.singletonList(msg);
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
List<BdfDictionary> messages = Collections.singletonList(msg);
List<Event> events = Collections.emptyList(); List<Event> events = Collections.emptyList();
return new StateUpdate<BdfDictionary, BdfDictionary>(false, false, return new StateUpdate<InviteeSessionState, BaseMessage>(false, false,
localState, messages, events); localState, messages, events);
} }
private StateUpdate<BdfDictionary, BdfDictionary> noUpdate( private StateUpdate<InviteeSessionState, BaseMessage> noUpdate(
BdfDictionary localState, boolean delete) throws FormatException { InviteeSessionState localState, boolean delete) throws FormatException {
return new StateUpdate<BdfDictionary, BdfDictionary>(delete, false, return new StateUpdate<InviteeSessionState, BaseMessage>(delete, false,
localState, Collections.<BdfDictionary>emptyList(), localState, Collections.<BaseMessage>emptyList(),
Collections.<Event>emptyList()); Collections.<Event>emptyList());
} }
} }

View File

@@ -0,0 +1,120 @@
package org.briarproject.forum;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import static org.briarproject.api.forum.ForumConstants.IS_SHARER;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
import static org.briarproject.api.forum.ForumConstants.STATE;
import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_ACCEPT;
import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_DECLINE;
import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_LEAVE;
import static org.briarproject.forum.InviteeSessionState.Action.REMOTE_INVITATION;
import static org.briarproject.forum.InviteeSessionState.Action.REMOTE_LEAVE;
// This class is not thread-safe
public class InviteeSessionState extends ForumSharingSessionState {
private State state;
public InviteeSessionState(SessionId sessionId, MessageId storageId,
GroupId groupId, State state, ContactId contactId, GroupId forumId,
String forumName, byte[] forumSalt) {
super(sessionId, storageId, groupId, contactId, forumId, forumName,
forumSalt);
this.state = state;
}
public BdfDictionary toBdfDictionary() {
BdfDictionary d = super.toBdfDictionary();
d.put(STATE, getState().getValue());
d.put(IS_SHARER, false);
return d;
}
public void setState(State state) {
this.state = state;
}
public State getState() {
return state;
}
public enum State {
ERROR(0),
AWAIT_INVITATION(1) {
@Override
public State next(Action a) {
if (a == REMOTE_INVITATION) return AWAIT_LOCAL_RESPONSE;
return ERROR;
}
},
AWAIT_LOCAL_RESPONSE(2) {
@Override
public State next(Action a) {
if (a == LOCAL_ACCEPT || a == LOCAL_DECLINE) return FINISHED;
if (a == REMOTE_LEAVE) return LEFT;
return ERROR;
}
},
FINISHED(3) {
@Override
public State next(Action a) {
if (a == LOCAL_LEAVE || a == REMOTE_LEAVE) return LEFT;
return FINISHED;
}
},
LEFT(4) {
@Override
public State next(Action a) {
if (a == LOCAL_LEAVE) return ERROR;
return LEFT;
}
};
private final int value;
State(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static State fromValue(int value) {
for (State s : values()) {
if (s.value == value) return s;
}
throw new IllegalArgumentException();
}
public State next(Action a) {
return this;
}
}
public enum Action {
LOCAL_ACCEPT,
LOCAL_DECLINE,
LOCAL_LEAVE,
LOCAL_ABORT,
REMOTE_INVITATION,
REMOTE_LEAVE,
REMOTE_ABORT;
public static Action getRemote(long type) {
if (type == SHARE_MSG_TYPE_INVITATION) return REMOTE_INVITATION;
if (type == SHARE_MSG_TYPE_LEAVE) return REMOTE_LEAVE;
if (type == SHARE_MSG_TYPE_ABORT) return REMOTE_ABORT;
return null;
}
}
}

View File

@@ -3,64 +3,52 @@ package org.briarproject.forum;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ProtocolEngine; import org.briarproject.api.clients.ProtocolEngine;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.ForumInvitationResponseReceivedEvent; import org.briarproject.api.event.ForumInvitationResponseReceivedEvent;
import org.briarproject.api.forum.SharerAction;
import org.briarproject.api.forum.SharerProtocolState;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.api.forum.ForumConstants.CONTACT_ID;
import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
import static org.briarproject.api.forum.ForumConstants.INVITATION_MSG;
import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT; import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT; import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE; import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE; import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
import static org.briarproject.api.forum.ForumConstants.STATE;
import static org.briarproject.api.forum.ForumConstants.TASK;
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US; import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US; import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.TASK_SHARE_FORUM; import static org.briarproject.api.forum.ForumConstants.TASK_SHARE_FORUM;
import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_BY_US; import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.TYPE; import static org.briarproject.api.forum.ForumSharingMessage.BaseMessage;
import static org.briarproject.api.forum.SharerAction.LOCAL_ABORT; import static org.briarproject.api.forum.ForumSharingMessage.Invitation;
import static org.briarproject.api.forum.SharerAction.LOCAL_INVITATION; import static org.briarproject.api.forum.ForumSharingMessage.SimpleMessage;
import static org.briarproject.api.forum.SharerAction.LOCAL_LEAVE; import static org.briarproject.forum.SharerSessionState.Action;
import static org.briarproject.api.forum.SharerAction.REMOTE_ACCEPT; import static org.briarproject.forum.SharerSessionState.Action.LOCAL_ABORT;
import static org.briarproject.api.forum.SharerAction.REMOTE_DECLINE; import static org.briarproject.forum.SharerSessionState.Action.LOCAL_INVITATION;
import static org.briarproject.api.forum.SharerAction.REMOTE_LEAVE; import static org.briarproject.forum.SharerSessionState.Action.LOCAL_LEAVE;
import static org.briarproject.api.forum.SharerProtocolState.ERROR; import static org.briarproject.forum.SharerSessionState.Action.REMOTE_ACCEPT;
import static org.briarproject.api.forum.SharerProtocolState.FINISHED; import static org.briarproject.forum.SharerSessionState.Action.REMOTE_DECLINE;
import static org.briarproject.api.forum.SharerProtocolState.LEFT; import static org.briarproject.forum.SharerSessionState.Action.REMOTE_LEAVE;
import static org.briarproject.forum.SharerSessionState.State;
import static org.briarproject.forum.SharerSessionState.State.ERROR;
import static org.briarproject.forum.SharerSessionState.State.FINISHED;
import static org.briarproject.forum.SharerSessionState.State.LEFT;
public class SharerEngine public class SharerEngine
implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> { implements ProtocolEngine<Action, SharerSessionState, BaseMessage> {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(SharerEngine.class.getName()); Logger.getLogger(SharerEngine.class.getName());
@Override @Override
public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction( public StateUpdate<SharerSessionState, BaseMessage> onLocalAction(
BdfDictionary localState, BdfDictionary localAction) { SharerSessionState localState, Action action) {
try { try {
SharerProtocolState currentState = State currentState = localState.getState();
getState(localState.getLong(STATE)); State nextState = currentState.next(action);
long type = localAction.getLong(TYPE); localState.setState(nextState);
SharerAction action = SharerAction.getLocal(type);
SharerProtocolState nextState = currentState.next(action);
localState.put(STATE, nextState.getValue());
if (action == LOCAL_ABORT && currentState != ERROR) { if (action == LOCAL_ABORT && currentState != ERROR) {
return abortSession(currentState, localState); return abortSession(currentState, localState);
@@ -73,38 +61,29 @@ public class SharerEngine
} }
return noUpdate(localState, true); return noUpdate(localState, true);
} }
List<BdfDictionary> messages; List<BaseMessage> messages;
List<Event> events = Collections.emptyList(); List<Event> events = Collections.emptyList();
if (action == LOCAL_INVITATION) { if (action == LOCAL_INVITATION) {
BdfDictionary msg = new BdfDictionary(); BaseMessage msg = new Invitation(localState.getGroupId(),
msg.put(TYPE, SHARE_MSG_TYPE_INVITATION); localState.getSessionId(), localState.getForumName(),
msg.put(SESSION_ID, localState.getRaw(SESSION_ID)); localState.getForumSalt(), localState.getMessage());
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
msg.put(FORUM_NAME, localState.getString(FORUM_NAME));
msg.put(FORUM_SALT, localState.getRaw(FORUM_SALT));
if (localAction.containsKey(INVITATION_MSG)) {
msg.put(INVITATION_MSG,
localAction.getString(INVITATION_MSG));
}
messages = Collections.singletonList(msg); messages = Collections.singletonList(msg);
logLocalAction(currentState, localState, msg); logLocalAction(currentState, nextState, msg);
// remember that we offered to share this forum // remember that we offered to share this forum
localState.put(TASK, TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US); localState.setTask(TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US);
} }
else if (action == LOCAL_LEAVE) { else if (action == LOCAL_LEAVE) {
BdfDictionary msg = new BdfDictionary(); BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_LEAVE,
msg.put(TYPE, SHARE_MSG_TYPE_LEAVE); localState.getGroupId(), localState.getSessionId());
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
messages = Collections.singletonList(msg); messages = Collections.singletonList(msg);
logLocalAction(currentState, localState, msg); logLocalAction(currentState, nextState, msg);
} }
else { else {
throw new IllegalArgumentException("Unknown Local Action"); throw new IllegalArgumentException("Unknown Local Action");
} }
return new StateUpdate<BdfDictionary, BdfDictionary>(false, return new StateUpdate<SharerSessionState, BaseMessage>(false,
false, localState, messages, events); false, localState, messages, events);
} catch (FormatException e) { } catch (FormatException e) {
throw new IllegalArgumentException(e); throw new IllegalArgumentException(e);
@@ -112,18 +91,16 @@ public class SharerEngine
} }
@Override @Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived( public StateUpdate<SharerSessionState, BaseMessage> onMessageReceived(
BdfDictionary localState, BdfDictionary msg) { SharerSessionState localState, BaseMessage msg) {
try { try {
SharerProtocolState currentState = State currentState = localState.getState();
getState(localState.getLong(STATE)); Action action = Action.getRemote(msg.getType());
long type = msg.getLong(TYPE); State nextState = currentState.next(action);
SharerAction action = SharerAction.getRemote(type); localState.setState(nextState);
SharerProtocolState nextState = currentState.next(action);
localState.put(STATE, nextState.getValue());
logMessageReceived(currentState, nextState, type, msg); logMessageReceived(currentState, nextState, msg.getType(), msg);
if (nextState == ERROR) { if (nextState == ERROR) {
if (currentState != ERROR) { if (currentState != ERROR) {
@@ -132,7 +109,7 @@ public class SharerEngine
return noUpdate(localState, true); return noUpdate(localState, true);
} }
} }
List<BdfDictionary> messages = Collections.emptyList(); List<BaseMessage> messages = Collections.emptyList();
List<Event> events = Collections.emptyList(); List<Event> events = Collections.emptyList();
boolean deleteMsg = false; boolean deleteMsg = false;
@@ -141,7 +118,7 @@ public class SharerEngine
deleteMsg = true; deleteMsg = true;
} }
else if (action == REMOTE_LEAVE) { else if (action == REMOTE_LEAVE) {
localState.put(TASK, TASK_UNSHARE_FORUM_SHARED_BY_US); localState.setTask(TASK_UNSHARE_FORUM_SHARED_BY_US);
} }
else if (currentState == FINISHED) { else if (currentState == FINISHED) {
// ignore and delete messages coming in while in that state // ignore and delete messages coming in while in that state
@@ -151,74 +128,65 @@ public class SharerEngine
// we have sent our invitation and just got a response // we have sent our invitation and just got a response
else if (action == REMOTE_ACCEPT || action == REMOTE_DECLINE) { else if (action == REMOTE_ACCEPT || action == REMOTE_DECLINE) {
if (action == REMOTE_ACCEPT) { if (action == REMOTE_ACCEPT) {
localState.put(TASK, TASK_SHARE_FORUM); localState.setTask(TASK_SHARE_FORUM);
} else { } else {
// this ensures that the forum can be shared again // this ensures that the forum can be shared again
localState.put(TASK, localState.setTask(
TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US); TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US);
} }
String name = localState.getString(FORUM_NAME); String name = localState.getForumName();
ContactId c = new ContactId( ContactId c = localState.getContactId();
localState.getLong(CONTACT_ID).intValue());
Event event = new ForumInvitationResponseReceivedEvent(name, c); Event event = new ForumInvitationResponseReceivedEvent(name, c);
events = Collections.singletonList(event); events = Collections.singletonList(event);
} }
else { else {
throw new IllegalArgumentException("Bad state"); throw new IllegalArgumentException("Bad state");
} }
return new StateUpdate<BdfDictionary, BdfDictionary>(deleteMsg, return new StateUpdate<SharerSessionState, BaseMessage>(deleteMsg,
false, localState, messages, events); false, localState, messages, events);
} catch (FormatException e) { } catch (FormatException e) {
throw new IllegalArgumentException(e); throw new IllegalArgumentException(e);
} }
} }
private void logLocalAction(SharerProtocolState state, private void logLocalAction(State currentState, State nextState,
BdfDictionary localState, BdfDictionary msg) { BaseMessage msg) {
if (!LOG.isLoggable(INFO)) return; if (!LOG.isLoggable(INFO)) return;
String a = "invitation"; String a = "invitation";
if (msg.getLong(TYPE, -1L) == SHARE_MSG_TYPE_LEAVE) a = "leave"; if (msg.getType() == SHARE_MSG_TYPE_LEAVE) a = "leave";
try { LOG.info("Sending " + a + " in state " + currentState.name() +
LOG.info("Sending " + a + " in state " + state.name() + " with session ID " +
" with session ID " + msg.getSessionId().hashCode() + " in group " +
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " + msg.getGroupId().hashCode() + ". " +
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " + "Moving on to state " + nextState.name()
"Moving on to state " + );
getState(localState.getLong(STATE)).name()
);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
} }
private void logMessageReceived(SharerProtocolState currentState, private void logMessageReceived(State currentState, State nextState,
SharerProtocolState nextState, long type, BdfDictionary msg) { long type, BaseMessage msg) {
if (!LOG.isLoggable(INFO)) return; if (!LOG.isLoggable(INFO)) return;
try { String t = "unknown";
String t = "unknown"; if (type == SHARE_MSG_TYPE_ACCEPT) t = "ACCEPT";
if (type == SHARE_MSG_TYPE_ACCEPT) t = "ACCEPT"; else if (type == SHARE_MSG_TYPE_DECLINE) t = "DECLINE";
else if (type == SHARE_MSG_TYPE_DECLINE) t = "DECLINE"; else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE";
else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE"; else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT";
else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT";
LOG.info("Received " + t + " in state " + currentState.name() + LOG.info("Received " + t + " in state " + currentState.name() +
" with session ID " + " with session ID " +
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " + msg.getSessionId().hashCode() + " in group " +
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " + msg.getGroupId().hashCode() + ". " +
"Moving on to state " + nextState.name() "Moving on to state " + nextState.name()
); );
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
} }
@Override @Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered( public StateUpdate<SharerSessionState, BaseMessage> onMessageDelivered(
BdfDictionary localState, BdfDictionary delivered) { SharerSessionState localState, BaseMessage delivered) {
try { try {
return noUpdate(localState, false); return noUpdate(localState, false);
} catch (FormatException e) { } catch (FormatException e) {
@@ -227,38 +195,34 @@ public class SharerEngine
} }
} }
private SharerProtocolState getState(Long state) { private StateUpdate<SharerSessionState, BaseMessage> abortSession(
return SharerProtocolState.fromValue(state.intValue()); State currentState, SharerSessionState localState)
}
private StateUpdate<BdfDictionary, BdfDictionary> abortSession(
SharerProtocolState currentState, BdfDictionary localState)
throws FormatException { throws FormatException {
if (LOG.isLoggable(WARNING)) { if (LOG.isLoggable(WARNING)) {
LOG.warning("Aborting protocol session " + LOG.warning("Aborting protocol session " +
Arrays.hashCode(localState.getRaw(SESSION_ID)) + localState.getSessionId().hashCode() +
" in state " + currentState.name()); " in state " + currentState.name());
} }
localState.put(STATE, ERROR.getValue()); localState.setState(ERROR);
BdfDictionary msg = new BdfDictionary(); BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_ABORT,
msg.put(TYPE, SHARE_MSG_TYPE_ABORT); localState.getGroupId(), localState.getSessionId());
msg.put(SESSION_ID, localState.getRaw(SESSION_ID)); List<BaseMessage> messages = Collections.singletonList(msg);
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
List<BdfDictionary> messages = Collections.singletonList(msg);
List<Event> events = Collections.emptyList(); List<Event> events = Collections.emptyList();
return new StateUpdate<BdfDictionary, BdfDictionary>(false, false, return new StateUpdate<SharerSessionState, BaseMessage>(false, false,
localState, messages, events); localState, messages, events);
} }
private StateUpdate<BdfDictionary, BdfDictionary> noUpdate( private StateUpdate<SharerSessionState, BaseMessage> noUpdate(
BdfDictionary localState, boolean delete) throws FormatException { SharerSessionState localState, boolean delete)
throws FormatException {
return new StateUpdate<BdfDictionary, BdfDictionary>(delete, false, return new StateUpdate<SharerSessionState, BaseMessage>(delete, false,
localState, Collections.<BdfDictionary>emptyList(), localState, Collections.<BaseMessage>emptyList(),
Collections.<Event>emptyList()); Collections.<Event>emptyList());
} }
} }

View File

@@ -0,0 +1,131 @@
package org.briarproject.forum;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import static org.briarproject.api.forum.ForumConstants.IS_SHARER;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
import static org.briarproject.api.forum.ForumConstants.STATE;
import static org.briarproject.forum.SharerSessionState.Action.LOCAL_INVITATION;
import static org.briarproject.forum.SharerSessionState.Action.LOCAL_LEAVE;
import static org.briarproject.forum.SharerSessionState.Action.REMOTE_ACCEPT;
import static org.briarproject.forum.SharerSessionState.Action.REMOTE_DECLINE;
import static org.briarproject.forum.SharerSessionState.Action.REMOTE_LEAVE;
// This class is not thread-safe
public class SharerSessionState extends ForumSharingSessionState {
private State state;
private String msg = null;
public SharerSessionState(SessionId sessionId, MessageId storageId,
GroupId groupId, State state, ContactId contactId, GroupId forumId,
String forumName, byte[] forumSalt) {
super(sessionId, storageId, groupId, contactId, forumId, forumName,
forumSalt);
this.state = state;
}
public BdfDictionary toBdfDictionary() {
BdfDictionary d = super.toBdfDictionary();
d.put(STATE, getState().getValue());
d.put(IS_SHARER, true);
return d;
}
public void setState(State state) {
this.state = state;
}
public State getState() {
return state;
}
public void setMessage(String msg) {
this.msg = msg;
}
public String getMessage() {
return this.msg;
}
public enum State {
ERROR(0),
PREPARE_INVITATION(1) {
@Override
public State next(Action a) {
if (a == LOCAL_INVITATION) return AWAIT_RESPONSE;
return ERROR;
}
},
AWAIT_RESPONSE(2) {
@Override
public State next(Action a) {
if (a == REMOTE_ACCEPT || a == REMOTE_DECLINE) return FINISHED;
if (a == LOCAL_LEAVE) return LEFT;
return ERROR;
}
},
FINISHED(3) {
@Override
public State next(Action a) {
if (a == LOCAL_LEAVE || a == REMOTE_LEAVE) return LEFT;
return FINISHED;
}
},
LEFT(4) {
@Override
public State next(Action a) {
if (a == LOCAL_LEAVE) return ERROR;
return LEFT;
}
};
private final int value;
State(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static State fromValue(int value) {
for (State s : values()) {
if (s.value == value) return s;
}
throw new IllegalArgumentException();
}
public State next(Action a) {
return this;
}
}
public enum Action {
LOCAL_INVITATION,
LOCAL_LEAVE,
LOCAL_ABORT,
REMOTE_ACCEPT,
REMOTE_DECLINE,
REMOTE_LEAVE,
REMOTE_ABORT;
public static Action getRemote(long type) {
if (type == SHARE_MSG_TYPE_ACCEPT) return REMOTE_ACCEPT;
if (type == SHARE_MSG_TYPE_DECLINE) return REMOTE_DECLINE;
if (type == SHARE_MSG_TYPE_LEAVE) return REMOTE_LEAVE;
if (type == SHARE_MSG_TYPE_ABORT) return REMOTE_ABORT;
return null;
}
}
}

View File

@@ -9,10 +9,12 @@ import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager.AddContactHook; import org.briarproject.api.contact.ContactManager.AddContactHook;
import org.briarproject.api.contact.ContactManager.RemoveContactHook; import org.briarproject.api.contact.ContactManager.RemoveContactHook;
import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfEntry;
import org.briarproject.api.data.BdfList; import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataParser; import org.briarproject.api.data.MetadataParser;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchContactException;
import org.briarproject.api.db.NoSuchMessageException; import org.briarproject.api.db.NoSuchMessageException;
import org.briarproject.api.db.Transaction; import org.briarproject.api.db.Transaction;
import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.AuthorId;
@@ -136,29 +138,58 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
@Override @Override
public void removingContact(Transaction txn, Contact c) throws DbException { public void removingContact(Transaction txn, Contact c) throws DbException {
// check for open sessions with that contact and abort those GroupId gId = introductionGroupFactory.createLocalGroup().getId();
Long id = (long) c.getId().getInt();
// search for session states where c introduced us
BdfDictionary query = BdfDictionary.of(
new BdfEntry(ROLE, ROLE_INTRODUCEE),
new BdfEntry(CONTACT_ID_1, c.getId().getInt())
);
try { try {
Map<MessageId, BdfDictionary> map = clientHelper Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, .getMessageMetadataAsDictionary(txn, gId, query);
introductionGroupFactory.createLocalGroup().getId()); for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
// delete states if introducee removes introducer
deleteMessage(txn, entry.getKey());
}
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
// check for open sessions with c and abort those,
// so the other introducee knows
query = BdfDictionary.of(
new BdfEntry(ROLE, ROLE_INTRODUCER)
);
try {
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, gId, query);
for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) { for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
BdfDictionary d = entry.getValue(); BdfDictionary d = entry.getValue();
long role = d.getLong(ROLE, -1L); ContactId c1 = new ContactId(d.getLong(CONTACT_ID_1).intValue());
if (role != ROLE_INTRODUCER) { ContactId c2 = new ContactId(d.getLong(CONTACT_ID_2).intValue());
if (d.getLong(CONTACT_ID_1).equals(id)) {
// delete states if introducee removes introducer
deleteMessage(txn, entry.getKey());
}
}
else if (d.getLong(CONTACT_ID_1).equals(id) ||
d.getLong(CONTACT_ID_2).equals(id)) {
if (c1.equals(c.getId()) || c2.equals(c.getId())) {
IntroducerProtocolState state = IntroducerProtocolState IntroducerProtocolState state = IntroducerProtocolState
.fromValue(d.getLong(STATE).intValue()); .fromValue(d.getLong(STATE).intValue());
// abort protocol if still ongoing
if (IntroducerProtocolState.isOngoing(state)) { if (IntroducerProtocolState.isOngoing(state)) {
introducerManager.abort(txn, d); introducerManager.abort(txn, d);
} }
// also delete state if both contacts have been deleted
if (c1.equals(c.getId())) {
try {
db.getContact(txn, c2);
} catch (NoSuchContactException e) {
deleteMessage(txn, entry.getKey());
}
} else if (c2.equals(c.getId())) {
try {
db.getContact(txn, c1);
} catch (NoSuchContactException e) {
deleteMessage(txn, entry.getKey());
}
}
} }
} }
} catch (FormatException e) { } catch (FormatException e) {

View File

@@ -2,10 +2,11 @@ package org.briarproject.introduction;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList; import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder; import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.Message; import org.briarproject.api.sync.Message;
import org.briarproject.api.system.Clock; import org.briarproject.api.system.Clock;
@@ -42,7 +43,7 @@ class IntroductionValidator extends BdfMessageValidator {
} }
@Override @Override
protected BdfDictionary validateMessage(Message m, Group g, BdfList body) protected BdfMessageContext validateMessage(Message m, Group g, BdfList body)
throws FormatException { throws FormatException {
BdfDictionary d; BdfDictionary d;
@@ -67,7 +68,7 @@ class IntroductionValidator extends BdfMessageValidator {
d.put(GROUP_ID, m.getGroupId()); d.put(GROUP_ID, m.getGroupId());
d.put(MESSAGE_ID, m.getId()); d.put(MESSAGE_ID, m.getId());
d.put(MESSAGE_TIME, m.getTimestamp()); d.put(MESSAGE_TIME, m.getTimestamp());
return d; return new BdfMessageContext(d);
} }
private BdfDictionary validateRequest(BdfList message) private BdfDictionary validateRequest(BdfList message)

View File

@@ -3,6 +3,7 @@ package org.briarproject.messaging;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.UniqueId; import org.briarproject.api.UniqueId;
import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList; import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder; import org.briarproject.api.data.MetadataEncoder;
@@ -22,7 +23,7 @@ class PrivateMessageValidator extends BdfMessageValidator {
} }
@Override @Override
protected BdfDictionary validateMessage(Message m, Group g, protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException { BdfList body) throws FormatException {
// Parent ID, content type, private message body // Parent ID, content type, private message body
checkSize(body, 3); checkSize(body, 3);
@@ -42,6 +43,6 @@ class PrivateMessageValidator extends BdfMessageValidator {
meta.put("contentType", contentType); meta.put("contentType", contentType);
meta.put("local", false); meta.put("local", false);
meta.put("read", false); meta.put("read", false);
return meta; return new BdfMessageContext(meta);
} }
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.properties;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList; import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder; import org.briarproject.api.data.MetadataEncoder;
@@ -22,7 +23,7 @@ public class TransportPropertyValidator extends BdfMessageValidator {
} }
@Override @Override
protected BdfDictionary validateMessage(Message m, Group g, protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException { BdfList body) throws FormatException {
// Transport ID, version, properties // Transport ID, version, properties
checkSize(body, 3); checkSize(body, 3);
@@ -45,6 +46,6 @@ public class TransportPropertyValidator extends BdfMessageValidator {
meta.put("transportId", transportId); meta.put("transportId", transportId);
meta.put("version", version); meta.put("version", version);
meta.put("local", false); meta.put("local", false);
return meta; return new BdfMessageContext(meta);
} }
} }

View File

@@ -16,9 +16,11 @@ import org.briarproject.api.lifecycle.Service;
import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message; import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.ValidationManager; import org.briarproject.api.sync.ValidationManager;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.util.ByteUtils; import org.briarproject.util.ByteUtils;
import java.util.LinkedList; import java.util.LinkedList;
@@ -31,6 +33,7 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
@@ -152,31 +155,54 @@ class ValidationManagerImpl implements ValidationManager, Service,
if (v == null) { if (v == null) {
LOG.warning("No validator"); LOG.warning("No validator");
} else { } else {
Metadata meta = v.validateMessage(m, g); try {
storeValidationResult(m, g.getClientId(), meta); MessageContext context = v.validateMessage(m, g);
storeMessageContext(m, g.getClientId(), context);
} catch (InvalidMessageException e) {
if (LOG.isLoggable(INFO))
LOG.log(INFO, e.toString(), e);
markMessageInvalid(m, g.getClientId());
}
} }
} }
}); });
} }
private void storeValidationResult(final Message m, final ClientId c, private void storeMessageContext(final Message m, final ClientId c,
final Metadata meta) { final MessageContext result) {
dbExecutor.execute(new Runnable() { dbExecutor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
if (meta == null) { Metadata meta = result.getMetadata();
db.setMessageValid(txn, m, c, false); db.mergeMessageMetadata(txn, m.getId(), meta);
} else { db.setMessageValid(txn, m, c, true);
db.mergeMessageMetadata(txn, m.getId(), meta); db.setMessageShared(txn, m, true);
db.setMessageValid(txn, m, c, true); IncomingMessageHook hook = hooks.get(c);
db.setMessageShared(txn, m, true); if (hook != null)
IncomingMessageHook hook = hooks.get(c); hook.incomingMessage(txn, m, meta);
if (hook != null) txn.setComplete();
hook.incomingMessage(txn, m, meta); } finally {
} db.endTransaction(txn);
}
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void markMessageInvalid(final Message m, final ClientId c) {
dbExecutor.execute(new Runnable() {
@Override
public void run() {
try {
Transaction txn = db.startTransaction(false);
try {
db.setMessageValid(txn, m, c, false);
txn.setComplete(); txn.setComplete();
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);

View File

@@ -4,10 +4,6 @@ targetCompatibility = 1.7
apply plugin: 'witness' apply plugin: 'witness'
repositories {
jcenter()
}
dependencies { dependencies {
compile project(':briar-api') compile project(':briar-api')
compile fileTree(dir: '../briar-core/libs', include: '*.jar') compile fileTree(dir: '../briar-core/libs', include: '*.jar')

View File

@@ -4,10 +4,6 @@ targetCompatibility = 1.7
apply plugin: 'witness' apply plugin: 'witness'
repositories {
jcenter()
}
dependencies { dependencies {
compile project(':briar-api') compile project(':briar-api')
compile project(':briar-core') compile project(':briar-core')

View File

@@ -20,6 +20,7 @@ import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.ValidationManager; import org.briarproject.api.sync.ValidationManager;
import org.briarproject.api.sync.ValidationManager.IncomingMessageHook; import org.briarproject.api.sync.ValidationManager.IncomingMessageHook;
import org.briarproject.api.sync.ValidationManager.MessageValidator; import org.briarproject.api.sync.ValidationManager.MessageValidator;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.util.ByteUtils; import org.briarproject.util.ByteUtils;
import org.hamcrest.Description; import org.hamcrest.Description;
import org.jmock.Expectations; import org.jmock.Expectations;
@@ -210,7 +211,9 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
new AtomicReference<MessageValidator>(); new AtomicReference<MessageValidator>();
final QueueMessageValidator queueMessageValidator = final QueueMessageValidator queueMessageValidator =
context.mock(QueueMessageValidator.class); context.mock(QueueMessageValidator.class);
final Metadata messageMetadata = new Metadata(); final Metadata metadata = new Metadata();
final MessageContext messageContext =
new MessageContext(metadata);
// The message is valid, with a queue position of zero // The message is valid, with a queue position of zero
final MessageId messageId = new MessageId(TestUtils.getRandomId()); final MessageId messageId = new MessageId(TestUtils.getRandomId());
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH]; final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
@@ -224,7 +227,7 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
// The message should be delegated // The message should be delegated
oneOf(queueMessageValidator).validateMessage( oneOf(queueMessageValidator).validateMessage(
with(any(QueueMessage.class)), with(group)); with(any(QueueMessage.class)), with(group));
will(returnValue(messageMetadata)); will(returnValue(messageContext));
}}); }});
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db, MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
@@ -235,7 +238,8 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
MessageValidator delegate = captured.get(); MessageValidator delegate = captured.get();
assertNotNull(delegate); assertNotNull(delegate);
// The message should be valid and the metadata should be returned // The message should be valid and the metadata should be returned
assertSame(messageMetadata, delegate.validateMessage(message, group)); assertSame(messageContext, delegate.validateMessage(message, group));
assertSame(metadata, messageContext.getMetadata());
context.assertIsSatisfied(); context.assertIsSatisfied();
} }

View File

@@ -85,7 +85,8 @@ public class IntroductionValidatorTest extends BriarTestCase {
name, publicKey, text); name, publicKey, text);
final BdfDictionary result = final BdfDictionary result =
validator.validateMessage(message, group, body); validator.validateMessage(message, group, body)
.getDictionary();
assertEquals(Long.valueOf(TYPE_REQUEST), result.getLong(TYPE)); assertEquals(Long.valueOf(TYPE_REQUEST), result.getLong(TYPE));
assertEquals(sessionId, result.getRaw(SESSION_ID)); assertEquals(sessionId, result.getRaw(SESSION_ID));
@@ -182,7 +183,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
msg.getRaw(E_PUBLIC_KEY), msg.getDictionary(TRANSPORT)); msg.getRaw(E_PUBLIC_KEY), msg.getDictionary(TRANSPORT));
final BdfDictionary result = final BdfDictionary result =
validator.validateMessage(message, group, body); validator.validateMessage(message, group, body).getDictionary();
assertEquals(Long.valueOf(TYPE_RESPONSE), result.getLong(TYPE)); assertEquals(Long.valueOf(TYPE_RESPONSE), result.getLong(TYPE));
assertEquals(sessionId, result.getRaw(SESSION_ID)); assertEquals(sessionId, result.getRaw(SESSION_ID));
@@ -199,7 +200,8 @@ public class IntroductionValidatorTest extends BriarTestCase {
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getBoolean(ACCEPT)); msg.getBoolean(ACCEPT));
BdfDictionary result = validator.validateMessage(message, group, body); BdfDictionary result = validator.validateMessage(message, group, body)
.getDictionary();
assertFalse(result.getBoolean(ACCEPT)); assertFalse(result.getBoolean(ACCEPT));
context.assertIsSatisfied(); context.assertIsSatisfied();
@@ -288,7 +290,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID)); BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
BdfDictionary result = BdfDictionary result =
validator.validateMessage(message, group, body); validator.validateMessage(message, group, body).getDictionary();
assertEquals(Long.valueOf(TYPE_ACK), result.getLong(TYPE)); assertEquals(Long.valueOf(TYPE_ACK), result.getLong(TYPE));
assertEquals(sessionId, result.getRaw(SESSION_ID)); assertEquals(sessionId, result.getRaw(SESSION_ID));
@@ -334,7 +336,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID)); BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
BdfDictionary result = BdfDictionary result =
validator.validateMessage(message, group, body); validator.validateMessage(message, group, body).getDictionary();
assertEquals(Long.valueOf(TYPE_ABORT), result.getLong(TYPE)); assertEquals(Long.valueOf(TYPE_ABORT), result.getLong(TYPE));
assertEquals(sessionId, result.getRaw(SESSION_ID)); assertEquals(sessionId, result.getRaw(SESSION_ID));

View File

@@ -59,7 +59,8 @@ public class TransportPropertyValidatorTest extends BriarTestCase {
BdfList body = BdfList.of(transportId.getString(), 4, bdfDictionary); BdfList body = BdfList.of(transportId.getString(), 4, bdfDictionary);
BdfDictionary result = tpv.validateMessage(message, group, body); BdfDictionary result = tpv.validateMessage(message, group, body)
.getDictionary();
assertEquals("test", result.getString("transportId")); assertEquals("test", result.getString("transportId"));
assertEquals(4, result.getLong("version").longValue()); assertEquals(4, result.getLong("version").longValue());

View File

@@ -14,10 +14,12 @@ import org.briarproject.api.event.MessageAddedEvent;
import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message; import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.ValidationManager.IncomingMessageHook; import org.briarproject.api.sync.ValidationManager.IncomingMessageHook;
import org.briarproject.api.sync.ValidationManager.MessageValidator; import org.briarproject.api.sync.ValidationManager.MessageValidator;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.util.ByteUtils; import org.briarproject.util.ByteUtils;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.jmock.Mockery; import org.jmock.Mockery;
@@ -41,6 +43,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
private final Message message1 = new Message(messageId1, groupId, timestamp, private final Message message1 = new Message(messageId1, groupId, timestamp,
raw); raw);
private final Metadata metadata = new Metadata(); private final Metadata metadata = new Metadata();
final MessageContext validResult = new MessageContext(metadata);
private final ContactId contactId = new ContactId(234); private final ContactId contactId = new ContactId(234);
public ValidationManagerImplTest() { public ValidationManagerImplTest() {
@@ -80,7 +83,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).endTransaction(txn1); oneOf(db).endTransaction(txn1);
// Validate the first message: valid // Validate the first message: valid
oneOf(validator).validateMessage(message, group); oneOf(validator).validateMessage(message, group);
will(returnValue(metadata)); will(returnValue(validResult));
// Store the validation result for the first message // Store the validation result for the first message
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn2)); will(returnValue(txn2));
@@ -100,7 +103,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).endTransaction(txn3); oneOf(db).endTransaction(txn3);
// Validate the second message: invalid // Validate the second message: invalid
oneOf(validator).validateMessage(message1, group); oneOf(validator).validateMessage(message1, group);
will(returnValue(null)); will(throwException(new InvalidMessageException()));
// Store the validation result for the second message // Store the validation result for the second message
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn4)); will(returnValue(txn4));
@@ -154,7 +157,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).endTransaction(txn2); oneOf(db).endTransaction(txn2);
// Validate the second message: invalid // Validate the second message: invalid
oneOf(validator).validateMessage(message1, group); oneOf(validator).validateMessage(message1, group);
will(returnValue(null)); will(throwException(new InvalidMessageException()));
// Store the validation result for the second message // Store the validation result for the second message
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn3)); will(returnValue(txn3));
@@ -211,7 +214,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).endTransaction(txn2); oneOf(db).endTransaction(txn2);
// Validate the second message: invalid // Validate the second message: invalid
oneOf(validator).validateMessage(message1, group); oneOf(validator).validateMessage(message1, group);
will(returnValue(null)); will(throwException(new InvalidMessageException()));
// Store the validation result for the second message // Store the validation result for the second message
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn3)); will(returnValue(txn3));
@@ -248,7 +251,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).endTransaction(txn); oneOf(db).endTransaction(txn);
// Validate the message: valid // Validate the message: valid
oneOf(validator).validateMessage(message, group); oneOf(validator).validateMessage(message, group);
will(returnValue(metadata)); will(returnValue(validResult));
// Store the validation result // Store the validation result
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn1)); will(returnValue(txn1));

View File

@@ -1,11 +1,16 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
allprojects {
repositories {
jcenter()
mavenLocal()
}
}
buildscript { buildscript {
repositories { repositories {
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
jcenter() jcenter()
mavenLocal()
} }
dependencies { dependencies {

View File

@@ -1,6 +1,6 @@
#Wed Apr 27 17:31:51 BST 2016 #Thu May 12 13:08:34 BST 2016
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-all.zip