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: 'com.neenbedankt.android-apt'
repositories {
maven { url 'http://repo1.maven.org/maven2' }
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"

View File

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

View File

@@ -149,6 +149,16 @@
/>
</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
android:name=".android.forum.WriteForumPostActivity"
android:label="@string/app_name"

View File

@@ -6,11 +6,6 @@ apply plugin: 'witness'
apply plugin: 'com.neenbedankt.android-apt'
apply plugin: 'de.undercouch.download'
repositories {
jcenter()
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
}
dependencies {
def supportVersion = '23.2.1'
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"?>
<RelativeLayout
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_height="match_parent">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarView"
style="@style/BriarAvatar"
android:layout_width="@dimen/listitem_picture_size"
android:layout_height="@dimen/listitem_picture_size"
android:layout_centerVertical="true"
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin"
app:civ_border_width="@dimen/avatar_border_width"
app:civ_border_color="@color/briar_primary"/>
tools:src="@drawable/ic_launcher"/>
<ImageView
android:id="@+id/statusView"

View File

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

View File

@@ -1,7 +1,6 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -9,11 +8,11 @@
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarView"
style="@style/BriarAvatar"
android:layout_width="@dimen/dropdown_picture_size"
android:layout_height="@dimen/dropdown_picture_size"
android:layout_margin="@dimen/margin_small"
app:civ_border_color="@color/briar_primary"
app:civ_border_width="@dimen/avatar_border_width"/>
tools:src="@drawable/ic_launcher"/>
<TextView
android:id="@+id/nameView"

View File

@@ -10,6 +10,11 @@
android:layout_width="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
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

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

View File

@@ -10,36 +10,55 @@
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/listitem_horizontal_margin"
android:paddingBottom="@dimen/listitem_horizontal_margin"
android:background="?attr/selectableItemBackground"
android:paddingBottom="@dimen/listitem_horizontal_margin"
android:paddingTop="@dimen/listitem_horizontal_margin"
>
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarView"
android:layout_width="@dimen/listitem_picture_size"
android:layout_height="@dimen/listitem_picture_size"
<FrameLayout
android:id="@+id/avatarFrameView"
android:layout_width="@dimen/listitem_picture_frame_size"
android:layout_height="@dimen/listitem_picture_frame_size"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin"
android:transitionName="avatar"
app:civ_border_color="@color/briar_primary"
app:civ_border_width="@dimen/avatar_border_width"
tools:src="@drawable/ic_launcher"/>
android:layout_marginStart="@dimen/listitem_horizontal_margin">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarView"
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
android:id="@+id/textViews"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_centerVertical="true"
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin"
android:layout_toEndOf="@+id/avatarFrameView"
android:layout_toLeftOf="@+id/bulbView"
android:layout_toRightOf="@+id/avatarView"
android:layout_toEndOf="@+id/avatarView">
android:layout_toRightOf="@+id/avatarFrameView"
android:orientation="vertical">
<TextView
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
android:id="@+id/avatarView"
style="@style/BriarAvatar"
android:layout_width="@dimen/listitem_selectable_picture_size"
android:layout_height="@dimen/listitem_selectable_picture_size"
android:layout_alignParentLeft="true"
@@ -22,8 +23,6 @@
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin"
android:transitionName="avatar"
app:civ_border_color="@color/briar_primary"
app:civ_border_width="@dimen/avatar_border_width"
tools:src="@drawable/ic_launcher"/>
<TextView

View File

@@ -1,18 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
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
android:id="@+id/avatarBackground"
style="@style/BriarAvatar"
android:layout_width="@dimen/avatar_forum_size"
android:layout_height="@dimen/avatar_forum_size"
android:layout_gravity="center"
android:src="@android:color/transparent"
app:civ_fill_color="@color/briar_button_positive"
app:civ_border_color="@color/briar_primary"
app:civ_border_width="@dimen/avatar_border_width"/>
app:civ_fill_color="@color/briar_button_positive"/>
<android.support.v7.widget.AppCompatTextView
android:id="@+id/textAvatarView"
@@ -29,4 +28,3 @@
tools:text="T"/>
</merge>

View File

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

View File

@@ -18,7 +18,7 @@
<color name="horizontal_border">#CCCCCC</color>
<color name="forums_available_background">@color/briar_gold</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_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 -->
<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_inverted">#ffffff</color>
<color name="menu_background">#FFFFFF</color>
<color name="spinner_border">#61000000</color> <!-- 38% Black -->
<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>

View File

@@ -26,10 +26,17 @@
<dimen name="listitem_height_one_line_avatar">56dp</dimen>
<dimen name="listitem_height_contact_selector">68dp</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="dropdown_picture_size">32dp</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_non_tail">51dp</dimen>

View File

@@ -80,6 +80,7 @@
<string name="show_forums">Show</string>
<string name="forum_leave">Leave 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_unread_posts">no unread posts</string>
<plurals name="unread_posts">
@@ -109,6 +110,9 @@
<string name="forum_joined_toast">Joined Forum</string>
<string name="forum_declined_toast">Forum Invitation Declined</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="add_button">Add</string>
<string name="cancel_button">Cancel</string>

View File

@@ -93,7 +93,7 @@
</style>
<style name="Divider">
<item name="android:background">?android:attr/listDivider</item>
<item name="android:background">@color/divider</item>
</style>
<style name="Divider.Horizontal" parent="Divider">
@@ -112,6 +112,14 @@
<item name="android:layout_height">1dp</item>
</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">
<item name="android:textSize">@dimen/text_size_medium</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.CreateForumActivity;
import org.briarproject.android.forum.ForumActivity;
import org.briarproject.android.forum.ForumSharingStatusActivity;
import org.briarproject.android.forum.ReadForumPostActivity;
import org.briarproject.android.forum.ShareForumActivity;
import org.briarproject.android.forum.ShareForumMessageFragment;
@@ -59,6 +60,8 @@ public interface ActivityComponent {
void inject(ShareForumActivity activity);
void inject(ForumSharingStatusActivity activity);
void inject(ReadForumPostActivity activity);
void inject(ForumActivity activity);

View File

@@ -1,17 +1,12 @@
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.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 {
@@ -21,19 +16,6 @@ public class ExpiredActivity extends Activity {
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
LinearLayout layout = new LinearLayout(this);
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);
setContentView(R.layout.activity_expired);
}
}

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,7 @@ import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
@@ -156,6 +157,10 @@ public class ConversationActivity extends BriarActivity
ab.setDisplayShowTitleEnabled(false);
}
String hexGroupId = StringUtils.toHexString(b);
ViewCompat.setTransitionName(toolbarAvatar, "avatar" + hexGroupId);
ViewCompat.setTransitionName(toolbarStatus, "bulb" + hexGroupId);
adapter = new ConversationAdapter(this, this);
list = (BriarRecyclerView) findViewById(R.id.conversationView);
list.setLayoutManager(new LinearLayoutManager(this));
@@ -207,7 +212,7 @@ public class ConversationActivity extends BriarActivity
// Handle presses on the action bar items
switch (item.getItemId()) {
case android.R.id.home:
supportFinishAfterTransition();
onBackPressed();
return true;
case R.id.action_introduction:
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() {
runOnDbThread(new Runnable() {
@Override

View File

@@ -153,7 +153,7 @@ public class AvailableForumsActivity extends BriarActivity
@Override
public void onItemClick(AvailableForumsItem item, boolean accept) {
respondToInvitation(item.getForum(), accept);
respondToInvitation(item, accept);
// show toast
int res = R.string.forum_declined_toast;
@@ -161,12 +161,16 @@ public class AvailableForumsActivity extends BriarActivity
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() {
@Override
public void run() {
try {
forumSharingManager.respondToInvitation(f, accept);
Forum f = item.getForum();
for (Contact c : item.getContacts()) {
forumSharingManager.respondToInvitation(f, c, accept);
}
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);

View File

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

View File

@@ -153,6 +153,9 @@ public class ForumActivity extends BriarActivity implements EventListener,
@Override
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
switch (item.getItemId()) {
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);
i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i2.putExtra(GROUP_ID, groupId.getBytes());
ActivityOptionsCompat options = ActivityOptionsCompat
.makeCustomAnimation(this, android.R.anim.slide_in_left,
android.R.anim.slide_out_right);
ActivityCompat
.startActivityForResult(this, i2, REQUEST_FORUM_SHARED,
options.toBundle());
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:
showUnsubscribeDialog();
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.os.Bundle;
import android.support.design.widget.TextInputLayout;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
@@ -16,7 +18,7 @@ import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
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.CryptoExecutor;
import org.briarproject.api.crypto.KeyPair;
@@ -31,18 +33,11 @@ import java.util.logging.Logger;
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.VISIBLE;
import static android.widget.LinearLayout.VERTICAL;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.logging.Level.INFO;
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;
public class CreateIdentityActivity extends BriarActivity
@@ -55,10 +50,10 @@ public class CreateIdentityActivity extends BriarActivity
@CryptoExecutor
protected Executor cryptoExecutor;
private TextInputLayout nicknameInput;
private EditText nicknameEntry;
private Button createIdentityButton;
private ProgressBar progress;
private TextView feedback;
// Fields that are accessed from background threads must be volatile
@Inject
@@ -71,52 +66,32 @@ public class CreateIdentityActivity extends BriarActivity
@Override
public void onCreate(Bundle 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);
chooseNickname.setGravity(CENTER);
chooseNickname.setTextSize(18);
chooseNickname.setText(R.string.choose_nickname);
layout.addView(chooseNickname);
setContentView(R.layout.activity_create_identity);
nicknameEntry = new EditText(this) {
nicknameInput = (TextInputLayout) findViewById(R.id.nicknameInputLayout);
nicknameEntry = (EditText) findViewById(R.id.nicknameEntry);
nicknameEntry.addTextChangedListener(new TextWatcher() {
@Override
protected void onTextChanged(CharSequence text, int start,
int lengthBefore, int lengthAfter) {
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
enableOrDisableCreateButton();
}
};
nicknameEntry.setId(1);
nicknameEntry.setMaxLines(1);
int inputType = TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_WORDS;
nicknameEntry.setInputType(inputType);
@Override
public void afterTextChanged(Editable s) {}
});
nicknameEntry.setOnEditorActionListener(this);
layout.addView(nicknameEntry);
feedback = new TextView(this);
feedback.setGravity(CENTER);
feedback.setPadding(0, pad, 0, pad);
layout.addView(feedback);
createIdentityButton = (Button) findViewById(R.id.createIdentityButton);
if (createIdentityButton != null)
createIdentityButton.setOnClickListener(this);
createIdentityButton = new Button(this);
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);
progress = (ProgressBar) findViewById(R.id.progressBar);
}
@Override
@@ -139,10 +114,11 @@ public class CreateIdentityActivity extends BriarActivity
String nickname = nicknameEntry.getText().toString();
int length = StringUtils.toUtf8(nickname).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;
}
feedback.setText("");
AndroidUtils.setError(nicknameInput, null, false);
return length > 0;
}
@@ -169,7 +145,7 @@ public class CreateIdentityActivity extends BriarActivity
}
private void storeLocalAuthor(final LocalAuthor a) {
runOnDbThread(new Runnable() {
dbController.runOnDbThread(new Runnable() {
@Override
public void run() {
try {

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
package org.briarproject.android.util;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.CameraInfo;
@@ -13,6 +15,7 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
@@ -31,17 +34,25 @@ import static java.util.logging.Level.WARNING;
@SuppressWarnings("deprecation")
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 MIN_FRAME_SIZE = 240;
private static final int MAX_FRAME_SIZE = 675; // = 5/8 * 1080
private static final Logger LOG =
Logger.getLogger(CameraView.class.getName());
private Camera camera = null;
private Rect framingRect;
private Rect framingRectInPreview;
private Rect framingRectInSensor;
private PreviewConsumer previewConsumer = null;
private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0;
private boolean autoFocus = false, surfaceExists = false;
private Point cameraResolution;
private final Object cameraResolutionLock = new Object();
public CameraView(Context context) {
super(context);
}
@@ -184,6 +195,24 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
LOG.info("No suitable focus mode");
}
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) {
@@ -222,6 +251,13 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
if (LOG.isLoggable(INFO))
LOG.info("Best size " + bestSize.width + "x" + 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);
}
}
/**
* 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 com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.Reader;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.ResultPointCallback;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
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 ResultCallback callback;
private final ResultPointCallback pointCallback;
private boolean stopped = false;
public QrCodeDecoder(ResultCallback callback) {
public QrCodeDecoder(ResultCallback callback,
ResultPointCallback pointCallback) {
this.callback = callback;
this.pointCallback = pointCallback;
}
public void start(Camera camera) {
@@ -72,9 +79,11 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
LuminanceSource src = new PlanarYUVLuminanceSource(data, width,
height, 0, 0, width, height, false);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(src));
Map<DecodeHintType, Object> hints = new HashMap<>();
hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, pointCallback);
Result result = null;
try {
result = reader.decode(bitmap);
result = reader.decode(bitmap, hints);
} catch (ReaderException e) {
return null; // No barcode found
} 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'
repositories {
jcenter()
}
dependencies {
compile "com.google.dagger:dagger: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.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.MessageContext;
public interface MessageQueueManager {
@@ -34,10 +36,11 @@ public interface MessageQueueManager {
interface QueueMessageValidator {
/**
* Validates the given message and returns its metadata if the message
* is valid, or null if the message is invalid.
* Validates the given message and returns its metadata and
* dependencies.
*/
Metadata validateMessage(QueueMessage q, Group g);
MessageContext validateMessage(QueueMessage q, Group g)
throws InvalidMessageException;
}
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. */
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. */
void addForum(Forum f) throws DbException;
Forum addForum(String name) throws DbException;
/** Unsubscribes from a forum. */
void removeForum(Forum f) throws DbException;

View File

@@ -24,7 +24,8 @@ public interface ForumSharingManager {
/**
* 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
@@ -40,7 +41,7 @@ public interface ForumSharingManager {
Collection<Contact> getSharedBy(GroupId g) throws DbException;
/** Returns the IDs of all contacts with whom the given forum is shared. */
Collection<ContactId> getSharedWith(GroupId g) throws DbException;
Collection<Contact> getSharedWith(GroupId g) throws DbException;
/** Returns true if the forum not already shared and no invitation is open */
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 {
/**
* Validates the given message and returns its metadata if the message
* is valid, or null if the message is invalid.
* Validates the given message and returns its metadata and
* dependencies.
*/
Metadata validateMessage(Message m, Group g);
MessageContext validateMessage(Message m, Group g)
throws InvalidMessageException;
}
interface IncomingMessageHook {

View File

@@ -4,10 +4,6 @@ targetCompatibility = 1.6
apply plugin: 'witness'
repositories {
jcenter()
}
dependencies {
compile project(':briar-api')
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.MessageQueueManager.QueueMessageValidator;
import org.briarproject.api.clients.QueueMessage;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.db.Metadata;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.ValidationManager.MessageValidator;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.api.system.Clock;
import org.briarproject.util.StringUtils;
@@ -37,43 +40,41 @@ public abstract class BdfMessageValidator implements MessageValidator,
this.clock = clock;
}
protected abstract BdfDictionary validateMessage(Message m, Group g,
BdfList body) throws FormatException;
protected abstract BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws InvalidMessageException, FormatException;
@Override
public Metadata validateMessage(Message m, Group g) {
public MessageContext validateMessage(Message m, Group g)
throws InvalidMessageException {
return validateMessage(m, g, MESSAGE_HEADER_LENGTH);
}
@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);
}
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
long now = clock.currentTimeMillis();
if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
LOG.info("Timestamp is too far in the future");
return null;
throw new InvalidMessageException(
"Timestamp is too far in the future");
}
byte[] raw = m.getRaw();
if (raw.length <= headerLength) {
LOG.info("Message is too short");
return null;
throw new InvalidMessageException("Message is too short");
}
try {
BdfList body = clientHelper.toList(raw, headerLength,
raw.length - headerLength);
BdfDictionary meta = validateMessage(m, g, body);
if (meta == null) {
LOG.info("Invalid message");
return null;
}
return metadataEncoder.encode(meta);
BdfMessageContext result = validateMessage(m, g, body);
Metadata meta = metadataEncoder.encode(result.getDictionary());
return new MessageContext(meta, result.getDependencies());
} catch (FormatException e) {
LOG.info("Invalid message");
return null;
throw new InvalidMessageException(e);
}
}

View File

@@ -14,10 +14,12 @@ import org.briarproject.api.db.Transaction;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.ValidationManager;
import org.briarproject.api.sync.ValidationManager.IncomingMessageHook;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.util.ByteUtils;
import java.util.ArrayList;
@@ -169,7 +171,8 @@ class MessageQueueManagerImpl implements MessageQueueManager {
}
@Override
public Metadata validateMessage(Message m, Group g) {
public MessageContext validateMessage(Message m, Group g)
throws InvalidMessageException {
byte[] raw = m.getRaw();
if (raw.length < QUEUE_MESSAGE_HEADER_LENGTH) return null;
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.Transaction;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumFactory;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumPost;
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.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupFactory;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -35,8 +34,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
import static org.briarproject.api.identity.Author.Status.ANONYMOUS;
import static org.briarproject.api.identity.Author.Status.UNKNOWN;
import static org.briarproject.api.identity.Author.Status.VERIFIED;
@@ -49,18 +46,16 @@ class ForumManagerImpl implements ForumManager {
private final DatabaseComponent db;
private final ClientHelper clientHelper;
private final GroupFactory groupFactory;
private final SecureRandom random;
private final ForumFactory forumFactory;
private final List<RemoveForumHook> removeHooks;
@Inject
ForumManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
GroupFactory groupFactory, SecureRandom random) {
ForumFactory forumFactory) {
this.db = db;
this.clientHelper = clientHelper;
this.groupFactory = groupFactory;
this.random = random;
this.forumFactory = forumFactory;
removeHooks = new CopyOnWriteArrayList<RemoveForumHook>();
}
@@ -70,30 +65,9 @@ class ForumManagerImpl implements ForumManager {
}
@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);
}
public Forum addForum(String name) throws DbException {
Forum f = forumFactory.createForum(name);
@Override
public Forum createForum(String name, byte[] salt) {
try {
BdfList forum = BdfList.of(name, salt);
byte[] descriptor = clientHelper.toByteArray(forum);
Group g = groupFactory.createGroup(getClientId(), descriptor);
return new Forum(g, name, salt);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
@Override
public void addForum(Forum f) throws DbException {
Transaction txn = db.startTransaction(false);
try {
db.addGroup(txn, f.getGroup());
@@ -101,6 +75,7 @@ class ForumManagerImpl implements ForumManager {
} finally {
db.endTransaction(txn);
}
return f;
}
@Override

View File

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

View File

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

View File

@@ -3,65 +3,59 @@ package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ProtocolEngine;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfEntry;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.ForumInvitationReceivedEvent;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.InviteeAction;
import org.briarproject.api.forum.InviteeProtocolState;
import org.briarproject.api.forum.ForumFactory;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.forum.ForumConstants.CONTACT_ID;
import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
import static org.briarproject.api.forum.ForumConstants.STATE;
import static org.briarproject.api.forum.ForumConstants.TASK;
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US;
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_SHARED_FORUM;
import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US;
import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_WITH_US;
import static org.briarproject.api.forum.ForumConstants.TYPE;
import static org.briarproject.api.forum.InviteeAction.LOCAL_ABORT;
import static org.briarproject.api.forum.InviteeAction.LOCAL_ACCEPT;
import static org.briarproject.api.forum.InviteeAction.LOCAL_DECLINE;
import static org.briarproject.api.forum.InviteeAction.LOCAL_LEAVE;
import static org.briarproject.api.forum.InviteeAction.REMOTE_INVITATION;
import static org.briarproject.api.forum.InviteeAction.REMOTE_LEAVE;
import static org.briarproject.api.forum.InviteeProtocolState.ERROR;
import static org.briarproject.api.forum.InviteeProtocolState.FINISHED;
import static org.briarproject.api.forum.InviteeProtocolState.LEFT;
import static org.briarproject.api.forum.ForumSharingMessage.SimpleMessage;
import static org.briarproject.api.forum.ForumSharingMessage.BaseMessage;
import static org.briarproject.forum.InviteeSessionState.Action;
import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_ABORT;
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;
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
implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> {
implements ProtocolEngine<Action, InviteeSessionState, BaseMessage> {
private final ForumFactory forumFactory;
private static final Logger LOG =
Logger.getLogger(SharerEngine.class.getName());
InviteeEngine(ForumFactory forumFactory) {
this.forumFactory = forumFactory;
}
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
BdfDictionary localState, BdfDictionary localAction) {
public StateUpdate<InviteeSessionState, BaseMessage> onLocalAction(
InviteeSessionState localState, Action action) {
try {
InviteeProtocolState currentState =
getState(localState.getLong(STATE));
long type = localAction.getLong(TYPE);
InviteeAction action = InviteeAction.getLocal(type);
InviteeProtocolState nextState = currentState.next(action);
localState.put(STATE, nextState.getValue());
State currentState = localState.getState();
State nextState = currentState.next(action);
localState.setState(nextState);
if (action == LOCAL_ABORT && currentState != ERROR) {
return abortSession(currentState, localState);
@@ -74,37 +68,34 @@ public class InviteeEngine
}
return noUpdate(localState, true);
}
List<BdfDictionary> messages;
List<BaseMessage> messages;
List<Event> events = Collections.emptyList();
if (action == LOCAL_ACCEPT || action == LOCAL_DECLINE) {
BdfDictionary msg = BdfDictionary.of(
new BdfEntry(SESSION_ID, localState.getRaw(SESSION_ID)),
new BdfEntry(GROUP_ID, localState.getRaw(GROUP_ID))
);
BaseMessage msg;
if (action == LOCAL_ACCEPT) {
localState.put(TASK, TASK_ADD_SHARED_FORUM);
msg.put(TYPE, SHARE_MSG_TYPE_ACCEPT);
localState.setTask(TASK_ADD_SHARED_FORUM);
msg = new SimpleMessage(SHARE_MSG_TYPE_ACCEPT,
localState.getGroupId(), localState.getSessionId());
} else {
localState.put(TASK,
localState.setTask(
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);
logLocalAction(currentState, localState, msg);
}
else if (action == LOCAL_LEAVE) {
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, SHARE_MSG_TYPE_LEAVE);
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_LEAVE,
localState.getGroupId(), localState.getSessionId());
messages = Collections.singletonList(msg);
logLocalAction(currentState, localState, msg);
}
else {
throw new IllegalArgumentException("Unknown Local Action");
}
return new StateUpdate<BdfDictionary, BdfDictionary>(false,
return new StateUpdate<InviteeSessionState, BaseMessage>(false,
false, localState, messages, events);
} catch (FormatException e) {
throw new IllegalArgumentException(e);
@@ -112,18 +103,16 @@ public class InviteeEngine
}
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
BdfDictionary localState, BdfDictionary msg) {
public StateUpdate<InviteeSessionState, BaseMessage> onMessageReceived(
InviteeSessionState localState, BaseMessage msg) {
try {
InviteeProtocolState currentState =
getState(localState.getLong(STATE));
long type = msg.getLong(TYPE);
InviteeAction action = InviteeAction.getRemote(type);
InviteeProtocolState nextState = currentState.next(action);
localState.put(STATE, nextState.getValue());
State currentState = localState.getState();
Action action = Action.getRemote(msg.getType());
State nextState = currentState.next(action);
localState.setState(nextState);
logMessageReceived(currentState, nextState, type, msg);
logMessageReceived(currentState, nextState, msg.getType(), msg);
if (nextState == 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();
boolean deleteMsg = false;
@@ -143,7 +132,7 @@ public class InviteeEngine
}
// the sharer left the forum she had shared with us
else if (action == REMOTE_LEAVE && currentState == FINISHED) {
localState.put(TASK, TASK_UNSHARE_FORUM_SHARED_WITH_US);
localState.setTask(TASK_UNSHARE_FORUM_SHARED_WITH_US);
}
else if (currentState == FINISHED) {
// 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
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
else if (action == REMOTE_INVITATION) {
localState.put(TASK, TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US);
// TODO how to get the proper group here?
Forum forum = new Forum(null, localState.getString(FORUM_NAME),
localState.getRaw(FORUM_SALT));
ContactId contactId = new ContactId(
localState.getLong(CONTACT_ID).intValue());
Forum forum = forumFactory
.createForum(localState.getForumName(),
localState.getForumSalt());
localState.setTask(TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US);
ContactId contactId = localState.getContactId();
Event event = new ForumInvitationReceivedEvent(forum, contactId);
events = Collections.singletonList(event);
}
else {
throw new IllegalArgumentException("Bad state");
}
return new StateUpdate<BdfDictionary, BdfDictionary>(deleteMsg,
return new StateUpdate<InviteeSessionState, BaseMessage>(deleteMsg,
false, localState, messages, events);
} catch (FormatException e) {
throw new IllegalArgumentException(e);
}
}
private void logLocalAction(InviteeProtocolState state,
BdfDictionary localState, BdfDictionary msg) {
private void logLocalAction(State state,
InviteeSessionState localState, BaseMessage msg) {
if (!LOG.isLoggable(INFO)) return;
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() +
" with session ID " +
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
"Moving on to state " +
getState(localState.getLong(STATE)).name()
);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
LOG.info("Sending " + a + " in state " + state.name() +
" with session ID " +
msg.getSessionId().hashCode() + " in group " +
msg.getGroupId().hashCode() + ". " +
"Moving on to state " + localState.getState().name()
);
}
private void logMessageReceived(InviteeProtocolState currentState,
InviteeProtocolState nextState, long type, BdfDictionary msg) {
private void logMessageReceived(State currentState, State nextState,
long type, BaseMessage msg) {
if (!LOG.isLoggable(INFO)) return;
try {
String t = "unknown";
if (type == SHARE_MSG_TYPE_INVITATION) t = "INVITE";
else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE";
else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT";
String t = "unknown";
if (type == SHARE_MSG_TYPE_INVITATION) t = "INVITE";
else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE";
else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT";
LOG.info("Received " + t + " in state " + currentState.name() +
" with session ID " +
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
"Moving on to state " + nextState.name()
);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
LOG.info("Received " + t + " in state " + currentState.name() +
" with session ID " +
msg.getSessionId().hashCode() + " in group " +
msg.getGroupId().hashCode() + ". " +
"Moving on to state " + nextState.name()
);
}
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered(
BdfDictionary localState, BdfDictionary delivered) {
public StateUpdate<InviteeSessionState, BaseMessage> onMessageDelivered(
InviteeSessionState localState, BaseMessage delivered) {
try {
return noUpdate(localState, false);
} catch (FormatException e) {
@@ -228,38 +208,32 @@ public class InviteeEngine
}
}
private InviteeProtocolState getState(Long state) {
return InviteeProtocolState.fromValue(state.intValue());
}
private StateUpdate<BdfDictionary, BdfDictionary> abortSession(
InviteeProtocolState currentState, BdfDictionary localState)
private StateUpdate<InviteeSessionState, BaseMessage> abortSession(
State currentState, InviteeSessionState localState)
throws FormatException {
if (LOG.isLoggable(WARNING)) {
LOG.warning("Aborting protocol session " +
Arrays.hashCode(localState.getRaw(SESSION_ID)) +
localState.getSessionId().hashCode() +
" in state " + currentState.name());
}
localState.put(STATE, ERROR.getValue());
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, SHARE_MSG_TYPE_ABORT);
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
List<BdfDictionary> messages = Collections.singletonList(msg);
localState.setState(ERROR);
BaseMessage msg =
new SimpleMessage(SHARE_MSG_TYPE_ABORT, localState.getGroupId(),
localState.getSessionId());
List<BaseMessage> messages = Collections.singletonList(msg);
List<Event> events = Collections.emptyList();
return new StateUpdate<BdfDictionary, BdfDictionary>(false, false,
return new StateUpdate<InviteeSessionState, BaseMessage>(false, false,
localState, messages, events);
}
private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
BdfDictionary localState, boolean delete) throws FormatException {
private StateUpdate<InviteeSessionState, BaseMessage> noUpdate(
InviteeSessionState localState, boolean delete) throws FormatException {
return new StateUpdate<BdfDictionary, BdfDictionary>(delete, false,
localState, Collections.<BdfDictionary>emptyList(),
return new StateUpdate<InviteeSessionState, BaseMessage>(delete, false,
localState, Collections.<BaseMessage>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.clients.ProtocolEngine;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.ForumInvitationResponseReceivedEvent;
import org.briarproject.api.forum.SharerAction;
import org.briarproject.api.forum.SharerProtocolState;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.forum.ForumConstants.CONTACT_ID;
import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
import static org.briarproject.api.forum.ForumConstants.INVITATION_MSG;
import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
import static org.briarproject.api.forum.ForumConstants.STATE;
import static org.briarproject.api.forum.ForumConstants.TASK;
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.TASK_SHARE_FORUM;
import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.TYPE;
import static org.briarproject.api.forum.SharerAction.LOCAL_ABORT;
import static org.briarproject.api.forum.SharerAction.LOCAL_INVITATION;
import static org.briarproject.api.forum.SharerAction.LOCAL_LEAVE;
import static org.briarproject.api.forum.SharerAction.REMOTE_ACCEPT;
import static org.briarproject.api.forum.SharerAction.REMOTE_DECLINE;
import static org.briarproject.api.forum.SharerAction.REMOTE_LEAVE;
import static org.briarproject.api.forum.SharerProtocolState.ERROR;
import static org.briarproject.api.forum.SharerProtocolState.FINISHED;
import static org.briarproject.api.forum.SharerProtocolState.LEFT;
import static org.briarproject.api.forum.ForumSharingMessage.BaseMessage;
import static org.briarproject.api.forum.ForumSharingMessage.Invitation;
import static org.briarproject.api.forum.ForumSharingMessage.SimpleMessage;
import static org.briarproject.forum.SharerSessionState.Action;
import static org.briarproject.forum.SharerSessionState.Action.LOCAL_ABORT;
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;
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
implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> {
implements ProtocolEngine<Action, SharerSessionState, BaseMessage> {
private static final Logger LOG =
Logger.getLogger(SharerEngine.class.getName());
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
BdfDictionary localState, BdfDictionary localAction) {
public StateUpdate<SharerSessionState, BaseMessage> onLocalAction(
SharerSessionState localState, Action action) {
try {
SharerProtocolState currentState =
getState(localState.getLong(STATE));
long type = localAction.getLong(TYPE);
SharerAction action = SharerAction.getLocal(type);
SharerProtocolState nextState = currentState.next(action);
localState.put(STATE, nextState.getValue());
State currentState = localState.getState();
State nextState = currentState.next(action);
localState.setState(nextState);
if (action == LOCAL_ABORT && currentState != ERROR) {
return abortSession(currentState, localState);
@@ -73,38 +61,29 @@ public class SharerEngine
}
return noUpdate(localState, true);
}
List<BdfDictionary> messages;
List<BaseMessage> messages;
List<Event> events = Collections.emptyList();
if (action == LOCAL_INVITATION) {
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, SHARE_MSG_TYPE_INVITATION);
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
msg.put(FORUM_NAME, localState.getString(FORUM_NAME));
msg.put(FORUM_SALT, localState.getRaw(FORUM_SALT));
if (localAction.containsKey(INVITATION_MSG)) {
msg.put(INVITATION_MSG,
localAction.getString(INVITATION_MSG));
}
BaseMessage msg = new Invitation(localState.getGroupId(),
localState.getSessionId(), localState.getForumName(),
localState.getForumSalt(), localState.getMessage());
messages = Collections.singletonList(msg);
logLocalAction(currentState, localState, msg);
logLocalAction(currentState, nextState, msg);
// 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) {
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, SHARE_MSG_TYPE_LEAVE);
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_LEAVE,
localState.getGroupId(), localState.getSessionId());
messages = Collections.singletonList(msg);
logLocalAction(currentState, localState, msg);
logLocalAction(currentState, nextState, msg);
}
else {
throw new IllegalArgumentException("Unknown Local Action");
}
return new StateUpdate<BdfDictionary, BdfDictionary>(false,
return new StateUpdate<SharerSessionState, BaseMessage>(false,
false, localState, messages, events);
} catch (FormatException e) {
throw new IllegalArgumentException(e);
@@ -112,18 +91,16 @@ public class SharerEngine
}
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
BdfDictionary localState, BdfDictionary msg) {
public StateUpdate<SharerSessionState, BaseMessage> onMessageReceived(
SharerSessionState localState, BaseMessage msg) {
try {
SharerProtocolState currentState =
getState(localState.getLong(STATE));
long type = msg.getLong(TYPE);
SharerAction action = SharerAction.getRemote(type);
SharerProtocolState nextState = currentState.next(action);
localState.put(STATE, nextState.getValue());
State currentState = localState.getState();
Action action = Action.getRemote(msg.getType());
State nextState = currentState.next(action);
localState.setState(nextState);
logMessageReceived(currentState, nextState, type, msg);
logMessageReceived(currentState, nextState, msg.getType(), msg);
if (nextState == ERROR) {
if (currentState != ERROR) {
@@ -132,7 +109,7 @@ public class SharerEngine
return noUpdate(localState, true);
}
}
List<BdfDictionary> messages = Collections.emptyList();
List<BaseMessage> messages = Collections.emptyList();
List<Event> events = Collections.emptyList();
boolean deleteMsg = false;
@@ -141,7 +118,7 @@ public class SharerEngine
deleteMsg = true;
}
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) {
// 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
else if (action == REMOTE_ACCEPT || action == REMOTE_DECLINE) {
if (action == REMOTE_ACCEPT) {
localState.put(TASK, TASK_SHARE_FORUM);
localState.setTask(TASK_SHARE_FORUM);
} else {
// this ensures that the forum can be shared again
localState.put(TASK,
localState.setTask(
TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US);
}
String name = localState.getString(FORUM_NAME);
ContactId c = new ContactId(
localState.getLong(CONTACT_ID).intValue());
String name = localState.getForumName();
ContactId c = localState.getContactId();
Event event = new ForumInvitationResponseReceivedEvent(name, c);
events = Collections.singletonList(event);
}
else {
throw new IllegalArgumentException("Bad state");
}
return new StateUpdate<BdfDictionary, BdfDictionary>(deleteMsg,
return new StateUpdate<SharerSessionState, BaseMessage>(deleteMsg,
false, localState, messages, events);
} catch (FormatException e) {
throw new IllegalArgumentException(e);
}
}
private void logLocalAction(SharerProtocolState state,
BdfDictionary localState, BdfDictionary msg) {
private void logLocalAction(State currentState, State nextState,
BaseMessage msg) {
if (!LOG.isLoggable(INFO)) return;
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 " + state.name() +
" with session ID " +
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
"Moving on to state " +
getState(localState.getLong(STATE)).name()
);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
LOG.info("Sending " + a + " in state " + currentState.name() +
" with session ID " +
msg.getSessionId().hashCode() + " in group " +
msg.getGroupId().hashCode() + ". " +
"Moving on to state " + nextState.name()
);
}
private void logMessageReceived(SharerProtocolState currentState,
SharerProtocolState nextState, long type, BdfDictionary msg) {
private void logMessageReceived(State currentState, State nextState,
long type, BaseMessage msg) {
if (!LOG.isLoggable(INFO)) return;
try {
String t = "unknown";
if (type == SHARE_MSG_TYPE_ACCEPT) t = "ACCEPT";
else if (type == SHARE_MSG_TYPE_DECLINE) t = "DECLINE";
else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE";
else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT";
String t = "unknown";
if (type == SHARE_MSG_TYPE_ACCEPT) t = "ACCEPT";
else if (type == SHARE_MSG_TYPE_DECLINE) t = "DECLINE";
else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE";
else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT";
LOG.info("Received " + t + " in state " + currentState.name() +
" with session ID " +
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
"Moving on to state " + nextState.name()
);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
LOG.info("Received " + t + " in state " + currentState.name() +
" with session ID " +
msg.getSessionId().hashCode() + " in group " +
msg.getGroupId().hashCode() + ". " +
"Moving on to state " + nextState.name()
);
}
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered(
BdfDictionary localState, BdfDictionary delivered) {
public StateUpdate<SharerSessionState, BaseMessage> onMessageDelivered(
SharerSessionState localState, BaseMessage delivered) {
try {
return noUpdate(localState, false);
} catch (FormatException e) {
@@ -227,38 +195,34 @@ public class SharerEngine
}
}
private SharerProtocolState getState(Long state) {
return SharerProtocolState.fromValue(state.intValue());
}
private StateUpdate<BdfDictionary, BdfDictionary> abortSession(
SharerProtocolState currentState, BdfDictionary localState)
private StateUpdate<SharerSessionState, BaseMessage> abortSession(
State currentState, SharerSessionState localState)
throws FormatException {
if (LOG.isLoggable(WARNING)) {
LOG.warning("Aborting protocol session " +
Arrays.hashCode(localState.getRaw(SESSION_ID)) +
localState.getSessionId().hashCode() +
" in state " + currentState.name());
}
localState.put(STATE, ERROR.getValue());
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, SHARE_MSG_TYPE_ABORT);
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
List<BdfDictionary> messages = Collections.singletonList(msg);
localState.setState(ERROR);
BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_ABORT,
localState.getGroupId(), localState.getSessionId());
List<BaseMessage> messages = Collections.singletonList(msg);
List<Event> events = Collections.emptyList();
return new StateUpdate<BdfDictionary, BdfDictionary>(false, false,
return new StateUpdate<SharerSessionState, BaseMessage>(false, false,
localState, messages, events);
}
private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
BdfDictionary localState, boolean delete) throws FormatException {
private StateUpdate<SharerSessionState, BaseMessage> noUpdate(
SharerSessionState localState, boolean delete)
throws FormatException {
return new StateUpdate<BdfDictionary, BdfDictionary>(delete, false,
localState, Collections.<BdfDictionary>emptyList(),
return new StateUpdate<SharerSessionState, BaseMessage>(delete, false,
localState, Collections.<BaseMessage>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.RemoveContactHook;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfEntry;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataParser;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchContactException;
import org.briarproject.api.db.NoSuchMessageException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.identity.AuthorId;
@@ -136,29 +138,58 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
@Override
public void removingContact(Transaction txn, Contact c) throws DbException {
// check for open sessions with that contact and abort those
Long id = (long) c.getId().getInt();
GroupId gId = introductionGroupFactory.createLocalGroup().getId();
// 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 {
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn,
introductionGroupFactory.createLocalGroup().getId());
.getMessageMetadataAsDictionary(txn, gId, query);
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()) {
BdfDictionary d = entry.getValue();
long role = d.getLong(ROLE, -1L);
if (role != ROLE_INTRODUCER) {
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)) {
ContactId c1 = new ContactId(d.getLong(CONTACT_ID_1).intValue());
ContactId c2 = new ContactId(d.getLong(CONTACT_ID_2).intValue());
if (c1.equals(c.getId()) || c2.equals(c.getId())) {
IntroducerProtocolState state = IntroducerProtocolState
.fromValue(d.getLong(STATE).intValue());
// abort protocol if still ongoing
if (IntroducerProtocolState.isOngoing(state)) {
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) {

View File

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

View File

@@ -3,6 +3,7 @@ package org.briarproject.messaging;
import org.briarproject.api.FormatException;
import org.briarproject.api.UniqueId;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder;
@@ -22,7 +23,7 @@ class PrivateMessageValidator extends BdfMessageValidator {
}
@Override
protected BdfDictionary validateMessage(Message m, Group g,
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException {
// Parent ID, content type, private message body
checkSize(body, 3);
@@ -42,6 +43,6 @@ class PrivateMessageValidator extends BdfMessageValidator {
meta.put("contentType", contentType);
meta.put("local", 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.clients.ClientHelper;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder;
@@ -22,7 +23,7 @@ public class TransportPropertyValidator extends BdfMessageValidator {
}
@Override
protected BdfDictionary validateMessage(Message m, Group g,
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException {
// Transport ID, version, properties
checkSize(body, 3);
@@ -45,6 +46,6 @@ public class TransportPropertyValidator extends BdfMessageValidator {
meta.put("transportId", transportId);
meta.put("version", version);
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.Group;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.ValidationManager;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.util.ByteUtils;
import java.util.LinkedList;
@@ -31,6 +33,7 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
@@ -152,31 +155,54 @@ class ValidationManagerImpl implements ValidationManager, Service,
if (v == null) {
LOG.warning("No validator");
} else {
Metadata meta = v.validateMessage(m, g);
storeValidationResult(m, g.getClientId(), meta);
try {
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,
final Metadata meta) {
private void storeMessageContext(final Message m, final ClientId c,
final MessageContext result) {
dbExecutor.execute(new Runnable() {
@Override
public void run() {
try {
Transaction txn = db.startTransaction(false);
try {
if (meta == null) {
db.setMessageValid(txn, m, c, false);
} else {
db.mergeMessageMetadata(txn, m.getId(), meta);
db.setMessageValid(txn, m, c, true);
db.setMessageShared(txn, m, true);
IncomingMessageHook hook = hooks.get(c);
if (hook != null)
hook.incomingMessage(txn, m, meta);
}
Metadata meta = result.getMetadata();
db.mergeMessageMetadata(txn, m.getId(), meta);
db.setMessageValid(txn, m, c, true);
db.setMessageShared(txn, m, true);
IncomingMessageHook hook = hooks.get(c);
if (hook != null)
hook.incomingMessage(txn, m, meta);
txn.setComplete();
} 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();
} finally {
db.endTransaction(txn);

View File

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

View File

@@ -4,10 +4,6 @@ targetCompatibility = 1.7
apply plugin: 'witness'
repositories {
jcenter()
}
dependencies {
compile project(':briar-api')
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.IncomingMessageHook;
import org.briarproject.api.sync.ValidationManager.MessageValidator;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.util.ByteUtils;
import org.hamcrest.Description;
import org.jmock.Expectations;
@@ -210,7 +211,9 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
new AtomicReference<MessageValidator>();
final QueueMessageValidator queueMessageValidator =
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
final MessageId messageId = new MessageId(TestUtils.getRandomId());
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
@@ -224,7 +227,7 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
// The message should be delegated
oneOf(queueMessageValidator).validateMessage(
with(any(QueueMessage.class)), with(group));
will(returnValue(messageMetadata));
will(returnValue(messageContext));
}});
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
@@ -235,7 +238,8 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
MessageValidator delegate = captured.get();
assertNotNull(delegate);
// 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();
}

View File

@@ -85,7 +85,8 @@ public class IntroductionValidatorTest extends BriarTestCase {
name, publicKey, text);
final BdfDictionary result =
validator.validateMessage(message, group, body);
validator.validateMessage(message, group, body)
.getDictionary();
assertEquals(Long.valueOf(TYPE_REQUEST), result.getLong(TYPE));
assertEquals(sessionId, result.getRaw(SESSION_ID));
@@ -182,7 +183,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
msg.getRaw(E_PUBLIC_KEY), msg.getDictionary(TRANSPORT));
final BdfDictionary result =
validator.validateMessage(message, group, body);
validator.validateMessage(message, group, body).getDictionary();
assertEquals(Long.valueOf(TYPE_RESPONSE), result.getLong(TYPE));
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),
msg.getBoolean(ACCEPT));
BdfDictionary result = validator.validateMessage(message, group, body);
BdfDictionary result = validator.validateMessage(message, group, body)
.getDictionary();
assertFalse(result.getBoolean(ACCEPT));
context.assertIsSatisfied();
@@ -288,7 +290,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
BdfDictionary result =
validator.validateMessage(message, group, body);
validator.validateMessage(message, group, body).getDictionary();
assertEquals(Long.valueOf(TYPE_ACK), result.getLong(TYPE));
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));
BdfDictionary result =
validator.validateMessage(message, group, body);
validator.validateMessage(message, group, body).getDictionary();
assertEquals(Long.valueOf(TYPE_ABORT), result.getLong(TYPE));
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);
BdfDictionary result = tpv.validateMessage(message, group, body);
BdfDictionary result = tpv.validateMessage(message, group, body)
.getDictionary();
assertEquals("test", result.getString("transportId"));
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.Group;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.ValidationManager.IncomingMessageHook;
import org.briarproject.api.sync.ValidationManager.MessageValidator;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.util.ByteUtils;
import org.jmock.Expectations;
import org.jmock.Mockery;
@@ -41,6 +43,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
private final Message message1 = new Message(messageId1, groupId, timestamp,
raw);
private final Metadata metadata = new Metadata();
final MessageContext validResult = new MessageContext(metadata);
private final ContactId contactId = new ContactId(234);
public ValidationManagerImplTest() {
@@ -80,7 +83,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).endTransaction(txn1);
// Validate the first message: valid
oneOf(validator).validateMessage(message, group);
will(returnValue(metadata));
will(returnValue(validResult));
// Store the validation result for the first message
oneOf(db).startTransaction(false);
will(returnValue(txn2));
@@ -100,7 +103,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).endTransaction(txn3);
// Validate the second message: invalid
oneOf(validator).validateMessage(message1, group);
will(returnValue(null));
will(throwException(new InvalidMessageException()));
// Store the validation result for the second message
oneOf(db).startTransaction(false);
will(returnValue(txn4));
@@ -154,7 +157,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).endTransaction(txn2);
// Validate the second message: invalid
oneOf(validator).validateMessage(message1, group);
will(returnValue(null));
will(throwException(new InvalidMessageException()));
// Store the validation result for the second message
oneOf(db).startTransaction(false);
will(returnValue(txn3));
@@ -211,7 +214,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).endTransaction(txn2);
// Validate the second message: invalid
oneOf(validator).validateMessage(message1, group);
will(returnValue(null));
will(throwException(new InvalidMessageException()));
// Store the validation result for the second message
oneOf(db).startTransaction(false);
will(returnValue(txn3));
@@ -248,7 +251,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).endTransaction(txn);
// Validate the message: valid
oneOf(validator).validateMessage(message, group);
will(returnValue(metadata));
will(returnValue(validResult));
// Store the validation result
oneOf(db).startTransaction(false);
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.
allprojects {
repositories {
jcenter()
mavenLocal()
}
}
buildscript {
repositories {
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
jcenter()
mavenLocal()
}
dependencies {
@@ -14,4 +19,4 @@ buildscript {
classpath 'de.undercouch:gradle-download-task:2.1.0'
classpath files('briar-core/libs/gradle-witness.jar')
}
}
}

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
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
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