mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
93 Commits
alpha-1.4.
...
beta-1.4.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
592daf9c20 | ||
|
|
3922270db1 | ||
|
|
4ba4e41e69 | ||
|
|
1f699238a9 | ||
|
|
b8e91a12e8 | ||
|
|
06eb01ab0a | ||
|
|
d82509f3ce | ||
|
|
b01c306500 | ||
|
|
61e7635b9f | ||
|
|
f2f356cbd4 | ||
|
|
28f3ab1310 | ||
|
|
8bb3a83ccb | ||
|
|
a742b007ef | ||
|
|
6bfd7bcc4f | ||
|
|
17f5fc7518 | ||
|
|
8dcf988399 | ||
|
|
05bf3833cf | ||
|
|
c39c2ce124 | ||
|
|
0b93af5d71 | ||
|
|
f8e3579a92 | ||
|
|
54e434d812 | ||
|
|
13c3974f73 | ||
|
|
aeb2a370e1 | ||
|
|
0aff23a067 | ||
|
|
a2a2da0260 | ||
|
|
4d7a3bca62 | ||
|
|
91d5698fe9 | ||
|
|
7266c6ee6b | ||
|
|
06b539b911 | ||
|
|
486ba4a3fc | ||
|
|
7f987667fe | ||
|
|
8d22a0ffaf | ||
|
|
43d28608f5 | ||
|
|
c84d3f7707 | ||
|
|
2843e15905 | ||
|
|
a2fb388aa6 | ||
|
|
b7b253cf24 | ||
|
|
f05e9dd746 | ||
|
|
e2a63ee361 | ||
|
|
ff9f706670 | ||
|
|
10ab60569b | ||
|
|
d77d1d67aa | ||
|
|
924425522a | ||
|
|
356e0ee07b | ||
|
|
61658655ff | ||
|
|
40086ffde2 | ||
|
|
1551142e98 | ||
|
|
1c6fb6491a | ||
|
|
cfd4e85e77 | ||
|
|
4d6abfabf7 | ||
|
|
a38933df66 | ||
|
|
4993873ae2 | ||
|
|
02b805ce42 | ||
|
|
1a6ba16a59 | ||
|
|
654a05df8a | ||
|
|
ffe1876337 | ||
|
|
98963955b1 | ||
|
|
d83efce002 | ||
|
|
efb1b8c1ad | ||
|
|
3f36db8b3a | ||
|
|
a2f4e70a48 | ||
|
|
01e72eff40 | ||
|
|
dbcea3e1d1 | ||
|
|
6288577daa | ||
|
|
5d363496bd | ||
|
|
75b5c92495 | ||
|
|
bcc98cc4c9 | ||
|
|
2d605089bc | ||
|
|
01f8be1b66 | ||
|
|
eac6d0aa40 | ||
|
|
713be403eb | ||
|
|
2fd948b81d | ||
|
|
62af5e858c | ||
|
|
2201585a34 | ||
|
|
97d11cc602 | ||
|
|
79f41064e4 | ||
|
|
9aacd9d3d8 | ||
|
|
78f4dee43d | ||
|
|
2b4a1cf54b | ||
|
|
bb71de1a78 | ||
|
|
08bf13e44f | ||
|
|
cc7de2c70a | ||
|
|
0f4aa8027a | ||
|
|
b161a5e115 | ||
|
|
e112f69c4e | ||
|
|
4623d03c93 | ||
|
|
b128220be3 | ||
|
|
6aa24af94c | ||
|
|
de63a50662 | ||
|
|
5517ac14ed | ||
|
|
2672d82a40 | ||
|
|
63c0210047 | ||
|
|
47085722da |
@@ -118,11 +118,3 @@ mailbox integration test:
|
||||
- cd "$CI_PROJECT_DIR"
|
||||
- bramble-core/src/test/bash/wait-for-mailbox.sh
|
||||
- OPTIONAL_TESTS=org.briarproject.bramble.mailbox.MailboxIntegrationTest ./gradlew --info bramble-core:test --tests MailboxIntegrationTest
|
||||
|
||||
pre_release_tests:
|
||||
extends: .optional_tests
|
||||
script:
|
||||
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
|
||||
timeout: 3h
|
||||
only:
|
||||
- tags
|
||||
|
||||
@@ -15,8 +15,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
versionCode 10408
|
||||
versionName "1.4.8"
|
||||
versionCode 10409
|
||||
versionName "1.4.9"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface ConnectionManager {
|
||||
@@ -16,6 +17,17 @@ public interface ConnectionManager {
|
||||
*/
|
||||
void manageIncomingConnection(TransportId t, TransportConnectionReader r);
|
||||
|
||||
/**
|
||||
* Manages an incoming connection from a contact via a mailbox.
|
||||
* <p>
|
||||
* This method does not mark the tag as recognised until after the data
|
||||
* has been read from the {@link TransportConnectionReader}, at which
|
||||
* point the {@link TagController} is called to decide whether the tag
|
||||
* should be marked as recognised.
|
||||
*/
|
||||
void manageIncomingConnection(TransportId t, TransportConnectionReader r,
|
||||
TagController c);
|
||||
|
||||
/**
|
||||
* Manages an incoming connection from a contact over a duplex transport.
|
||||
*/
|
||||
@@ -34,6 +46,14 @@ public interface ConnectionManager {
|
||||
void manageOutgoingConnection(ContactId c, TransportId t,
|
||||
TransportConnectionWriter w);
|
||||
|
||||
/**
|
||||
* Manages an outgoing connection to a contact via a mailbox. The IDs of
|
||||
* any messages sent or acked are added to the given
|
||||
* {@link OutgoingSessionRecord}.
|
||||
*/
|
||||
void manageOutgoingConnection(ContactId c, TransportId t,
|
||||
TransportConnectionWriter w, OutgoingSessionRecord sessionRecord);
|
||||
|
||||
/**
|
||||
* Manages an outgoing connection to a contact over a duplex transport.
|
||||
*/
|
||||
@@ -46,4 +66,21 @@ public interface ConnectionManager {
|
||||
*/
|
||||
void manageOutgoingConnection(PendingContactId p, TransportId t,
|
||||
DuplexTransportConnection d);
|
||||
|
||||
/**
|
||||
* An interface for controlling whether a tag should be marked as
|
||||
* recognised.
|
||||
*/
|
||||
interface TagController {
|
||||
/**
|
||||
* This method is only called if a tag was read from the corresponding
|
||||
* {@link TransportConnectionReader} and recognised.
|
||||
*
|
||||
* @param exception True if an exception was thrown while reading from
|
||||
* the {@link TransportConnectionReader}, after successfully reading
|
||||
* and recognising the tag.
|
||||
* @return True if the tag should be marked as recognised.
|
||||
*/
|
||||
boolean shouldMarkTagAsRecognised(boolean exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,16 +126,11 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
TransportKeys k) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if there are any acks or messages to send to the given
|
||||
* contact over a transport with the given maximum latency.
|
||||
* Returns true if there are any acks to send to the given contact.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*
|
||||
* @param eager True if messages that are not yet due for retransmission
|
||||
* should be included
|
||||
*/
|
||||
boolean containsAnythingToSend(Transaction txn, ContactId c,
|
||||
long maxLatency, boolean eager) throws DbException;
|
||||
boolean containsAcksToSend(Transaction txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given contact for the given
|
||||
@@ -161,6 +156,18 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
*/
|
||||
boolean containsIdentity(Transaction txn, AuthorId a) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if there are any messages to send to the given contact
|
||||
* over a transport with the given maximum latency.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*
|
||||
* @param eager True if messages that are not yet due for retransmission
|
||||
* should be included
|
||||
*/
|
||||
boolean containsMessagesToSend(Transaction txn, ContactId c,
|
||||
long maxLatency, boolean eager) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given pending contact.
|
||||
* <p/>
|
||||
@@ -534,15 +541,18 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
*/
|
||||
long getNextCleanupDeadline(Transaction txn) throws DbException;
|
||||
|
||||
/*
|
||||
/**
|
||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||
* message is due to be sent to the given contact. The returned value may
|
||||
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE if
|
||||
* no messages are scheduled to be sent.
|
||||
* message is due to be sent to the given contact over a transport with
|
||||
* the given latency.
|
||||
* <p>
|
||||
* The returned value may be zero if a message is due to be sent
|
||||
* immediately, or Long.MAX_VALUE if no messages are scheduled to be sent.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
long getNextSendTime(Transaction txn, ContactId c) throws DbException;
|
||||
long getNextSendTime(Transaction txn, ContactId c, long maxLatency)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the pending contact with the given ID.
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.concurrent.TimeUnit.DAYS;
|
||||
import static java.util.concurrent.TimeUnit.HOURS;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||
@@ -8,6 +14,32 @@ import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENG
|
||||
|
||||
public interface MailboxConstants {
|
||||
|
||||
/**
|
||||
* The transport ID of the mailbox plugin.
|
||||
*/
|
||||
TransportId ID = new TransportId("org.briarproject.bramble.mailbox");
|
||||
|
||||
/**
|
||||
* Mailbox API versions that we support as a client. This is reported to our
|
||||
* contacts by {@link MailboxUpdateManager}.
|
||||
*/
|
||||
List<MailboxVersion> CLIENT_SUPPORTS = singletonList(
|
||||
new MailboxVersion(1, 0));
|
||||
|
||||
/**
|
||||
* The constant returned by
|
||||
* {@link MailboxHelper#getHighestCommonMajorVersion(List, List)}
|
||||
* when the server is too old to support our major version.
|
||||
*/
|
||||
int API_SERVER_TOO_OLD = -1;
|
||||
|
||||
/**
|
||||
* The constant returned by
|
||||
* {@link MailboxHelper#getHighestCommonMajorVersion(List, List)}
|
||||
* when we as a client are too old to support the server's major version.
|
||||
*/
|
||||
int API_CLIENT_TOO_OLD = -2;
|
||||
|
||||
/**
|
||||
* The maximum length of a file that can be uploaded to or downloaded from
|
||||
* a mailbox.
|
||||
@@ -34,4 +66,8 @@ public interface MailboxConstants {
|
||||
*/
|
||||
long PROBLEM_MS_SINCE_LAST_SUCCESS = HOURS.toMillis(1);
|
||||
|
||||
/**
|
||||
* The maximum latency of the mailbox transport in milliseconds.
|
||||
*/
|
||||
long MAX_LATENCY = DAYS.toMillis(14);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* Annotation for injecting the {@link File directory} where the Mailbox plugin
|
||||
* should store its state.
|
||||
*/
|
||||
@Qualifier
|
||||
@Target({FIELD, METHOD, PARAMETER})
|
||||
@Retention(RUNTIME)
|
||||
public @interface MailboxDirectory {
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_CLIENT_TOO_OLD;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_SERVER_TOO_OLD;
|
||||
|
||||
class MailboxHelper {
|
||||
|
||||
/**
|
||||
* Returns the highest major version that both client and server support
|
||||
* or {@link MailboxConstants#API_SERVER_TOO_OLD} if the server is too old
|
||||
* or {@link MailboxConstants#API_CLIENT_TOO_OLD} if the client is too old.
|
||||
*/
|
||||
static int getHighestCommonMajorVersion(
|
||||
List<MailboxVersion> client, List<MailboxVersion> server) {
|
||||
TreeSet<Integer> clientVersions = new TreeSet<>();
|
||||
for (MailboxVersion version : client) {
|
||||
clientVersions.add(version.getMajor());
|
||||
}
|
||||
TreeSet<Integer> serverVersions = new TreeSet<>();
|
||||
for (MailboxVersion version : server) {
|
||||
serverVersions.add(version.getMajor());
|
||||
}
|
||||
for (int clientVersion : clientVersions.descendingSet()) {
|
||||
if (serverVersions.contains(clientVersion)) return clientVersion;
|
||||
}
|
||||
if (clientVersions.last() < serverVersions.last()) {
|
||||
return API_CLIENT_TOO_OLD;
|
||||
}
|
||||
return API_SERVER_TOO_OLD;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,6 +35,9 @@ public interface MailboxSettingsManager {
|
||||
void recordSuccessfulConnection(Transaction txn, long now)
|
||||
throws DbException;
|
||||
|
||||
void recordSuccessfulConnection(Transaction txn, long now,
|
||||
List<MailboxVersion> versions) throws DbException;
|
||||
|
||||
void recordFailedConnectionAttempt(Transaction txn, long now)
|
||||
throws DbException;
|
||||
|
||||
|
||||
@@ -2,10 +2,14 @@ package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.PROBLEM_MS_SINCE_LAST_SUCCESS;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.PROBLEM_NUM_CONNECTION_FAILURES;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxHelper.getHighestCommonMajorVersion;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -13,12 +17,15 @@ public class MailboxStatus {
|
||||
|
||||
private final long lastAttempt, lastSuccess;
|
||||
private final int attemptsSinceSuccess;
|
||||
private final List<MailboxVersion> serverSupports;
|
||||
|
||||
public MailboxStatus(long lastAttempt, long lastSuccess,
|
||||
int attemptsSinceSuccess) {
|
||||
int attemptsSinceSuccess,
|
||||
List<MailboxVersion> serverSupports) {
|
||||
this.lastAttempt = lastAttempt;
|
||||
this.lastSuccess = lastSuccess;
|
||||
this.attemptsSinceSuccess = attemptsSinceSuccess;
|
||||
this.serverSupports = serverSupports;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,4 +74,13 @@ public class MailboxStatus {
|
||||
return attemptsSinceSuccess >= PROBLEM_NUM_CONNECTION_FAILURES &&
|
||||
(now - lastSuccess) >= PROBLEM_MS_SINCE_LAST_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a positive integer if the mailbox is compatible. Same result as
|
||||
* {@link MailboxHelper#getHighestCommonMajorVersion(List, List)}.
|
||||
*/
|
||||
public int getMailboxCompatibility() {
|
||||
return getHighestCommonMajorVersion(CLIENT_SUPPORTS, serverSupports);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -63,9 +63,26 @@ public interface MailboxUpdateManager {
|
||||
*/
|
||||
String GROUP_KEY_SENT_CLIENT_SUPPORTS = "sentClientSupports";
|
||||
|
||||
/**
|
||||
* Returns the latest {@link MailboxUpdate} sent to the given contact.
|
||||
* <p>
|
||||
* If we have our own mailbox then the update will be a
|
||||
* {@link MailboxUpdateWithMailbox} containing the
|
||||
* {@link MailboxProperties} the contact should use for communicating with
|
||||
* our mailbox.
|
||||
*/
|
||||
MailboxUpdate getLocalUpdate(Transaction txn, ContactId c)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the latest {@link MailboxUpdate} received from the given
|
||||
* contact, or null if no update has been received.
|
||||
* <p>
|
||||
* If the contact has a mailbox then the update will be a
|
||||
* {@link MailboxUpdateWithMailbox} containing the
|
||||
* {@link MailboxProperties} we should use for communicating with the
|
||||
* contact's mailbox.
|
||||
*/
|
||||
@Nullable
|
||||
MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
|
||||
throws DbException;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.briarproject.bramble.api.sync;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* An interface for holding the IDs of messages sent and acked during an
|
||||
* outgoing {@link SyncSession} so they can be recorded in the DB as sent
|
||||
* or acked at some later time.
|
||||
*/
|
||||
public interface DeferredSendHandler {
|
||||
|
||||
void onAckSent(Collection<MessageId> acked);
|
||||
|
||||
void onMessageSent(MessageId sent);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.briarproject.bramble.api.sync;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/**
|
||||
* A container for holding the IDs of messages sent and acked during an
|
||||
* outgoing {@link SyncSession}, so they can be recorded in the DB as sent
|
||||
* or acked at some later time.
|
||||
*/
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
public class OutgoingSessionRecord {
|
||||
|
||||
private final Collection<MessageId> ackedIds = new CopyOnWriteArrayList<>();
|
||||
private final Collection<MessageId> sentIds = new CopyOnWriteArrayList<>();
|
||||
|
||||
public void onAckSent(Collection<MessageId> acked) {
|
||||
ackedIds.addAll(acked);
|
||||
}
|
||||
|
||||
public void onMessageSent(MessageId sent) {
|
||||
sentIds.add(sent);
|
||||
}
|
||||
|
||||
public Collection<MessageId> getAckedIds() {
|
||||
return ackedIds;
|
||||
}
|
||||
|
||||
public Collection<MessageId> getSentIds() {
|
||||
return sentIds;
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,30 @@ import javax.annotation.Nullable;
|
||||
@NotNullByDefault
|
||||
public interface SyncSessionFactory {
|
||||
|
||||
/**
|
||||
* Creates a session for receiving data from a contact.
|
||||
*/
|
||||
SyncSession createIncomingSession(ContactId c, InputStream in,
|
||||
PriorityHandler handler);
|
||||
|
||||
/**
|
||||
* Creates a session for sending data to a contact over a simplex transport.
|
||||
*
|
||||
* @param eager True if messages should be sent eagerly, ie regardless of
|
||||
* whether they're due for retransmission.
|
||||
*/
|
||||
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||
long maxLatency, boolean eager, StreamWriter streamWriter);
|
||||
|
||||
/**
|
||||
* Creates a session for sending data to a contact via a mailbox. The IDs
|
||||
* of any messages sent or acked will be added to the given
|
||||
* {@link OutgoingSessionRecord}.
|
||||
*/
|
||||
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||
long maxLatency, StreamWriter streamWriter,
|
||||
OutgoingSessionRecord sessionRecord);
|
||||
|
||||
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
||||
long maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
||||
@Nullable Priority priority);
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.sync.event;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -15,12 +16,19 @@ import javax.annotation.concurrent.Immutable;
|
||||
@NotNullByDefault
|
||||
public class GroupVisibilityUpdatedEvent extends Event {
|
||||
|
||||
private final Visibility visibility;
|
||||
private final Collection<ContactId> affected;
|
||||
|
||||
public GroupVisibilityUpdatedEvent(Collection<ContactId> affected) {
|
||||
public GroupVisibilityUpdatedEvent(Visibility visibility,
|
||||
Collection<ContactId> affected) {
|
||||
this.visibility = visibility;
|
||||
this.affected = affected;
|
||||
}
|
||||
|
||||
public Visibility getVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contacts affected by the update.
|
||||
*/
|
||||
|
||||
@@ -40,7 +40,7 @@ public class IoUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private static void delete(File f) {
|
||||
public static void delete(File f) {
|
||||
if (!f.delete() && LOG.isLoggable(WARNING))
|
||||
LOG.warning("Could not delete " + f.getAbsolutePath());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_CLIENT_TOO_OLD;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_SERVER_TOO_OLD;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxHelper.getHighestCommonMajorVersion;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class MailboxHelperTest {
|
||||
|
||||
private final Random random = new Random();
|
||||
|
||||
@Test
|
||||
public void testGetHighestCommonMajorVersion() {
|
||||
assertEquals(2, getHighestCommonMajorVersion(v(2), v(2)));
|
||||
assertEquals(2, getHighestCommonMajorVersion(v(1, 2), v(2, 3, 4)));
|
||||
assertEquals(2, getHighestCommonMajorVersion(v(2, 3, 4), v(2)));
|
||||
assertEquals(2, getHighestCommonMajorVersion(v(2), v(2, 3, 4)));
|
||||
|
||||
assertEquals(API_CLIENT_TOO_OLD,
|
||||
getHighestCommonMajorVersion(v(2), v(3, 4)));
|
||||
assertEquals(API_CLIENT_TOO_OLD,
|
||||
getHighestCommonMajorVersion(v(2), v(1, 3)));
|
||||
assertEquals(API_SERVER_TOO_OLD,
|
||||
getHighestCommonMajorVersion(v(3, 4, 5), v(2)));
|
||||
assertEquals(API_SERVER_TOO_OLD,
|
||||
getHighestCommonMajorVersion(v(1, 3), v(2)));
|
||||
}
|
||||
|
||||
private List<MailboxVersion> v(int... ints) {
|
||||
List<MailboxVersion> versions = new ArrayList<>(ints.length);
|
||||
for (int v : ints) {
|
||||
// minor versions should not matter
|
||||
versions.add(new MailboxVersion(v, random.nextInt(42)));
|
||||
}
|
||||
return versions;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -50,6 +51,7 @@ import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||
@@ -335,4 +337,13 @@ public class TestUtils {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isCryptoStrengthUnlimited() {
|
||||
try {
|
||||
return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding")
|
||||
== Integer.MAX_VALUE;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ abstract class Connection {
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] readTag(InputStream in) throws IOException {
|
||||
byte[] readTag(InputStream in) throws IOException {
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
read(in, tag);
|
||||
return tag;
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||
import org.briarproject.bramble.api.transport.KeyManager;
|
||||
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
||||
@@ -67,7 +68,15 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
TransportConnectionReader r) {
|
||||
ioExecutor.execute(new IncomingSimplexSyncConnection(keyManager,
|
||||
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
||||
syncSessionFactory, transportPropertyManager, t, r));
|
||||
syncSessionFactory, transportPropertyManager, t, r, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manageIncomingConnection(TransportId t,
|
||||
TransportConnectionReader r, TagController c) {
|
||||
ioExecutor.execute(new IncomingSimplexSyncConnection(keyManager,
|
||||
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
||||
syncSessionFactory, transportPropertyManager, t, r, c));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,7 +101,16 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
TransportConnectionWriter w) {
|
||||
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
|
||||
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
||||
syncSessionFactory, transportPropertyManager, c, t, w));
|
||||
syncSessionFactory, transportPropertyManager, c, t, w, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manageOutgoingConnection(ContactId c, TransportId t,
|
||||
TransportConnectionWriter w, OutgoingSessionRecord sessionRecord) {
|
||||
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
|
||||
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
||||
syncSessionFactory, transportPropertyManager, c, t, w,
|
||||
sessionRecord));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.briarproject.bramble.connection;
|
||||
|
||||
import org.briarproject.bramble.api.connection.ConnectionManager.TagController;
|
||||
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
@@ -15,6 +17,8 @@ import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@@ -23,6 +27,8 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
|
||||
|
||||
private final TransportId transportId;
|
||||
private final TransportConnectionReader reader;
|
||||
@Nullable
|
||||
private final TagController tagController;
|
||||
|
||||
IncomingSimplexSyncConnection(KeyManager keyManager,
|
||||
ConnectionRegistry connectionRegistry,
|
||||
@@ -30,33 +36,50 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
|
||||
StreamWriterFactory streamWriterFactory,
|
||||
SyncSessionFactory syncSessionFactory,
|
||||
TransportPropertyManager transportPropertyManager,
|
||||
TransportId transportId, TransportConnectionReader reader) {
|
||||
TransportId transportId,
|
||||
TransportConnectionReader reader,
|
||||
@Nullable TagController tagController) {
|
||||
super(keyManager, connectionRegistry, streamReaderFactory,
|
||||
streamWriterFactory, syncSessionFactory,
|
||||
transportPropertyManager);
|
||||
this.transportId = transportId;
|
||||
this.reader = reader;
|
||||
this.tagController = tagController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Read and recognise the tag
|
||||
StreamContext ctx = recogniseTag(reader, transportId);
|
||||
byte[] tag;
|
||||
StreamContext ctx;
|
||||
try {
|
||||
tag = readTag(reader.getInputStream());
|
||||
// If we have a tag controller, defer marking the tag as recognised
|
||||
if (tagController == null) {
|
||||
ctx = keyManager.getStreamContext(transportId, tag);
|
||||
} else {
|
||||
ctx = keyManager.getStreamContextOnly(transportId, tag);
|
||||
}
|
||||
} catch (IOException | DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
onError();
|
||||
return;
|
||||
}
|
||||
if (ctx == null) {
|
||||
LOG.info("Unrecognised tag");
|
||||
onError(false);
|
||||
onError();
|
||||
return;
|
||||
}
|
||||
ContactId contactId = ctx.getContactId();
|
||||
if (contactId == null) {
|
||||
LOG.warning("Received rendezvous stream, expected contact");
|
||||
onError(true);
|
||||
onError(tag);
|
||||
return;
|
||||
}
|
||||
if (ctx.isHandshakeMode()) {
|
||||
// TODO: Support handshake mode for contacts
|
||||
LOG.warning("Received handshake tag, expected rotation mode");
|
||||
onError(true);
|
||||
onError(tag);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -65,15 +88,33 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
|
||||
LOG.info("Ignoring priority for simplex connection");
|
||||
// Create and run the incoming session
|
||||
createIncomingSession(ctx, reader, handler).run();
|
||||
// Success
|
||||
markTagAsRecognisedIfRequired(false, tag);
|
||||
reader.dispose(false, true);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
onError(true);
|
||||
onError(tag);
|
||||
}
|
||||
}
|
||||
|
||||
private void onError(boolean recognised) {
|
||||
disposeOnError(reader, recognised);
|
||||
private void onError() {
|
||||
disposeOnError(reader, false);
|
||||
}
|
||||
|
||||
private void onError(byte[] tag) {
|
||||
markTagAsRecognisedIfRequired(true, tag);
|
||||
disposeOnError(reader, true);
|
||||
}
|
||||
|
||||
private void markTagAsRecognisedIfRequired(boolean exception, byte[] tag) {
|
||||
if (tagController != null &&
|
||||
tagController.shouldMarkTagAsRecognised(exception)) {
|
||||
try {
|
||||
keyManager.markTagAsRecognised(transportId, tag);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||
import org.briarproject.bramble.api.sync.SyncSession;
|
||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||
import org.briarproject.bramble.api.transport.KeyManager;
|
||||
@@ -16,6 +17,8 @@ import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
@@ -26,6 +29,8 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
||||
private final ContactId contactId;
|
||||
private final TransportId transportId;
|
||||
private final TransportConnectionWriter writer;
|
||||
@Nullable
|
||||
private final OutgoingSessionRecord sessionRecord;
|
||||
|
||||
OutgoingSimplexSyncConnection(KeyManager keyManager,
|
||||
ConnectionRegistry connectionRegistry,
|
||||
@@ -34,13 +39,15 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
||||
SyncSessionFactory syncSessionFactory,
|
||||
TransportPropertyManager transportPropertyManager,
|
||||
ContactId contactId, TransportId transportId,
|
||||
TransportConnectionWriter writer) {
|
||||
TransportConnectionWriter writer,
|
||||
@Nullable OutgoingSessionRecord sessionRecord) {
|
||||
super(keyManager, connectionRegistry, streamReaderFactory,
|
||||
streamWriterFactory, syncSessionFactory,
|
||||
transportPropertyManager);
|
||||
this.contactId = contactId;
|
||||
this.transportId = transportId;
|
||||
this.writer = writer;
|
||||
this.sessionRecord = sessionRecord;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -71,10 +78,16 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
||||
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
||||
w.getOutputStream(), ctx);
|
||||
ContactId c = requireNonNull(ctx.getContactId());
|
||||
// Use eager retransmission if the transport is lossy and cheap
|
||||
return syncSessionFactory.createSimplexOutgoingSession(c,
|
||||
ctx.getTransportId(), w.getMaxLatency(), w.isLossyAndCheap(),
|
||||
streamWriter);
|
||||
if (sessionRecord == null) {
|
||||
// Use eager retransmission if the transport is lossy and cheap
|
||||
return syncSessionFactory.createSimplexOutgoingSession(c,
|
||||
ctx.getTransportId(), w.getMaxLatency(),
|
||||
w.isLossyAndCheap(), streamWriter);
|
||||
} else {
|
||||
return syncSessionFactory.createSimplexOutgoingSession(c,
|
||||
ctx.getTransportId(), w.getMaxLatency(), streamWriter,
|
||||
sessionRecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -163,16 +163,11 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if there are any acks or messages to send to the given
|
||||
* contact over a transport with the given maximum latency.
|
||||
* Returns true if there are any acks to send to the given contact.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*
|
||||
* @param eager True if messages that are not yet due for retransmission
|
||||
* should be included
|
||||
*/
|
||||
boolean containsAnythingToSend(T txn, ContactId c, long maxLatency,
|
||||
boolean eager) throws DbException;
|
||||
boolean containsAcksToSend(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given contact for the given
|
||||
@@ -212,6 +207,18 @@ interface Database<T> {
|
||||
*/
|
||||
boolean containsMessage(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if there are any messages to send to the given
|
||||
* contact over a transport with the given maximum latency.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*
|
||||
* @param eager True if messages that are not yet due for retransmission
|
||||
* should be included
|
||||
*/
|
||||
boolean containsMessagesToSend(T txn, ContactId c, long maxLatency,
|
||||
boolean eager) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given pending contact.
|
||||
* <p/>
|
||||
@@ -580,13 +587,16 @@ interface Database<T> {
|
||||
|
||||
/**
|
||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||
* message is due to be sent to the given contact. The returned value may
|
||||
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE
|
||||
* if no messages are scheduled to be sent.
|
||||
* message is due to be sent to the given contact over a transport with
|
||||
* the given latency.
|
||||
* <p>
|
||||
* The returned value may be zero if a message is due to be sent
|
||||
* immediately, or Long.MAX_VALUE if no messages are scheduled to be sent.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
long getNextSendTime(T txn, ContactId c) throws DbException;
|
||||
long getNextSendTime(T txn, ContactId c, long maxLatency)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the pending contact with the given ID.
|
||||
|
||||
@@ -342,12 +342,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAnythingToSend(Transaction transaction, ContactId c,
|
||||
long maxLatency, boolean eager) throws DbException {
|
||||
public boolean containsAcksToSend(Transaction transaction, ContactId c)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
return db.containsAnythingToSend(txn, c, maxLatency, eager);
|
||||
return db.containsAcksToSend(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -373,6 +373,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.containsIdentity(txn, a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsMessagesToSend(Transaction transaction, ContactId c,
|
||||
long maxLatency, boolean eager) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
return db.containsMessagesToSend(txn, c, maxLatency, eager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsPendingContact(Transaction transaction,
|
||||
PendingContactId p) throws DbException {
|
||||
@@ -805,10 +814,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextSendTime(Transaction transaction, ContactId c)
|
||||
throws DbException {
|
||||
public long getNextSendTime(Transaction transaction, ContactId c,
|
||||
long maxLatency) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getNextSendTime(txn, c);
|
||||
return db.getNextSendTime(txn, c, maxLatency);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1016,7 +1025,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
db.getGroupVisibility(txn, id).keySet();
|
||||
db.removeGroup(txn, id);
|
||||
transaction.attach(new GroupRemovedEvent(g));
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(INVISIBLE,
|
||||
affected));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1141,7 +1151,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
|
||||
else db.setGroupVisibility(txn, c, g, v == SHARED);
|
||||
List<ContactId> affected = singletonList(c);
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(v, affected));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1147,8 +1147,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAnythingToSend(Connection txn, ContactId c,
|
||||
long maxLatency, boolean eager) throws DbException {
|
||||
public boolean containsAcksToSend(Connection txn, ContactId c)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -1160,34 +1160,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
boolean acksToSend = rs.next();
|
||||
rs.close();
|
||||
ps.close();
|
||||
if (acksToSend) return true;
|
||||
if (eager) {
|
||||
sql = "SELECT NULL from statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
} else {
|
||||
long now = clock.currentTimeMillis();
|
||||
sql = "SELECT NULL FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||
+ " OR ? < maxLatency)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, maxLatency);
|
||||
}
|
||||
rs = ps.executeQuery();
|
||||
boolean messagesToSend = rs.next();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return messagesToSend;
|
||||
return acksToSend;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
@@ -1307,6 +1280,46 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsMessagesToSend(Connection txn, ContactId c,
|
||||
long maxLatency, boolean eager) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
if (eager) {
|
||||
String sql = "SELECT NULL from statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
} else {
|
||||
long now = clock.currentTimeMillis();
|
||||
String sql = "SELECT NULL FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||
+ " OR ? < maxLatency)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, maxLatency);
|
||||
}
|
||||
rs = ps.executeQuery();
|
||||
boolean messagesToSend = rs.next();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return messagesToSend;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsPendingContact(Connection txn, PendingContactId p)
|
||||
throws DbException {
|
||||
@@ -2477,12 +2490,28 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextSendTime(Connection txn, ContactId c)
|
||||
public long getNextSendTime(Connection txn, ContactId c, long maxLatency)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT expiry FROM statuses"
|
||||
// Are any messages sendable immediately?
|
||||
String sql = "SELECT NULL FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
+ " AND (maxLatency IS NULL OR ? < maxLatency)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, maxLatency);
|
||||
rs = ps.executeQuery();
|
||||
boolean found = rs.next();
|
||||
rs.close();
|
||||
ps.close();
|
||||
if (found) return 0;
|
||||
// When is the earliest expiry time (could be in the past)?
|
||||
sql = "SELECT expiry FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
|
||||
@@ -190,6 +190,10 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (state == STOPPING) {
|
||||
LOG.info("Already stopped");
|
||||
return;
|
||||
}
|
||||
LOG.info("Stopping services");
|
||||
state = STOPPING;
|
||||
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
||||
|
||||
@@ -22,10 +22,21 @@ interface ConnectivityChecker {
|
||||
* the check succeeds. If a check is already running then the observer is
|
||||
* called when the check succeeds. If a connectivity check has recently
|
||||
* succeeded then the observer is called immediately.
|
||||
* <p>
|
||||
* Observers are removed after being called, or when the checker is
|
||||
* {@link #destroy() destroyed}.
|
||||
*/
|
||||
void checkConnectivity(MailboxProperties properties,
|
||||
ConnectivityObserver o);
|
||||
|
||||
/**
|
||||
* Removes an observer that was added via
|
||||
* {@link #checkConnectivity(MailboxProperties, ConnectivityObserver)}. If
|
||||
* there are no remaining observers and a connectivity check is running
|
||||
* then the check will be cancelled.
|
||||
*/
|
||||
void removeObserver(ConnectivityObserver o);
|
||||
|
||||
interface ConnectivityObserver {
|
||||
void onConnectivityCheckSucceeded();
|
||||
}
|
||||
|
||||
@@ -80,8 +80,7 @@ abstract class ConnectivityCheckerImpl implements ConnectivityChecker {
|
||||
> CONNECTIVITY_CHECK_FRESHNESS_MS) {
|
||||
// The last connectivity check is stale, start a new one
|
||||
connectivityObservers.add(o);
|
||||
ApiCall task =
|
||||
createConnectivityCheckTask(properties);
|
||||
ApiCall task = createConnectivityCheckTask(properties);
|
||||
connectivityCheck = mailboxApiCaller.retryWithBackoff(task);
|
||||
} else {
|
||||
// The last connectivity check is fresh
|
||||
@@ -108,4 +107,16 @@ abstract class ConnectivityCheckerImpl implements ConnectivityChecker {
|
||||
o.onConnectivityCheckSucceeded();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeObserver(ConnectivityObserver o) {
|
||||
synchronized (lock) {
|
||||
if (destroyed) return;
|
||||
connectivityObservers.remove(o);
|
||||
if (connectivityObservers.isEmpty() && connectivityCheck != null) {
|
||||
connectivityCheck.cancel();
|
||||
connectivityCheck = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class ContactMailboxClient implements MailboxClient {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ContactMailboxClient.class.getName());
|
||||
|
||||
private final MailboxWorkerFactory workerFactory;
|
||||
private final ConnectivityChecker connectivityChecker;
|
||||
private final TorReachabilityMonitor reachabilityMonitor;
|
||||
private final Object lock = new Object();
|
||||
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private MailboxWorker uploadWorker = null, downloadWorker = null;
|
||||
|
||||
@Inject
|
||||
ContactMailboxClient(MailboxWorkerFactory workerFactory,
|
||||
ConnectivityChecker connectivityChecker,
|
||||
TorReachabilityMonitor reachabilityMonitor) {
|
||||
this.workerFactory = workerFactory;
|
||||
this.connectivityChecker = connectivityChecker;
|
||||
this.reachabilityMonitor = reachabilityMonitor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
LOG.info("Started");
|
||||
// Nothing to do until contact is assigned
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
LOG.info("Destroyed");
|
||||
MailboxWorker uploadWorker, downloadWorker;
|
||||
synchronized (lock) {
|
||||
uploadWorker = this.uploadWorker;
|
||||
this.uploadWorker = null;
|
||||
downloadWorker = this.downloadWorker;
|
||||
this.downloadWorker = null;
|
||||
}
|
||||
if (uploadWorker != null) uploadWorker.destroy();
|
||||
if (downloadWorker != null) downloadWorker.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assignContactForUpload(ContactId contactId,
|
||||
MailboxProperties properties, MailboxFolderId folderId) {
|
||||
LOG.info("Contact assigned for upload");
|
||||
if (properties.isOwner()) throw new IllegalArgumentException();
|
||||
// For a contact's mailbox we should always be uploading to the outbox
|
||||
// assigned to us by the contact
|
||||
if (!folderId.equals(properties.getOutboxId())) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
MailboxWorker uploadWorker = workerFactory.createUploadWorker(
|
||||
connectivityChecker, properties, folderId, contactId);
|
||||
synchronized (lock) {
|
||||
if (this.uploadWorker != null) throw new IllegalStateException();
|
||||
this.uploadWorker = uploadWorker;
|
||||
}
|
||||
uploadWorker.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deassignContactForUpload(ContactId contactId) {
|
||||
LOG.info("Contact deassigned for upload");
|
||||
MailboxWorker uploadWorker;
|
||||
synchronized (lock) {
|
||||
uploadWorker = this.uploadWorker;
|
||||
this.uploadWorker = null;
|
||||
}
|
||||
if (uploadWorker != null) uploadWorker.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assignContactForDownload(ContactId contactId,
|
||||
MailboxProperties properties, MailboxFolderId folderId) {
|
||||
LOG.info("Contact assigned for download");
|
||||
if (properties.isOwner()) throw new IllegalArgumentException();
|
||||
// For a contact's mailbox we should always be downloading from the
|
||||
// inbox assigned to us by the contact
|
||||
if (!folderId.equals(properties.getInboxId())) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
MailboxWorker downloadWorker =
|
||||
workerFactory.createDownloadWorkerForContactMailbox(
|
||||
connectivityChecker, reachabilityMonitor, properties);
|
||||
synchronized (lock) {
|
||||
if (this.downloadWorker != null) throw new IllegalStateException();
|
||||
this.downloadWorker = downloadWorker;
|
||||
}
|
||||
downloadWorker.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deassignContactForDownload(ContactId contactId) {
|
||||
LOG.info("Contact deassigned for download");
|
||||
MailboxWorker downloadWorker;
|
||||
synchronized (lock) {
|
||||
downloadWorker = this.downloadWorker;
|
||||
this.downloadWorker = null;
|
||||
}
|
||||
if (downloadWorker != null) downloadWorker.destroy();
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
@ThreadSafe
|
||||
@@ -24,16 +22,11 @@ class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
||||
@Override
|
||||
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
|
||||
if (properties.isOwner()) throw new IllegalArgumentException();
|
||||
return new SimpleApiCall() {
|
||||
@Override
|
||||
void tryToCallApi() throws IOException, ApiException {
|
||||
if (!mailboxApi.checkStatus(properties)) {
|
||||
throw new ApiException();
|
||||
}
|
||||
// Call the observers and cache the result
|
||||
onConnectivityCheckSucceeded(clock.currentTimeMillis());
|
||||
}
|
||||
};
|
||||
return new SimpleApiCall(() -> {
|
||||
if (!mailboxApi.checkStatus(properties)) throw new ApiException();
|
||||
// Call the observers and cache the result
|
||||
onConnectivityCheckSucceeded(clock.currentTimeMillis());
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.Cancellable;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
||||
import org.briarproject.bramble.mailbox.TorReachabilityMonitor.TorReachabilityObserver;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class ContactMailboxDownloadWorker implements MailboxWorker,
|
||||
ConnectivityObserver, TorReachabilityObserver {
|
||||
|
||||
/**
|
||||
* When the worker is started it waits for a connectivity check, then
|
||||
* starts its first download cycle: checking the inbox, downloading and
|
||||
* deleting any files, and checking again until the inbox is empty.
|
||||
* <p>
|
||||
* The worker then waits for our Tor hidden service to be reachable before
|
||||
* starting its second download cycle. This ensures that if a contact
|
||||
* tried and failed to connect to our hidden service before it was
|
||||
* reachable, and therefore uploaded a file to the mailbox instead, we'll
|
||||
* find the file in the second download cycle.
|
||||
*/
|
||||
private enum State {
|
||||
CREATED,
|
||||
CONNECTIVITY_CHECK,
|
||||
DOWNLOAD_CYCLE_1,
|
||||
WAITING_FOR_TOR,
|
||||
DOWNLOAD_CYCLE_2,
|
||||
FINISHED,
|
||||
DESTROYED
|
||||
}
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ContactMailboxDownloadWorker.class.getName());
|
||||
|
||||
private final ConnectivityChecker connectivityChecker;
|
||||
private final TorReachabilityMonitor torReachabilityMonitor;
|
||||
private final MailboxApiCaller mailboxApiCaller;
|
||||
private final MailboxApi mailboxApi;
|
||||
private final MailboxFileManager mailboxFileManager;
|
||||
private final MailboxProperties mailboxProperties;
|
||||
private final Object lock = new Object();
|
||||
|
||||
@GuardedBy("lock")
|
||||
private State state = State.CREATED;
|
||||
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private Cancellable apiCall = null;
|
||||
|
||||
ContactMailboxDownloadWorker(
|
||||
ConnectivityChecker connectivityChecker,
|
||||
TorReachabilityMonitor torReachabilityMonitor,
|
||||
MailboxApiCaller mailboxApiCaller,
|
||||
MailboxApi mailboxApi,
|
||||
MailboxFileManager mailboxFileManager,
|
||||
MailboxProperties mailboxProperties) {
|
||||
if (mailboxProperties.isOwner()) throw new IllegalArgumentException();
|
||||
this.connectivityChecker = connectivityChecker;
|
||||
this.torReachabilityMonitor = torReachabilityMonitor;
|
||||
this.mailboxApiCaller = mailboxApiCaller;
|
||||
this.mailboxApi = mailboxApi;
|
||||
this.mailboxFileManager = mailboxFileManager;
|
||||
this.mailboxProperties = mailboxProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
LOG.info("Started");
|
||||
synchronized (lock) {
|
||||
// Don't allow the worker to be reused
|
||||
if (state != State.CREATED) return;
|
||||
state = State.CONNECTIVITY_CHECK;
|
||||
}
|
||||
// Avoid leaking observer in case destroy() is called concurrently
|
||||
// before observer is added
|
||||
connectivityChecker.checkConnectivity(mailboxProperties, this);
|
||||
boolean destroyed;
|
||||
synchronized (lock) {
|
||||
destroyed = state == State.DESTROYED;
|
||||
}
|
||||
if (destroyed) connectivityChecker.removeObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
LOG.info("Destroyed");
|
||||
Cancellable apiCall;
|
||||
synchronized (lock) {
|
||||
state = State.DESTROYED;
|
||||
apiCall = this.apiCall;
|
||||
this.apiCall = null;
|
||||
}
|
||||
if (apiCall != null) apiCall.cancel();
|
||||
connectivityChecker.removeObserver(this);
|
||||
torReachabilityMonitor.removeObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectivityCheckSucceeded() {
|
||||
LOG.info("Connectivity check succeeded");
|
||||
synchronized (lock) {
|
||||
if (state != State.CONNECTIVITY_CHECK) return;
|
||||
state = State.DOWNLOAD_CYCLE_1;
|
||||
// Start first download cycle
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
new SimpleApiCall(this::apiCallListInbox));
|
||||
}
|
||||
}
|
||||
|
||||
private void apiCallListInbox() throws IOException, ApiException {
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
}
|
||||
LOG.info("Listing inbox");
|
||||
List<MailboxFile> files = mailboxApi.getFiles(mailboxProperties,
|
||||
requireNonNull(mailboxProperties.getInboxId()));
|
||||
if (files.isEmpty()) onDownloadCycleFinished();
|
||||
else downloadNextFile(new LinkedList<>(files));
|
||||
}
|
||||
|
||||
private void onDownloadCycleFinished() {
|
||||
boolean addObserver = false;
|
||||
synchronized (lock) {
|
||||
if (state == State.DOWNLOAD_CYCLE_1) {
|
||||
LOG.info("First download cycle finished");
|
||||
state = State.WAITING_FOR_TOR;
|
||||
apiCall = null;
|
||||
addObserver = true;
|
||||
} else if (state == State.DOWNLOAD_CYCLE_2) {
|
||||
LOG.info("Second download cycle finished");
|
||||
state = State.FINISHED;
|
||||
apiCall = null;
|
||||
}
|
||||
}
|
||||
if (addObserver) {
|
||||
// Avoid leaking observer in case destroy() is called concurrently
|
||||
// before observer is added
|
||||
torReachabilityMonitor.addOneShotObserver(this);
|
||||
boolean destroyed;
|
||||
synchronized (lock) {
|
||||
destroyed = state == State.DESTROYED;
|
||||
}
|
||||
if (destroyed) torReachabilityMonitor.removeObserver(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadNextFile(Queue<MailboxFile> queue) {
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
MailboxFile file = queue.remove();
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
new SimpleApiCall(() -> apiCallDownloadFile(file, queue)));
|
||||
}
|
||||
}
|
||||
|
||||
private void apiCallDownloadFile(MailboxFile file,
|
||||
Queue<MailboxFile> queue) throws IOException, ApiException {
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
}
|
||||
LOG.info("Downloading file");
|
||||
File tempFile = mailboxFileManager.createTempFileForDownload();
|
||||
try {
|
||||
mailboxApi.getFile(mailboxProperties,
|
||||
requireNonNull(mailboxProperties.getInboxId()),
|
||||
file.name, tempFile);
|
||||
} catch (IOException | ApiException e) {
|
||||
if (!tempFile.delete()) {
|
||||
LOG.warning("Failed to delete temporary file");
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
mailboxFileManager.handleDownloadedFile(tempFile);
|
||||
deleteFile(file, queue);
|
||||
}
|
||||
|
||||
private void deleteFile(MailboxFile file, Queue<MailboxFile> queue) {
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
new SimpleApiCall(() -> apiCallDeleteFile(file, queue)));
|
||||
}
|
||||
}
|
||||
|
||||
private void apiCallDeleteFile(MailboxFile file, Queue<MailboxFile> queue)
|
||||
throws IOException, ApiException {
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
}
|
||||
try {
|
||||
mailboxApi.deleteFile(mailboxProperties,
|
||||
requireNonNull(mailboxProperties.getInboxId()), file.name);
|
||||
} catch (TolerableFailureException e) {
|
||||
// Catch this so we can continue to the next file
|
||||
logException(LOG, INFO, e);
|
||||
}
|
||||
if (queue.isEmpty()) {
|
||||
// List the inbox again to check for files that may have arrived
|
||||
// while we were downloading
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
new SimpleApiCall(this::apiCallListInbox));
|
||||
}
|
||||
} else {
|
||||
downloadNextFile(queue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTorReachable() {
|
||||
LOG.info("Our Tor hidden service is reachable");
|
||||
synchronized (lock) {
|
||||
if (state != State.WAITING_FOR_TOR) return;
|
||||
state = State.DOWNLOAD_CYCLE_2;
|
||||
// Start second download cycle
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
new SimpleApiCall(this::apiCallListInbox));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFileId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@@ -19,18 +18,9 @@ import java.util.List;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
@NotNullByDefault
|
||||
interface MailboxApi {
|
||||
|
||||
/**
|
||||
* Mailbox API versions that we support as a client. This is reported to our
|
||||
* contacts by {@link MailboxUpdateManager}.
|
||||
*/
|
||||
List<MailboxVersion> CLIENT_SUPPORTS = singletonList(
|
||||
new MailboxVersion(1, 0));
|
||||
|
||||
List<MailboxVersion> getServerSupports(MailboxProperties properties)
|
||||
throws IOException, ApiException;
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ interface MailboxApiCaller {
|
||||
* Asynchronously calls the given API call on the {@link IoExecutor},
|
||||
* automatically retrying at increasing intervals until the API call
|
||||
* returns false or retries are cancelled.
|
||||
* <p>
|
||||
* This method is safe to call while holding a lock.
|
||||
*
|
||||
* @return A {@link Cancellable} that can be used to cancel any future
|
||||
* retries.
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
interface MailboxClient {
|
||||
|
||||
/**
|
||||
* Asynchronously starts the client.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Destroys the client and its workers, cancelling any pending tasks or
|
||||
* retries.
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* Assigns a contact to the client for upload.
|
||||
*/
|
||||
void assignContactForUpload(ContactId c, MailboxProperties properties,
|
||||
MailboxFolderId folderId);
|
||||
|
||||
/**
|
||||
* Deassigns a contact from the client for upload.
|
||||
*/
|
||||
void deassignContactForUpload(ContactId c);
|
||||
|
||||
/**
|
||||
* Assigns a contact to the client for download.
|
||||
*/
|
||||
void assignContactForDownload(ContactId c, MailboxProperties properties,
|
||||
MailboxFolderId folderId);
|
||||
|
||||
/**
|
||||
* Deassigns a contact from the client for download.
|
||||
*/
|
||||
void deassignContactForDownload(ContactId c);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
interface MailboxFileManager {
|
||||
|
||||
/**
|
||||
* Creates an empty file for storing a download.
|
||||
*/
|
||||
File createTempFileForDownload() throws IOException;
|
||||
|
||||
/**
|
||||
* Creates a file to be uploaded to the given contact and writes any
|
||||
* waiting data to the file. The IDs of any messages sent or acked will
|
||||
* be added to the given {@link OutgoingSessionRecord}.
|
||||
*/
|
||||
File createAndWriteTempFileForUpload(ContactId contactId,
|
||||
OutgoingSessionRecord sessionRecord) throws IOException;
|
||||
|
||||
/**
|
||||
* Handles a file that has been downloaded. The file should be created
|
||||
* with {@link #createTempFileForDownload()}.
|
||||
*/
|
||||
void handleDownloadedFile(File f);
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxDirectory;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
|
||||
import static org.briarproject.bramble.util.IoUtils.delete;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(MailboxFileManagerImpl.class.getName());
|
||||
|
||||
// Package access for testing
|
||||
static final String DOWNLOAD_DIR_NAME = "downloads";
|
||||
static final String UPLOAD_DIR_NAME = "uploads";
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final PluginManager pluginManager;
|
||||
private final ConnectionManager connectionManager;
|
||||
private final LifecycleManager lifecycleManager;
|
||||
private final File mailboxDir;
|
||||
private final EventBus eventBus;
|
||||
private final CountDownLatch orphanLatch = new CountDownLatch(1);
|
||||
|
||||
@Inject
|
||||
MailboxFileManagerImpl(@IoExecutor Executor ioExecutor,
|
||||
PluginManager pluginManager,
|
||||
ConnectionManager connectionManager,
|
||||
LifecycleManager lifecycleManager,
|
||||
@MailboxDirectory File mailboxDir,
|
||||
EventBus eventBus) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.pluginManager = pluginManager;
|
||||
this.connectionManager = connectionManager;
|
||||
this.lifecycleManager = lifecycleManager;
|
||||
this.mailboxDir = mailboxDir;
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File createTempFileForDownload() throws IOException {
|
||||
return createTempFile(DOWNLOAD_DIR_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File createAndWriteTempFileForUpload(ContactId contactId,
|
||||
OutgoingSessionRecord sessionRecord) throws IOException {
|
||||
File f = createTempFile(UPLOAD_DIR_NAME);
|
||||
// We shouldn't reach this point until the plugin has been started
|
||||
SimplexPlugin plugin =
|
||||
(SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID));
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_PATH, f.getAbsolutePath());
|
||||
TransportConnectionWriter writer = plugin.createWriter(p);
|
||||
if (writer == null) {
|
||||
delete(f);
|
||||
throw new IOException();
|
||||
}
|
||||
MailboxFileWriter decorated = new MailboxFileWriter(writer);
|
||||
LOG.info("Writing file for upload");
|
||||
connectionManager.manageOutgoingConnection(contactId, ID, decorated,
|
||||
sessionRecord);
|
||||
if (decorated.awaitDisposal()) {
|
||||
// An exception was thrown during the session - delete the file
|
||||
delete(f);
|
||||
throw new IOException();
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
private File createTempFile(String dirName) throws IOException {
|
||||
// Wait for orphaned files to be handled before creating new files
|
||||
try {
|
||||
orphanLatch.await();
|
||||
} catch (InterruptedException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
File dir = createDirectoryIfNeeded(dirName);
|
||||
return File.createTempFile("mailbox", ".tmp", dir);
|
||||
}
|
||||
|
||||
private File createDirectoryIfNeeded(String name) throws IOException {
|
||||
File dir = new File(mailboxDir, name);
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
dir.mkdirs();
|
||||
if (!dir.isDirectory()) {
|
||||
throw new IOException("Failed to create directory '" + name + "'");
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDownloadedFile(File f) {
|
||||
// We shouldn't reach this point until the plugin has been started
|
||||
SimplexPlugin plugin =
|
||||
(SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID));
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_PATH, f.getAbsolutePath());
|
||||
TransportConnectionReader reader = plugin.createReader(p);
|
||||
if (reader == null) {
|
||||
LOG.warning("Failed to create reader for downloaded file");
|
||||
return;
|
||||
}
|
||||
TransportConnectionReader decorated = new MailboxFileReader(reader, f);
|
||||
LOG.info("Reading downloaded file");
|
||||
connectionManager.manageIncomingConnection(ID, decorated,
|
||||
exception -> isHandlingComplete(exception, true));
|
||||
}
|
||||
|
||||
private boolean isHandlingComplete(boolean exception, boolean recognised) {
|
||||
// If we've successfully read the file then we're done
|
||||
if (!exception && recognised) return true;
|
||||
// If the app is shutting down we may get spurious IO exceptions
|
||||
// due to executors being shut down. Leave the file in the download
|
||||
// directory and we'll try to read it again at the next startup
|
||||
return !lifecycleManager.getLifecycleState().isAfter(RUNNING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
// Wait for the transport to become active before handling orphaned
|
||||
// files so that we can get the plugin from the plugin manager
|
||||
if (e instanceof TransportActiveEvent) {
|
||||
TransportActiveEvent t = (TransportActiveEvent) e;
|
||||
if (t.getTransportId().equals(ID)) {
|
||||
ioExecutor.execute(this::handleOrphanedFiles);
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called at startup, as soon as the plugin is started, to
|
||||
* delete any files that were left in the upload directory at the last
|
||||
* shutdown and handle any files that were left in the download directory.
|
||||
*/
|
||||
@IoExecutor
|
||||
private void handleOrphanedFiles() {
|
||||
try {
|
||||
File uploadDir = createDirectoryIfNeeded(UPLOAD_DIR_NAME);
|
||||
File[] orphanedUploads = uploadDir.listFiles();
|
||||
if (orphanedUploads != null) {
|
||||
for (File f : orphanedUploads) delete(f);
|
||||
}
|
||||
File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
|
||||
File[] orphanedDownloads = downloadDir.listFiles();
|
||||
// Now that we've got the list of orphaned downloads, new files
|
||||
// can be created in the download directory
|
||||
orphanLatch.countDown();
|
||||
if (orphanedDownloads != null) {
|
||||
for (File f : orphanedDownloads) handleDownloadedFile(f);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
|
||||
private class MailboxFileReader implements TransportConnectionReader {
|
||||
|
||||
private final TransportConnectionReader delegate;
|
||||
private final File file;
|
||||
|
||||
private MailboxFileReader(TransportConnectionReader delegate,
|
||||
File file) {
|
||||
this.delegate = delegate;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return delegate.getInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(boolean exception, boolean recognised)
|
||||
throws IOException {
|
||||
delegate.dispose(exception, recognised);
|
||||
if (isHandlingComplete(exception, recognised)) {
|
||||
LOG.info("Deleting downloaded file");
|
||||
delete(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class MailboxFileWriter
|
||||
implements TransportConnectionWriter {
|
||||
|
||||
private final TransportConnectionWriter delegate;
|
||||
private final BlockingQueue<Boolean> disposalResult =
|
||||
new ArrayBlockingQueue<>(1);
|
||||
|
||||
private MailboxFileWriter(TransportConnectionWriter delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxLatency() {
|
||||
return delegate.getMaxLatency();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxIdleTime() {
|
||||
return delegate.getMaxIdleTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLossyAndCheap() {
|
||||
return delegate.isLossyAndCheap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
return delegate.getOutputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(boolean exception) throws IOException {
|
||||
delegate.dispose(exception);
|
||||
disposalResult.add(exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the delegate to be disposed and returns true if an
|
||||
* exception occurred.
|
||||
*/
|
||||
private boolean awaitDisposal() {
|
||||
try {
|
||||
return disposalResult.take();
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while waiting for disposal");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,12 @@ import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -98,34 +100,35 @@ class MailboxManagerImpl implements MailboxManager {
|
||||
|
||||
@Override
|
||||
public boolean checkConnection() {
|
||||
boolean success;
|
||||
List<MailboxVersion> versions = null;
|
||||
try {
|
||||
MailboxProperties props = db.transactionWithNullableResult(true,
|
||||
mailboxSettingsManager::getOwnMailboxProperties);
|
||||
if (props == null) throw new DbException();
|
||||
success = api.checkStatus(props);
|
||||
versions = api.getServerSupports(props);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
// we don't treat this is a failure to record
|
||||
return false;
|
||||
} catch (IOException | MailboxApi.ApiException e) {
|
||||
// we record this as a failure
|
||||
success = false;
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
try {
|
||||
recordCheckResult(success);
|
||||
recordCheckResult(versions);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
return success;
|
||||
return versions != null;
|
||||
}
|
||||
|
||||
private void recordCheckResult(boolean success) throws DbException {
|
||||
private void recordCheckResult(@Nullable List<MailboxVersion> versions)
|
||||
throws DbException {
|
||||
long now = clock.currentTimeMillis();
|
||||
db.transaction(false, txn -> {
|
||||
if (success) {
|
||||
mailboxSettingsManager.recordSuccessfulConnection(txn, now);
|
||||
if (versions != null) {
|
||||
mailboxSettingsManager
|
||||
.recordSuccessfulConnection(txn, now, versions);
|
||||
} else {
|
||||
mailboxSettingsManager.recordFailedConnectionAttempt(txn, now);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.FeatureFlags;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
@@ -21,10 +22,10 @@ import javax.inject.Singleton;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.CLIENT_ID;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MAJOR_VERSION;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MINOR_VERSION;
|
||||
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
|
||||
|
||||
@Module
|
||||
public class MailboxModule {
|
||||
@@ -34,6 +35,8 @@ public class MailboxModule {
|
||||
MailboxUpdateValidator mailboxUpdateValidator;
|
||||
@Inject
|
||||
MailboxUpdateManager mailboxUpdateManager;
|
||||
@Inject
|
||||
MailboxFileManager mailboxFileManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -49,6 +52,7 @@ public class MailboxModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
MailboxSettingsManager provideMailboxSettingsManager(
|
||||
MailboxSettingsManagerImpl mailboxSettingsManager) {
|
||||
return mailboxSettingsManager;
|
||||
@@ -101,4 +105,20 @@ public class MailboxModule {
|
||||
}
|
||||
return mailboxUpdateManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
MailboxFileManager provideMailboxFileManager(FeatureFlags featureFlags,
|
||||
EventBus eventBus, MailboxFileManagerImpl mailboxFileManager) {
|
||||
if (featureFlags.shouldEnableMailbox()) {
|
||||
eventBus.addListener(mailboxFileManager);
|
||||
}
|
||||
return mailboxFileManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
MailboxWorkerFactory provideMailboxWorkerFactory(
|
||||
MailboxWorkerFactoryImpl mailboxWorkerFactory) {
|
||||
return mailboxWorkerFactory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,13 @@ import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
|
||||
@Immutable
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
|
||||
@@ -60,15 +61,7 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
String onion = s.get(SETTINGS_KEY_ONION);
|
||||
String token = s.get(SETTINGS_KEY_TOKEN);
|
||||
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return null;
|
||||
int[] ints = s.getIntArray(SETTINGS_KEY_SERVER_SUPPORTS);
|
||||
// We know we were paired, so we must have proper serverSupports
|
||||
if (ints == null || ints.length == 0 || ints.length % 2 != 0) {
|
||||
throw new DbException();
|
||||
}
|
||||
List<MailboxVersion> serverSupports = new ArrayList<>();
|
||||
for (int i = 0; i < ints.length - 1; i += 2) {
|
||||
serverSupports.add(new MailboxVersion(ints[i], ints[i + 1]));
|
||||
}
|
||||
List<MailboxVersion> serverSupports = parseServerSupports(s);
|
||||
try {
|
||||
MailboxAuthToken tokenId = MailboxAuthToken.fromString(token);
|
||||
return new MailboxProperties(onion, tokenId, serverSupports);
|
||||
@@ -84,13 +77,7 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
s.put(SETTINGS_KEY_ONION, p.getBaseUrl());
|
||||
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
|
||||
List<MailboxVersion> serverSupports = p.getServerSupports();
|
||||
int[] ints = new int[serverSupports.size() * 2];
|
||||
int i = 0;
|
||||
for (MailboxVersion v : serverSupports) {
|
||||
ints[i++] = v.getMajor();
|
||||
ints[i++] = v.getMinor();
|
||||
}
|
||||
s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints);
|
||||
encodeServerSupports(serverSupports, s);
|
||||
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||
for (MailboxHook hook : hooks) {
|
||||
hook.mailboxPaired(txn, p.getOnion(), p.getServerSupports());
|
||||
@@ -115,18 +102,44 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
long lastAttempt = s.getLong(SETTINGS_KEY_LAST_ATTEMPT, -1);
|
||||
long lastSuccess = s.getLong(SETTINGS_KEY_LAST_SUCCESS, -1);
|
||||
int attempts = s.getInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||
return new MailboxStatus(lastAttempt, lastSuccess, attempts);
|
||||
List<MailboxVersion> serverSupports;
|
||||
try {
|
||||
serverSupports = parseServerSupports(s);
|
||||
} catch (DbException e) {
|
||||
serverSupports = emptyList();
|
||||
}
|
||||
return new MailboxStatus(lastAttempt, lastSuccess, attempts,
|
||||
serverSupports);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordSuccessfulConnection(Transaction txn, long now)
|
||||
throws DbException {
|
||||
recordSuccessfulConnection(txn, now, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordSuccessfulConnection(Transaction txn, long now,
|
||||
@Nullable List<MailboxVersion> versions) throws DbException {
|
||||
Settings s = new Settings();
|
||||
// fetch version that the server supports first
|
||||
List<MailboxVersion> serverSupports;
|
||||
if (versions == null) {
|
||||
Settings oldSettings =
|
||||
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
|
||||
serverSupports = parseServerSupports(oldSettings);
|
||||
} else {
|
||||
serverSupports = versions;
|
||||
// store new versions
|
||||
encodeServerSupports(serverSupports, s);
|
||||
}
|
||||
// now record the successful connection
|
||||
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
||||
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
|
||||
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||
MailboxStatus status = new MailboxStatus(now, now, 0);
|
||||
// broadcast status event
|
||||
MailboxStatus status = new MailboxStatus(now, now, 0, serverSupports);
|
||||
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
||||
}
|
||||
|
||||
@@ -141,7 +154,9 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
newSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
||||
newSettings.putInt(SETTINGS_KEY_ATTEMPTS, newAttempts);
|
||||
settingsManager.mergeSettings(txn, newSettings, SETTINGS_NAMESPACE);
|
||||
MailboxStatus status = new MailboxStatus(now, lastSuccess, newAttempts);
|
||||
List<MailboxVersion> serverSupports = parseServerSupports(oldSettings);
|
||||
MailboxStatus status = new MailboxStatus(now, lastSuccess, newAttempts,
|
||||
serverSupports);
|
||||
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
||||
if (status.hasProblem(now)) txn.attach(new MailboxProblemEvent());
|
||||
}
|
||||
@@ -165,4 +180,30 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
if (isNullOrEmpty(filename)) return null;
|
||||
return filename;
|
||||
}
|
||||
|
||||
private void encodeServerSupports(List<MailboxVersion> serverSupports,
|
||||
Settings s) {
|
||||
int[] ints = new int[serverSupports.size() * 2];
|
||||
int i = 0;
|
||||
for (MailboxVersion v : serverSupports) {
|
||||
ints[i++] = v.getMajor();
|
||||
ints[i++] = v.getMinor();
|
||||
}
|
||||
s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints);
|
||||
}
|
||||
|
||||
private List<MailboxVersion> parseServerSupports(Settings s)
|
||||
throws DbException {
|
||||
if (!s.containsKey(SETTINGS_KEY_SERVER_SUPPORTS)) return emptyList();
|
||||
int[] ints = s.getIntArray(SETTINGS_KEY_SERVER_SUPPORTS);
|
||||
// We know we were paired, so we must have proper serverSupports
|
||||
if (ints == null || ints.length == 0 || ints.length % 2 != 0) {
|
||||
throw new DbException();
|
||||
}
|
||||
List<MailboxVersion> serverSupports = new ArrayList<>();
|
||||
for (int i = 0; i < ints.length - 1; i += 2) {
|
||||
serverSupports.add(new MailboxVersion(ints[i], ints[i + 1]));
|
||||
}
|
||||
return serverSupports;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,394 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.Cancellable;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessageSharedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessageToAckEvent;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_LATENCY;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.util.IoUtils.delete;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class MailboxUploadWorker implements MailboxWorker, ConnectivityObserver,
|
||||
EventListener {
|
||||
|
||||
/**
|
||||
* When the worker is started it checks for data to send. If data is ready
|
||||
* to send, the worker waits for a connectivity check, then writes and
|
||||
* uploads a file and checks again for data to send.
|
||||
* <p>
|
||||
* If data is due to be sent at some time in the future, the worker
|
||||
* schedules a wakeup for that time and also listens for events indicating
|
||||
* that new data may be ready to send.
|
||||
* <p>
|
||||
* If there's no data to send, the worker listens for events indicating
|
||||
* that new data may be ready to send.
|
||||
*/
|
||||
private enum State {
|
||||
CREATED,
|
||||
CHECKING_FOR_DATA,
|
||||
WAITING_FOR_DATA,
|
||||
CONNECTIVITY_CHECK,
|
||||
WRITING_UPLOADING,
|
||||
DESTROYED
|
||||
}
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(MailboxUploadWorker.class.getName());
|
||||
|
||||
/**
|
||||
* When we're waiting for data to send and an event indicates that new data
|
||||
* may have become available, wait this long before checking the DB. This
|
||||
* should help to avoid creating lots of small files when several acks or
|
||||
* messages become available to send in a short period (eg when reading a
|
||||
* file downloaded from a mailbox).
|
||||
* <p>
|
||||
* Package access for testing.
|
||||
*/
|
||||
static final long CHECK_DELAY_MS = 5_000;
|
||||
|
||||
/**
|
||||
* How long to wait before retrying when an exception occurs while writing
|
||||
* a file.
|
||||
* <p>
|
||||
* Package access for testing.
|
||||
*/
|
||||
static final long RETRY_DELAY_MS = MINUTES.toMillis(1);
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final DatabaseComponent db;
|
||||
private final Clock clock;
|
||||
private final TaskScheduler taskScheduler;
|
||||
private final EventBus eventBus;
|
||||
private final ConnectivityChecker connectivityChecker;
|
||||
private final MailboxApiCaller mailboxApiCaller;
|
||||
private final MailboxApi mailboxApi;
|
||||
private final MailboxFileManager mailboxFileManager;
|
||||
private final MailboxProperties mailboxProperties;
|
||||
private final MailboxFolderId folderId;
|
||||
private final ContactId contactId;
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
@GuardedBy("lock")
|
||||
private State state = State.CREATED;
|
||||
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private Cancellable wakeupTask = null, checkTask = null, apiCall = null;
|
||||
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private File file = null;
|
||||
|
||||
MailboxUploadWorker(@IoExecutor Executor ioExecutor,
|
||||
DatabaseComponent db,
|
||||
Clock clock,
|
||||
TaskScheduler taskScheduler,
|
||||
EventBus eventBus,
|
||||
ConnectivityChecker connectivityChecker,
|
||||
MailboxApiCaller mailboxApiCaller,
|
||||
MailboxApi mailboxApi,
|
||||
MailboxFileManager mailboxFileManager,
|
||||
MailboxProperties mailboxProperties,
|
||||
MailboxFolderId folderId,
|
||||
ContactId contactId) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.db = db;
|
||||
this.clock = clock;
|
||||
this.taskScheduler = taskScheduler;
|
||||
this.eventBus = eventBus;
|
||||
this.connectivityChecker = connectivityChecker;
|
||||
this.mailboxApiCaller = mailboxApiCaller;
|
||||
this.mailboxApi = mailboxApi;
|
||||
this.mailboxFileManager = mailboxFileManager;
|
||||
this.mailboxProperties = mailboxProperties;
|
||||
this.folderId = folderId;
|
||||
this.contactId = contactId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
LOG.info("Started");
|
||||
synchronized (lock) {
|
||||
// Don't allow the worker to be reused
|
||||
if (state != State.CREATED) return;
|
||||
state = State.CHECKING_FOR_DATA;
|
||||
}
|
||||
ioExecutor.execute(this::checkForDataToSend);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
LOG.info("Destroyed");
|
||||
Cancellable wakeupTask, checkTask, apiCall;
|
||||
File file;
|
||||
synchronized (lock) {
|
||||
state = State.DESTROYED;
|
||||
wakeupTask = this.wakeupTask;
|
||||
this.wakeupTask = null;
|
||||
checkTask = this.checkTask;
|
||||
this.checkTask = null;
|
||||
apiCall = this.apiCall;
|
||||
this.apiCall = null;
|
||||
file = this.file;
|
||||
this.file = null;
|
||||
}
|
||||
if (wakeupTask != null) wakeupTask.cancel();
|
||||
if (checkTask != null) checkTask.cancel();
|
||||
if (apiCall != null) apiCall.cancel();
|
||||
if (file != null) delete(file);
|
||||
connectivityChecker.removeObserver(this);
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void checkForDataToSend() {
|
||||
synchronized (lock) {
|
||||
checkTask = null;
|
||||
if (state != State.CHECKING_FOR_DATA) return;
|
||||
}
|
||||
LOG.info("Checking for data to send");
|
||||
try {
|
||||
db.transaction(true, txn -> {
|
||||
long nextSendTime;
|
||||
if (db.containsAcksToSend(txn, contactId)) {
|
||||
nextSendTime = 0L;
|
||||
} else {
|
||||
nextSendTime = db.getNextSendTime(txn, contactId,
|
||||
MAX_LATENCY);
|
||||
}
|
||||
// Handle the result on the event executor to avoid races with
|
||||
// incoming events
|
||||
txn.attach(() -> handleNextSendTime(nextSendTime));
|
||||
});
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void handleNextSendTime(long nextSendTime) {
|
||||
if (nextSendTime == Long.MAX_VALUE) {
|
||||
// Nothing is sendable now or due to be sent in the future. Wait
|
||||
// for an event indicating that new data may be ready to send
|
||||
waitForDataToSend();
|
||||
} else {
|
||||
// Work out the delay until data's ready to send (may be negative)
|
||||
long delay = nextSendTime - clock.currentTimeMillis();
|
||||
if (delay > 0) {
|
||||
// Schedule a wakeup when data will be ready to send. If an
|
||||
// event is received in the meantime indicating that new data
|
||||
// may be ready to send, we'll cancel the wakeup
|
||||
scheduleWakeup(delay);
|
||||
} else {
|
||||
// Data is ready to send now
|
||||
checkConnectivity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void waitForDataToSend() {
|
||||
synchronized (lock) {
|
||||
if (state != State.CHECKING_FOR_DATA) return;
|
||||
state = State.WAITING_FOR_DATA;
|
||||
LOG.info("Waiting for data to send");
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void scheduleWakeup(long delay) {
|
||||
synchronized (lock) {
|
||||
if (state != State.CHECKING_FOR_DATA) return;
|
||||
state = State.WAITING_FOR_DATA;
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Scheduling wakeup in " + delay + " ms");
|
||||
}
|
||||
wakeupTask = taskScheduler.schedule(this::wakeUp, ioExecutor,
|
||||
delay, MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void wakeUp() {
|
||||
LOG.info("Woke up");
|
||||
synchronized (lock) {
|
||||
wakeupTask = null;
|
||||
if (state != State.WAITING_FOR_DATA) return;
|
||||
state = State.CHECKING_FOR_DATA;
|
||||
}
|
||||
checkForDataToSend();
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void checkConnectivity() {
|
||||
synchronized (lock) {
|
||||
if (state != State.CHECKING_FOR_DATA) return;
|
||||
state = State.CONNECTIVITY_CHECK;
|
||||
}
|
||||
LOG.info("Checking connectivity");
|
||||
// Avoid leaking observer in case destroy() is called concurrently
|
||||
// before observer is added
|
||||
connectivityChecker.checkConnectivity(mailboxProperties, this);
|
||||
boolean destroyed;
|
||||
synchronized (lock) {
|
||||
destroyed = state == State.DESTROYED;
|
||||
}
|
||||
if (destroyed) connectivityChecker.removeObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectivityCheckSucceeded() {
|
||||
LOG.info("Connectivity check succeeded");
|
||||
synchronized (lock) {
|
||||
if (state != State.CONNECTIVITY_CHECK) return;
|
||||
state = State.WRITING_UPLOADING;
|
||||
}
|
||||
ioExecutor.execute(this::writeAndUploadFile);
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void writeAndUploadFile() {
|
||||
synchronized (lock) {
|
||||
if (state != State.WRITING_UPLOADING) return;
|
||||
}
|
||||
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
|
||||
File file;
|
||||
try {
|
||||
file = mailboxFileManager.createAndWriteTempFileForUpload(
|
||||
contactId, sessionRecord);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
// Try again after a delay
|
||||
synchronized (lock) {
|
||||
if (state != State.WRITING_UPLOADING) return;
|
||||
state = State.CHECKING_FOR_DATA;
|
||||
checkTask = taskScheduler.schedule(this::checkForDataToSend,
|
||||
ioExecutor, RETRY_DELAY_MS, MILLISECONDS);
|
||||
}
|
||||
return;
|
||||
}
|
||||
boolean deleteFile = false;
|
||||
synchronized (lock) {
|
||||
if (state == State.WRITING_UPLOADING) {
|
||||
this.file = file;
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
new SimpleApiCall(() -> apiCallUploadFile(file,
|
||||
sessionRecord)));
|
||||
} else {
|
||||
deleteFile = true;
|
||||
}
|
||||
}
|
||||
if (deleteFile) delete(file);
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void apiCallUploadFile(File file,
|
||||
OutgoingSessionRecord sessionRecord)
|
||||
throws IOException, ApiException {
|
||||
synchronized (lock) {
|
||||
if (state != State.WRITING_UPLOADING) return;
|
||||
}
|
||||
LOG.info("Uploading file");
|
||||
mailboxApi.addFile(mailboxProperties, folderId, file);
|
||||
markMessagesSentOrAcked(sessionRecord);
|
||||
synchronized (lock) {
|
||||
if (state != State.WRITING_UPLOADING) return;
|
||||
state = State.CHECKING_FOR_DATA;
|
||||
apiCall = null;
|
||||
this.file = null;
|
||||
}
|
||||
delete(file);
|
||||
checkForDataToSend();
|
||||
}
|
||||
|
||||
private void markMessagesSentOrAcked(OutgoingSessionRecord sessionRecord) {
|
||||
Collection<MessageId> acked = sessionRecord.getAckedIds();
|
||||
Collection<MessageId> sent = sessionRecord.getSentIds();
|
||||
try {
|
||||
db.transaction(false, txn -> {
|
||||
if (!acked.isEmpty()) {
|
||||
db.setAckSent(txn, contactId, acked);
|
||||
}
|
||||
if (!sent.isEmpty()) {
|
||||
db.setMessagesSent(txn, contactId, sent, MAX_LATENCY);
|
||||
}
|
||||
});
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessageToAckEvent) {
|
||||
MessageToAckEvent m = (MessageToAckEvent) e;
|
||||
if (m.getContactId().equals(contactId)) {
|
||||
LOG.info("Message to ack");
|
||||
onDataToSend();
|
||||
}
|
||||
} else if (e instanceof MessageSharedEvent) {
|
||||
LOG.info("Message shared");
|
||||
onDataToSend();
|
||||
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
||||
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
||||
if (g.getVisibility() == SHARED &&
|
||||
g.getAffectedContacts().contains(contactId)) {
|
||||
LOG.info("Group shared");
|
||||
onDataToSend();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void onDataToSend() {
|
||||
Cancellable wakeupTask;
|
||||
synchronized (lock) {
|
||||
if (state != State.WAITING_FOR_DATA) return;
|
||||
state = State.CHECKING_FOR_DATA;
|
||||
wakeupTask = this.wakeupTask;
|
||||
this.wakeupTask = null;
|
||||
// Delay the check to avoid creating lots of small files
|
||||
checkTask = taskScheduler.schedule(this::checkForDataToSend,
|
||||
ioExecutor, CHECK_DELAY_MS, MILLISECONDS);
|
||||
}
|
||||
// If we had scheduled a wakeup when data was due to be sent, cancel it
|
||||
if (wakeupTask != null) wakeupTask.cancel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/**
|
||||
* A worker that downloads files from a contact's mailbox.
|
||||
*/
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
interface MailboxWorker {
|
||||
|
||||
/**
|
||||
* Asynchronously starts the worker.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Destroys the worker and cancels any pending tasks or retries.
|
||||
*/
|
||||
void destroy();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
interface MailboxWorkerFactory {
|
||||
|
||||
MailboxWorker createUploadWorker(ConnectivityChecker connectivityChecker,
|
||||
MailboxProperties properties, MailboxFolderId folderId,
|
||||
ContactId contactId);
|
||||
|
||||
MailboxWorker createDownloadWorkerForContactMailbox(
|
||||
ConnectivityChecker connectivityChecker,
|
||||
TorReachabilityMonitor reachabilityMonitor,
|
||||
MailboxProperties properties);
|
||||
|
||||
MailboxWorker createDownloadWorkerForOwnMailbox(
|
||||
ConnectivityChecker connectivityChecker,
|
||||
TorReachabilityMonitor reachabilityMonitor,
|
||||
MailboxProperties properties);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class MailboxWorkerFactoryImpl implements MailboxWorkerFactory {
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final DatabaseComponent db;
|
||||
private final Clock clock;
|
||||
private final TaskScheduler taskScheduler;
|
||||
private final EventBus eventBus;
|
||||
private final MailboxApiCaller mailboxApiCaller;
|
||||
private final MailboxApi mailboxApi;
|
||||
private final MailboxFileManager mailboxFileManager;
|
||||
|
||||
@Inject
|
||||
MailboxWorkerFactoryImpl(@IoExecutor Executor ioExecutor,
|
||||
DatabaseComponent db,
|
||||
Clock clock,
|
||||
TaskScheduler taskScheduler,
|
||||
EventBus eventBus,
|
||||
MailboxApiCaller mailboxApiCaller,
|
||||
MailboxApi mailboxApi,
|
||||
MailboxFileManager mailboxFileManager) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.db = db;
|
||||
this.clock = clock;
|
||||
this.taskScheduler = taskScheduler;
|
||||
this.eventBus = eventBus;
|
||||
this.mailboxApiCaller = mailboxApiCaller;
|
||||
this.mailboxApi = mailboxApi;
|
||||
this.mailboxFileManager = mailboxFileManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailboxWorker createUploadWorker(
|
||||
ConnectivityChecker connectivityChecker,
|
||||
MailboxProperties properties, MailboxFolderId folderId,
|
||||
ContactId contactId) {
|
||||
MailboxUploadWorker worker = new MailboxUploadWorker(ioExecutor, db,
|
||||
clock, taskScheduler, eventBus, connectivityChecker,
|
||||
mailboxApiCaller, mailboxApi, mailboxFileManager,
|
||||
properties, folderId, contactId);
|
||||
eventBus.addListener(worker);
|
||||
return worker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailboxWorker createDownloadWorkerForContactMailbox(
|
||||
ConnectivityChecker connectivityChecker,
|
||||
TorReachabilityMonitor reachabilityMonitor,
|
||||
MailboxProperties properties) {
|
||||
return new ContactMailboxDownloadWorker(connectivityChecker,
|
||||
reachabilityMonitor, mailboxApiCaller, mailboxApi,
|
||||
mailboxFileManager, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailboxWorker createDownloadWorkerForOwnMailbox(
|
||||
ConnectivityChecker connectivityChecker,
|
||||
TorReachabilityMonitor reachabilityMonitor,
|
||||
MailboxProperties properties) {
|
||||
// TODO
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,13 @@ import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
@@ -55,11 +57,12 @@ class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
||||
private boolean checkConnectivityAndStoreResult(
|
||||
MailboxProperties properties) throws DbException {
|
||||
try {
|
||||
if (!mailboxApi.checkStatus(properties)) throw new ApiException();
|
||||
List<MailboxVersion> serverSupports =
|
||||
mailboxApi.getServerSupports(properties);
|
||||
LOG.info("Own mailbox is reachable");
|
||||
long now = clock.currentTimeMillis();
|
||||
db.transaction(false, txn -> mailboxSettingsManager
|
||||
.recordSuccessfulConnection(txn, now));
|
||||
.recordSuccessfulConnection(txn, now, serverSupports));
|
||||
// Call the observers and cache the result
|
||||
onConnectivityCheckSucceeded(now);
|
||||
return false; // Don't retry
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.Cancellable;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class OwnMailboxContactListWorker
|
||||
implements MailboxWorker, ConnectivityObserver, EventListener {
|
||||
|
||||
/**
|
||||
* When the worker is started it waits for a connectivity check, then
|
||||
* fetches the remote contact list and compares it to the local contact
|
||||
* list.
|
||||
* <p>
|
||||
* Any contacts that are missing from the remote list are added to the
|
||||
* mailbox's contact list, while any contacts that are missing from the
|
||||
* local list are removed from the mailbox's contact list.
|
||||
* <p>
|
||||
* Once the remote contact list has been brought up to date, the worker
|
||||
* waits for events indicating that contacts have been added or removed.
|
||||
* Each time an event is received, the worker updates the mailbox's
|
||||
* contact list and then goes back to waiting.
|
||||
*/
|
||||
private enum State {
|
||||
CREATED,
|
||||
CONNECTIVITY_CHECK,
|
||||
FETCHING_CONTACT_LIST,
|
||||
UPDATING_CONTACT_LIST,
|
||||
WAITING_FOR_CHANGES,
|
||||
DESTROYED
|
||||
}
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(OwnMailboxContactListWorker.class.getName());
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final DatabaseComponent db;
|
||||
private final EventBus eventBus;
|
||||
private final ConnectivityChecker connectivityChecker;
|
||||
private final MailboxApiCaller mailboxApiCaller;
|
||||
private final MailboxApi mailboxApi;
|
||||
private final MailboxUpdateManager mailboxUpdateManager;
|
||||
private final MailboxProperties mailboxProperties;
|
||||
private final Object lock = new Object();
|
||||
|
||||
@GuardedBy("lock")
|
||||
private State state = State.CREATED;
|
||||
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private Cancellable apiCall = null;
|
||||
|
||||
/**
|
||||
* A queue of updates waiting to be applied to the remote contact list.
|
||||
*/
|
||||
@GuardedBy("lock")
|
||||
private final Queue<Update> updates = new LinkedList<>();
|
||||
|
||||
OwnMailboxContactListWorker(@IoExecutor Executor ioExecutor,
|
||||
DatabaseComponent db,
|
||||
EventBus eventBus,
|
||||
ConnectivityChecker connectivityChecker,
|
||||
MailboxApiCaller mailboxApiCaller,
|
||||
MailboxApi mailboxApi,
|
||||
MailboxUpdateManager mailboxUpdateManager,
|
||||
MailboxProperties mailboxProperties) {
|
||||
if (!mailboxProperties.isOwner()) throw new IllegalArgumentException();
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.db = db;
|
||||
this.connectivityChecker = connectivityChecker;
|
||||
this.mailboxApiCaller = mailboxApiCaller;
|
||||
this.mailboxApi = mailboxApi;
|
||||
this.mailboxUpdateManager = mailboxUpdateManager;
|
||||
this.mailboxProperties = mailboxProperties;
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
LOG.info("Started");
|
||||
synchronized (lock) {
|
||||
if (state != State.CREATED) return;
|
||||
state = State.CONNECTIVITY_CHECK;
|
||||
}
|
||||
// Avoid leaking observer in case destroy() is called concurrently
|
||||
// before observer is added
|
||||
connectivityChecker.checkConnectivity(mailboxProperties, this);
|
||||
boolean destroyed;
|
||||
synchronized (lock) {
|
||||
destroyed = state == State.DESTROYED;
|
||||
}
|
||||
if (destroyed) connectivityChecker.removeObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
LOG.info("Destroyed");
|
||||
Cancellable apiCall;
|
||||
synchronized (lock) {
|
||||
state = State.DESTROYED;
|
||||
apiCall = this.apiCall;
|
||||
this.apiCall = null;
|
||||
}
|
||||
if (apiCall != null) apiCall.cancel();
|
||||
connectivityChecker.removeObserver(this);
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectivityCheckSucceeded() {
|
||||
LOG.info("Connectivity check succeeded");
|
||||
synchronized (lock) {
|
||||
if (state != State.CONNECTIVITY_CHECK) return;
|
||||
state = State.FETCHING_CONTACT_LIST;
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
new SimpleApiCall(this::apiCallFetchContactList));
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void apiCallFetchContactList() throws IOException, ApiException {
|
||||
synchronized (lock) {
|
||||
if (state != State.FETCHING_CONTACT_LIST) return;
|
||||
}
|
||||
LOG.info("Fetching remote contact list");
|
||||
Collection<ContactId> remote =
|
||||
mailboxApi.getContacts(mailboxProperties);
|
||||
ioExecutor.execute(() -> loadLocalContactList(remote));
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void loadLocalContactList(Collection<ContactId> remote) {
|
||||
synchronized (lock) {
|
||||
if (state != State.FETCHING_CONTACT_LIST) return;
|
||||
apiCall = null;
|
||||
}
|
||||
LOG.info("Loading local contact list");
|
||||
try {
|
||||
db.transaction(true, txn -> {
|
||||
Collection<Contact> local = db.getContacts(txn);
|
||||
// Handle the result on the event executor to avoid races with
|
||||
// incoming events
|
||||
txn.attach(() -> reconcileContactLists(local, remote));
|
||||
});
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void reconcileContactLists(Collection<Contact> local,
|
||||
Collection<ContactId> remote) {
|
||||
Set<ContactId> localIds = new HashSet<>();
|
||||
for (Contact c : local) localIds.add(c.getId());
|
||||
remote = new HashSet<>(remote);
|
||||
synchronized (lock) {
|
||||
if (state != State.FETCHING_CONTACT_LIST) return;
|
||||
for (ContactId c : localIds) {
|
||||
if (!remote.contains(c)) updates.add(new Update(true, c));
|
||||
}
|
||||
for (ContactId c : remote) {
|
||||
if (!localIds.contains(c)) updates.add(new Update(false, c));
|
||||
}
|
||||
if (updates.isEmpty()) {
|
||||
LOG.info("Contact list is up to date");
|
||||
state = State.WAITING_FOR_CHANGES;
|
||||
} else {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(updates.size() + " updates to apply");
|
||||
}
|
||||
state = State.UPDATING_CONTACT_LIST;
|
||||
ioExecutor.execute(this::updateContactList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void updateContactList() {
|
||||
Update update;
|
||||
synchronized (lock) {
|
||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||
update = updates.poll();
|
||||
if (update == null) {
|
||||
LOG.info("No more updates to process");
|
||||
state = State.WAITING_FOR_CHANGES;
|
||||
apiCall = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (update.add) loadMailboxProperties(update.contactId);
|
||||
else removeContact(update.contactId);
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void loadMailboxProperties(ContactId c) {
|
||||
synchronized (lock) {
|
||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||
}
|
||||
LOG.info("Loading mailbox properties for contact");
|
||||
try {
|
||||
MailboxUpdate mailboxUpdate = db.transactionWithResult(true, txn ->
|
||||
mailboxUpdateManager.getLocalUpdate(txn, c));
|
||||
if (mailboxUpdate instanceof MailboxUpdateWithMailbox) {
|
||||
addContact(c, (MailboxUpdateWithMailbox) mailboxUpdate);
|
||||
} else {
|
||||
// Our own mailbox was concurrently unpaired. This worker will
|
||||
// be destroyed soon, so we can stop here
|
||||
LOG.info("Own mailbox was unpaired");
|
||||
}
|
||||
} catch (NoSuchContactException e) {
|
||||
// Contact was removed concurrently. Move on to the next update.
|
||||
// Later we may process a removal update for this contact, which
|
||||
// was never added to the mailbox's contact list. The removal API
|
||||
// call should fail safely with a TolerableFailureException
|
||||
LOG.info("No such contact");
|
||||
updateContactList();
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void addContact(ContactId c, MailboxUpdateWithMailbox withMailbox) {
|
||||
MailboxProperties props = withMailbox.getMailboxProperties();
|
||||
MailboxContact contact = new MailboxContact(c, props.getAuthToken(),
|
||||
requireNonNull(props.getInboxId()),
|
||||
requireNonNull(props.getOutboxId()));
|
||||
synchronized (lock) {
|
||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
|
||||
apiCallAddContact(contact)));
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void apiCallAddContact(MailboxContact contact)
|
||||
throws IOException, ApiException, TolerableFailureException {
|
||||
synchronized (lock) {
|
||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||
}
|
||||
LOG.info("Adding contact to remote contact list");
|
||||
mailboxApi.addContact(mailboxProperties, contact);
|
||||
updateContactList();
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void removeContact(ContactId c) {
|
||||
synchronized (lock) {
|
||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
|
||||
apiCallRemoveContact(c)));
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void apiCallRemoveContact(ContactId c)
|
||||
throws IOException, ApiException {
|
||||
synchronized (lock) {
|
||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||
}
|
||||
LOG.info("Removing contact from remote contact list");
|
||||
try {
|
||||
mailboxApi.deleteContact(mailboxProperties, c);
|
||||
} catch (TolerableFailureException e) {
|
||||
// Catch this so we can continue to the next update
|
||||
logException(LOG, INFO, e);
|
||||
}
|
||||
updateContactList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof ContactAddedEvent) {
|
||||
LOG.info("Contact added");
|
||||
onContactAdded(((ContactAddedEvent) e).getContactId());
|
||||
} else if (e instanceof ContactRemovedEvent) {
|
||||
LOG.info("Contact removed");
|
||||
onContactRemoved(((ContactRemovedEvent) e).getContactId());
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void onContactAdded(ContactId c) {
|
||||
synchronized (lock) {
|
||||
if (state != State.UPDATING_CONTACT_LIST &&
|
||||
state != State.WAITING_FOR_CHANGES) {
|
||||
return;
|
||||
}
|
||||
updates.add(new Update(true, c));
|
||||
if (state == State.WAITING_FOR_CHANGES) {
|
||||
state = State.UPDATING_CONTACT_LIST;
|
||||
ioExecutor.execute(this::updateContactList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void onContactRemoved(ContactId c) {
|
||||
synchronized (lock) {
|
||||
if (state != State.UPDATING_CONTACT_LIST &&
|
||||
state != State.WAITING_FOR_CHANGES) {
|
||||
return;
|
||||
}
|
||||
updates.add(new Update(false, c));
|
||||
if (state == State.WAITING_FOR_CHANGES) {
|
||||
state = State.UPDATING_CONTACT_LIST;
|
||||
ioExecutor.execute(this::updateContactList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An update that should be applied to the remote contact list.
|
||||
*/
|
||||
private static class Update {
|
||||
|
||||
/**
|
||||
* True if the contact should be added, false if the contact should be
|
||||
* removed.
|
||||
*/
|
||||
private final boolean add;
|
||||
private final ContactId contactId;
|
||||
|
||||
private Update(boolean add, ContactId contactId) {
|
||||
this.add = add;
|
||||
this.contactId = contactId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,17 +16,20 @@ import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
* Convenience class for making simple API calls that don't return values.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public abstract class SimpleApiCall implements ApiCall {
|
||||
class SimpleApiCall implements ApiCall {
|
||||
|
||||
private static final Logger LOG = getLogger(SimpleApiCall.class.getName());
|
||||
|
||||
abstract void tryToCallApi()
|
||||
throws IOException, ApiException, TolerableFailureException;
|
||||
private final Attempt attempt;
|
||||
|
||||
SimpleApiCall(Attempt attempt) {
|
||||
this.attempt = attempt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean callApi() {
|
||||
try {
|
||||
tryToCallApi();
|
||||
attempt.tryToCallApi();
|
||||
return false; // Succeeded, don't retry
|
||||
} catch (IOException | ApiException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
@@ -36,4 +39,17 @@ public abstract class SimpleApiCall implements ApiCall {
|
||||
return false; // Failed tolerably, don't retry
|
||||
}
|
||||
}
|
||||
|
||||
interface Attempt {
|
||||
|
||||
/**
|
||||
* Makes a single attempt to call an API endpoint. If this method
|
||||
* throws an {@link IOException} or an {@link ApiException}, the call
|
||||
* will be retried. If it throws a {@link TolerableFailureException}
|
||||
* or returns without throwing an exception, the call will not be
|
||||
* retried.
|
||||
*/
|
||||
void tryToCallApi()
|
||||
throws IOException, ApiException, TolerableFailureException;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Plugin;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
interface TorReachabilityMonitor {
|
||||
|
||||
/**
|
||||
* How long the Tor plugin needs to be continuously
|
||||
* {@link Plugin.State#ACTIVE active} before we assume our contacts can
|
||||
* reach our hidden service.
|
||||
*/
|
||||
long REACHABILITY_PERIOD_MS = MINUTES.toMillis(10);
|
||||
|
||||
/**
|
||||
* Starts the monitor.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Destroys the monitor.
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* Adds an observer that will be called when our Tor hidden service becomes
|
||||
* reachable. If our hidden service is already reachable, the observer is
|
||||
* called immediately.
|
||||
* <p>
|
||||
* Observers are removed after being called, or when the monitor is
|
||||
* {@link #destroy() destroyed}.
|
||||
*/
|
||||
void addOneShotObserver(TorReachabilityObserver o);
|
||||
|
||||
/**
|
||||
* Removes an observer that was added via
|
||||
* {@link #addOneShotObserver(TorReachabilityObserver)}.
|
||||
*/
|
||||
void removeObserver(TorReachabilityObserver o);
|
||||
|
||||
interface TorReachabilityObserver {
|
||||
|
||||
void onTorReachable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.Cancellable;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Plugin;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class TorReachabilityMonitorImpl
|
||||
implements TorReachabilityMonitor, EventListener {
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final TaskScheduler taskScheduler;
|
||||
private final PluginManager pluginManager;
|
||||
private final EventBus eventBus;
|
||||
private final Object lock = new Object();
|
||||
|
||||
@GuardedBy("lock")
|
||||
private boolean reachable = false, destroyed = false;
|
||||
|
||||
@GuardedBy("lock")
|
||||
private final List<TorReachabilityObserver> observers = new ArrayList<>();
|
||||
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private Cancellable task = null;
|
||||
|
||||
@Inject
|
||||
TorReachabilityMonitorImpl(
|
||||
@IoExecutor Executor ioExecutor,
|
||||
TaskScheduler taskScheduler,
|
||||
PluginManager pluginManager,
|
||||
EventBus eventBus) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.taskScheduler = taskScheduler;
|
||||
this.pluginManager = pluginManager;
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
eventBus.addListener(this);
|
||||
Plugin plugin = pluginManager.getPlugin(ID);
|
||||
if (plugin != null && plugin.getState() == ACTIVE) onTorActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
eventBus.removeListener(this);
|
||||
synchronized (lock) {
|
||||
destroyed = true;
|
||||
if (task != null) task.cancel();
|
||||
task = null;
|
||||
observers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOneShotObserver(TorReachabilityObserver o) {
|
||||
boolean callNow = false;
|
||||
synchronized (lock) {
|
||||
if (destroyed) return;
|
||||
if (reachable) callNow = true;
|
||||
else observers.add(o);
|
||||
}
|
||||
if (callNow) o.onTorReachable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeObserver(TorReachabilityObserver o) {
|
||||
synchronized (lock) {
|
||||
if (destroyed) return;
|
||||
observers.remove(o);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof TransportActiveEvent) {
|
||||
TransportActiveEvent t = (TransportActiveEvent) e;
|
||||
if (t.getTransportId().equals(ID)) onTorActive();
|
||||
} else if (e instanceof TransportInactiveEvent) {
|
||||
TransportInactiveEvent t = (TransportInactiveEvent) e;
|
||||
if (t.getTransportId().equals(ID)) onTorInactive();
|
||||
}
|
||||
}
|
||||
|
||||
private void onTorActive() {
|
||||
synchronized (lock) {
|
||||
if (destroyed || task != null) return;
|
||||
task = taskScheduler.schedule(this::onTorReachable, ioExecutor,
|
||||
REACHABILITY_PERIOD_MS, MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private void onTorInactive() {
|
||||
synchronized (lock) {
|
||||
reachable = false;
|
||||
if (task != null) task.cancel();
|
||||
task = null;
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void onTorReachable() {
|
||||
List<TorReachabilityObserver> observers;
|
||||
synchronized (lock) {
|
||||
if (destroyed) return;
|
||||
reachable = true;
|
||||
observers = new ArrayList<>(this.observers);
|
||||
this.observers.clear();
|
||||
task = null;
|
||||
}
|
||||
for (TorReachabilityObserver o : observers) o.onTorReachable();
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,7 @@ abstract class AbstractRemovableDrivePlugin implements SimplexPlugin {
|
||||
public void start() {
|
||||
callback.mergeLocalProperties(
|
||||
new TransportProperties(singletonMap(PROP_SUPPORTED, "true")));
|
||||
callback.pluginStateChanged(ACTIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
@@ -29,11 +30,6 @@ abstract class FilePlugin implements SimplexPlugin {
|
||||
protected final PluginCallback callback;
|
||||
protected final long maxLatency;
|
||||
|
||||
protected abstract void writerFinished(File f, boolean exception);
|
||||
|
||||
protected abstract void readerFinished(File f, boolean exception,
|
||||
boolean recognised);
|
||||
|
||||
FilePlugin(PluginCallback callback, long maxLatency) {
|
||||
this.callback = callback;
|
||||
this.maxLatency = maxLatency;
|
||||
@@ -50,9 +46,8 @@ abstract class FilePlugin implements SimplexPlugin {
|
||||
String path = p.get(PROP_PATH);
|
||||
if (isNullOrEmpty(path)) return null;
|
||||
try {
|
||||
File file = new File(path);
|
||||
FileInputStream in = new FileInputStream(file);
|
||||
return new FileTransportReader(file, in, this);
|
||||
FileInputStream in = new FileInputStream(path);
|
||||
return new TransportInputStreamReader(in);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return null;
|
||||
@@ -70,8 +65,8 @@ abstract class FilePlugin implements SimplexPlugin {
|
||||
LOG.info("Failed to create file");
|
||||
return null;
|
||||
}
|
||||
FileOutputStream out = new FileOutputStream(file);
|
||||
return new FileTransportWriter(file, out, this);
|
||||
OutputStream out = new FileOutputStream(file);
|
||||
return new TransportOutputStreamWriter(this, out);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return null;
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||
|
||||
@NotNullByDefault
|
||||
class FileTransportReader implements TransportConnectionReader {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(FileTransportReader.class.getName());
|
||||
|
||||
private final File file;
|
||||
private final InputStream in;
|
||||
private final FilePlugin plugin;
|
||||
|
||||
FileTransportReader(File file, InputStream in, FilePlugin plugin) {
|
||||
this.file = file;
|
||||
this.in = in;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return in;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(boolean exception, boolean recognised) {
|
||||
tryToClose(in, LOG, WARNING);
|
||||
plugin.readerFinished(file, exception, recognised);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.OutputStream;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||
|
||||
@NotNullByDefault
|
||||
class FileTransportWriter implements TransportConnectionWriter {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(FileTransportWriter.class.getName());
|
||||
|
||||
private final File file;
|
||||
private final OutputStream out;
|
||||
private final FilePlugin plugin;
|
||||
|
||||
FileTransportWriter(File file, OutputStream out, FilePlugin plugin) {
|
||||
this.file = file;
|
||||
this.out = out;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxLatency() {
|
||||
return plugin.getMaxLatency();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxIdleTime() {
|
||||
return plugin.getMaxIdleTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLossyAndCheap() {
|
||||
return plugin.isLossyAndCheap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() {
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(boolean exception) {
|
||||
tryToClose(out, LOG, WARNING);
|
||||
plugin.writerFinished(file, exception);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.Pair;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.ConnectionHandler;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.PluginException;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
|
||||
@NotNullByDefault
|
||||
class MailboxPlugin extends FilePlugin {
|
||||
|
||||
MailboxPlugin(PluginCallback callback, long maxLatency) {
|
||||
super(callback, maxLatency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxIdleTime() {
|
||||
// Unused for simplex transports
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws PluginException {
|
||||
callback.pluginStateChanged(ACTIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws PluginException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public State getState() {
|
||||
return ACTIVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getReasonsDisabled() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPoll() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPollingInterval() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll(
|
||||
Collection<Pair<TransportProperties, ConnectionHandler>> properties) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLossyAndCheap() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_LATENCY;
|
||||
|
||||
@NotNullByDefault
|
||||
public class MailboxPluginFactory implements SimplexPluginFactory {
|
||||
|
||||
@Inject
|
||||
MailboxPluginFactory() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxLatency() {
|
||||
return MAX_LATENCY;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public SimplexPlugin createPlugin(PluginCallback callback) {
|
||||
return new MailboxPlugin(callback, MAX_LATENCY);
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,8 @@ class RemovableDriveManagerImpl
|
||||
@Override
|
||||
public boolean isWriterTaskNeeded(ContactId c) throws DbException {
|
||||
return db.transactionWithResult(true, txn ->
|
||||
db.containsAnythingToSend(txn, c, MAX_LATENCY, true));
|
||||
db.containsAcksToSend(txn, c) ||
|
||||
db.containsMessagesToSend(txn, c, MAX_LATENCY, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,7 +27,7 @@ public interface CircumventionProvider {
|
||||
* Countries where bridge connections are likely to work.
|
||||
* Should be a subset of {@link #BLOCKED} and the union of
|
||||
* {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and
|
||||
* {@link #MEEK_BRIDGES}.
|
||||
* {@link #DPI_BRIDGES}.
|
||||
*/
|
||||
String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
|
||||
|
||||
@@ -44,10 +44,10 @@ public interface CircumventionProvider {
|
||||
String[] NON_DEFAULT_BRIDGES = {"BY", "RU", "TM"};
|
||||
|
||||
/**
|
||||
* Countries where obfs4 and vanilla bridges won't work and meek is needed.
|
||||
* Should be a subset of {@link #BRIDGES}.
|
||||
* Countries where vanilla bridges are blocked via DPI but non-default
|
||||
* obfs4 bridges and meek may work. Should be a subset of {@link #BRIDGES}.
|
||||
*/
|
||||
String[] MEEK_BRIDGES = {"CN", "IR"};
|
||||
String[] DPI_BRIDGES = {"CN", "IR"};
|
||||
|
||||
/**
|
||||
* Returns true if vanilla Tor connections are blocked in the given country.
|
||||
|
||||
@@ -14,7 +14,6 @@ import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
|
||||
@@ -35,8 +34,8 @@ class CircumventionProviderImpl implements CircumventionProvider {
|
||||
new HashSet<>(asList(DEFAULT_BRIDGES));
|
||||
private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES =
|
||||
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
|
||||
private static final Set<String> MEEK_COUNTRIES =
|
||||
new HashSet<>(asList(MEEK_BRIDGES));
|
||||
private static final Set<String> DPI_COUNTRIES =
|
||||
new HashSet<>(asList(DPI_BRIDGES));
|
||||
|
||||
@Inject
|
||||
CircumventionProviderImpl() {
|
||||
@@ -58,8 +57,8 @@ class CircumventionProviderImpl implements CircumventionProvider {
|
||||
return asList(DEFAULT_OBFS4, VANILLA);
|
||||
} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
|
||||
return asList(NON_DEFAULT_OBFS4, VANILLA);
|
||||
} else if (MEEK_COUNTRIES.contains(countryCode)) {
|
||||
return singletonList(MEEK);
|
||||
} else if (DPI_COUNTRIES.contains(countryCode)) {
|
||||
return asList(NON_DEFAULT_OBFS4, MEEK);
|
||||
} else {
|
||||
return asList(DEFAULT_OBFS4, VANILLA);
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
@ParametersNotNullByDefault
|
||||
abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
private static final Logger LOG = getLogger(TorPlugin.class.getName());
|
||||
protected static final Logger LOG = getLogger(TorPlugin.class.getName());
|
||||
|
||||
private static final String[] EVENTS = {
|
||||
"CIRC",
|
||||
@@ -124,7 +124,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
|
||||
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
|
||||
|
||||
private final Executor ioExecutor, wakefulIoExecutor;
|
||||
protected final Executor ioExecutor;
|
||||
private final Executor wakefulIoExecutor;
|
||||
private final Executor connectionStatusExecutor;
|
||||
private final NetworkManager networkManager;
|
||||
private final LocationUtils locationUtils;
|
||||
@@ -254,34 +255,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
Map<String, String> env = pb.environment();
|
||||
env.put("HOME", torDirectory.getAbsolutePath());
|
||||
pb.directory(torDirectory);
|
||||
pb.redirectErrorStream(true);
|
||||
try {
|
||||
torProcess = pb.start();
|
||||
} catch (SecurityException | IOException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
// Log the process's standard output until it detaches
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||
Scanner stderr = new Scanner(torProcess.getErrorStream());
|
||||
while (stdout.hasNextLine() || stderr.hasNextLine()) {
|
||||
if (stdout.hasNextLine()) {
|
||||
LOG.info(stdout.nextLine());
|
||||
}
|
||||
if (stderr.hasNextLine()) {
|
||||
LOG.info(stderr.nextLine());
|
||||
}
|
||||
}
|
||||
stdout.close();
|
||||
stderr.close();
|
||||
}
|
||||
try {
|
||||
// Wait for the process to detach or exit
|
||||
int exit = torProcess.waitFor();
|
||||
if (exit != 0) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.warning("Tor exited with value " + exit);
|
||||
throw new PluginException();
|
||||
}
|
||||
// Wait for the Tor process to start
|
||||
waitForTorToStart(torProcess);
|
||||
// Wait for the auth cookie file to be created/updated
|
||||
long start = clock.currentTimeMillis();
|
||||
while (cookieFile.length() < 32) {
|
||||
@@ -397,7 +379,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
return zin;
|
||||
}
|
||||
|
||||
private static void append(StringBuilder strb, String name, int value) {
|
||||
private static void append(StringBuilder strb, String name, Object value) {
|
||||
strb.append(name);
|
||||
strb.append(" ");
|
||||
strb.append(value);
|
||||
@@ -405,13 +387,17 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
|
||||
private InputStream getConfigInputStream() {
|
||||
File dataDirectory = new File(torDirectory, ".tor");
|
||||
StringBuilder strb = new StringBuilder();
|
||||
append(strb, "ControlPort", torControlPort);
|
||||
append(strb, "CookieAuthentication", 1);
|
||||
append(strb, "DataDirectory", dataDirectory.getAbsolutePath());
|
||||
append(strb, "DisableNetwork", 1);
|
||||
append(strb, "RunAsDaemon", 1);
|
||||
append(strb, "SafeSocks", 1);
|
||||
append(strb, "SocksPort", torSocksPort);
|
||||
strb.append("GeoIPFile\n");
|
||||
strb.append("GeoIPv6File\n");
|
||||
//noinspection CharsetObjectCanBeUsed
|
||||
return new ByteArrayInputStream(
|
||||
strb.toString().getBytes(Charset.forName("UTF-8")));
|
||||
@@ -442,6 +428,23 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
protected void waitForTorToStart(Process torProcess)
|
||||
throws InterruptedException, PluginException {
|
||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||
// Log the first line of stdout (contains Tor and library versions)
|
||||
if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
|
||||
// Read the process's stdout (and redirected stderr) until it detaches
|
||||
while (stdout.hasNextLine()) stdout.nextLine();
|
||||
stdout.close();
|
||||
// Wait for the process to detach or exit
|
||||
int exit = torProcess.waitFor();
|
||||
if (exit != 0) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.warning("Tor exited with value " + exit);
|
||||
throw new PluginException();
|
||||
}
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
ioExecutor.execute(() -> {
|
||||
// If there's already a port number stored in config, reuse it
|
||||
|
||||
@@ -50,6 +50,7 @@ import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS;
|
||||
@@ -235,8 +236,10 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
generateOffer();
|
||||
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
||||
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
||||
if (g.getAffectedContacts().contains(contactId))
|
||||
if (g.getVisibility() == SHARED &&
|
||||
g.getAffectedContacts().contains(contactId)) {
|
||||
generateOffer();
|
||||
}
|
||||
} else if (e instanceof MessageRequestedEvent) {
|
||||
if (((MessageRequestedEvent) e).getContactId().equals(contactId))
|
||||
generateBatch();
|
||||
@@ -310,7 +313,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
Collection<Message> batch =
|
||||
db.generateRequestedBatch(txn, contactId,
|
||||
BATCH_CAPACITY, maxLatency);
|
||||
setNextSendTime(db.getNextSendTime(txn, contactId));
|
||||
setNextSendTime(db.getNextSendTime(txn, contactId,
|
||||
maxLatency));
|
||||
return batch;
|
||||
});
|
||||
if (LOG.isLoggable(INFO))
|
||||
@@ -353,7 +357,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
Offer o = db.transactionWithNullableResult(false, txn -> {
|
||||
Offer offer = db.generateOffer(txn, contactId,
|
||||
MAX_MESSAGE_IDS, maxLatency);
|
||||
setNextSendTime(db.getNextSendTime(txn, contactId));
|
||||
setNextSendTime(db.getNextSendTime(txn, contactId,
|
||||
maxLatency));
|
||||
return offer;
|
||||
});
|
||||
if (LOG.isLoggable(INFO))
|
||||
|
||||
@@ -7,9 +7,9 @@ import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.DeferredSendHandler;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||
|
||||
@@ -29,7 +29,7 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LEN
|
||||
|
||||
/**
|
||||
* A {@link SimplexOutgoingSession} for sending and acking messages via a
|
||||
* mailbox. The session uses a {@link DeferredSendHandler} to record the IDs
|
||||
* mailbox. The session uses a {@link OutgoingSessionRecord} to record the IDs
|
||||
* of the messages sent and acked during the session so that they can be
|
||||
* recorded in the DB as sent or acked after the file has been successfully
|
||||
* uploaded to the mailbox.
|
||||
@@ -41,7 +41,7 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
|
||||
private static final Logger LOG =
|
||||
getLogger(MailboxOutgoingSession.class.getName());
|
||||
|
||||
private final DeferredSendHandler deferredSendHandler;
|
||||
private final OutgoingSessionRecord sessionRecord;
|
||||
private final long initialCapacity;
|
||||
|
||||
MailboxOutgoingSession(DatabaseComponent db,
|
||||
@@ -51,11 +51,11 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
|
||||
long maxLatency,
|
||||
StreamWriter streamWriter,
|
||||
SyncRecordWriter recordWriter,
|
||||
DeferredSendHandler deferredSendHandler,
|
||||
OutgoingSessionRecord sessionRecord,
|
||||
long capacity) {
|
||||
super(db, eventBus, contactId, transportId, maxLatency, streamWriter,
|
||||
recordWriter);
|
||||
this.deferredSendHandler = deferredSendHandler;
|
||||
this.sessionRecord = sessionRecord;
|
||||
this.initialCapacity = capacity;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
|
||||
Collection<MessageId> idsToAck = loadMessageIdsToAck();
|
||||
if (idsToAck.isEmpty()) break;
|
||||
recordWriter.writeAck(new Ack(idsToAck));
|
||||
deferredSendHandler.onAckSent(idsToAck);
|
||||
sessionRecord.onAckSent(idsToAck);
|
||||
LOG.info("Sent ack");
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
|
||||
db.getMessageToSend(txn, contactId, m, maxLatency, false));
|
||||
if (message == null) continue; // No longer shared
|
||||
recordWriter.writeMessage(message);
|
||||
deferredSendHandler.onMessageSent(m);
|
||||
sessionRecord.onMessageSent(m);
|
||||
LOG.info("Sent message");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||
import org.briarproject.bramble.api.sync.Priority;
|
||||
import org.briarproject.bramble.api.sync.PriorityHandler;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
||||
@@ -25,6 +26,8 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_FILE_PAYLOAD_BYTES;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class SyncSessionFactoryImpl implements SyncSessionFactory {
|
||||
@@ -73,6 +76,18 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||
long maxLatency, StreamWriter streamWriter,
|
||||
OutgoingSessionRecord sessionRecord) {
|
||||
OutputStream out = streamWriter.getOutputStream();
|
||||
SyncRecordWriter recordWriter =
|
||||
recordWriterFactory.createRecordWriter(out);
|
||||
return new MailboxOutgoingSession(db, eventBus, c, t, maxLatency,
|
||||
streamWriter, recordWriter, sessionRecord,
|
||||
MAX_FILE_PAYLOAD_BYTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
||||
long maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
||||
|
||||
@@ -15,16 +15,19 @@ n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cer
|
||||
n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
|
||||
n Bridge obfs4 85.242.211.221:8042 A36A938DD7FDB8BACC846BA326EE0BA0D89A9252 cert=1AN6Pt1eFca3Y/WYD2TGAU3Al9cO4eouXE9SX63s66Z/ks3tVmgQ5GeXi1B5DOvx6Il7Zw iat-mode=0
|
||||
n Bridge obfs4 74.104.165.202:9002 EF432018A6AA5D970B2F84E39CD30A147030141C cert=PhppfUusY85dHGvWtGTybZ1fED4DtbHmALkNMIOIYrAz1B4xN7/2a5gyiZe1epju1BOHVg iat-mode=0
|
||||
n Bridge obfs4 70.34.242.31:443 7F026956402CDFF4BCBA8E11EE9C50E3FE0A2B72 cert=hP/KU7JATSfWH3HwS5Er/YLT0J+bRO3+s2fWx2yirrgf37EyrWvm/BQshoNje8WfUm6CBw iat-mode=0
|
||||
n Bridge obfs4 93.95.226.151:41185 460B0CFFC0CF1D965F3DE064E08BA1915E7C916A cert=inluPzp5Jp5OzZar1eQb4dcQ/YlAj/v0kHAUCoCr3rmLt03+pVuVTjoH4mRy4+acXpn+Gw iat-mode=0
|
||||
n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0
|
||||
n Bridge obfs4 83.97.179.29:1199 D83068BFAA28E71DB024B786E1E803BE14257127 cert=IduGtt05tM59Xmvo0oVNWgIRgY4OGPJjFP+y2oa6RMDHQBL/GRyFOOgX70iiazNAIJNkPw iat-mode=0
|
||||
n Bridge obfs4 207.181.244.13:40132 37FE8D782F5DD2BAEEDAAB8257B701344676B6DD cert=f5Hbfn3ToMzH170cV8DfLly3vRynveidfOfDcbradIDtbLDX15V2yQ8eEH2CPKQJmQR2Hg iat-mode=0
|
||||
n Bridge obfs4 76.255.201.112:8888 96CF36C2ECCFB7376AB6BE905BECD2C2AE8AEFCD cert=+q0pjaiM0JMqHL/BKqCRD+pjflaw/S406eUDF7CnFgamvQW3l2HVLJhQ6uX9P8zff0PLGg iat-mode=0
|
||||
n Bridge obfs4 65.108.159.114:14174 E1AD374BA9F34BD98862D128AC54D40C7DC138AE cert=YMkxMSBN2OOd99AkJpFaEUyKkdqZgJt9oVJEgO1QnT37n/Vc2yR4nhx4k4VkPLfEP1f4eg iat-mode=0
|
||||
n Bridge obfs4 185.177.207.138:8443 53716FE26F23C8C6A71A2BC5D9D8DC64747278C7 cert=6jcYVilMEzxdsWghSrQFpYYJlkkir/GPIXw/EnddUK3S8ToVpMG8u1SwMOEdEs735RrMHw iat-mode=0
|
||||
n Bridge obfs4 176.123.2.253:1933 B855D141CE6C4DE0F7EA4AAED83EBA8373FA8191 cert=1rOoSaRagc6PPR//paIl+ukv1N+xWKCdBXMFxK0k/moEwH0lk5bURBrUDzIX35fVzaiicQ iat-mode=0
|
||||
n Bridge obfs4 5.252.176.61:9418 3E61130100AD827AB9CB33DAC052D9BC49A39509 cert=/aMyBPnKbQFISithD3i1KHUdpWeMpWG3SvUpq1YWCf2EQohFxQfw+646gW1knm4BI/DLRA iat-mode=0
|
||||
n Bridge obfs4 202.61.224.111:6902 A4F91299763DB925AE3BD29A0FC1A9821E5D9BAE cert=NBKm2MJ83wMvYShkqpD5RrbDtW5YpIZrFNnMw7Dj1XOM3plU60Bh4eziaQXe8fGtb8ZqKg iat-mode=0
|
||||
n Bridge obfs4 87.121.72.109:9002 C8081D4731C953FA4AE166946E72B29153351E34 cert=bikAqxKV6Ch5gFCBTdPI28VeShYa1ZgkLmvc7YZNLWFsFZoaCULL/3AQKjpIfvSiJs5jGQ iat-mode=0
|
||||
n Bridge obfs4 172.104.17.96:17900 B6B37AC96E163D0A5ECE55826D17B50B70F0A7F8 cert=gUz7svhPxoALgeN4lMYrXK7NBnaDqwu6SKRJOhaO9IIMBpnB8UhMCMKzzMho3b0RxWzBVg iat-mode=0
|
||||
n Bridge obfs4 70.34.249.113:443 F441B16ABB1055794C2CE01821FC05047B2C8CFC cert=MauLNoyq8EwjY4Qe0oASYzs2hXdSjNgy+BtP9oo1naHhRsyKTtAZzeNv08RnzWjMJrTwcg iat-mode=0
|
||||
v Bridge 135.181.113.164:54444 74AF4CCA614C454B7D3E81FF8BACD78CEBC7D7DE
|
||||
v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
|
||||
v Bridge 77.96.91.103:443 ED000A75B79A58F1D83A4D1675C2A9395B71BE8E
|
||||
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
|
||||
v Bridge 213.108.108.145:17674 A39C0FE47963B6E8CFE9815549864DE544935A31
|
||||
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
|
||||
|
||||
@@ -17,7 +17,6 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.transport.KeyManager;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -63,13 +62,9 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
private final long timestamp = System.currentTimeMillis();
|
||||
private final boolean alice = new Random().nextBoolean();
|
||||
|
||||
private ContactManagerImpl contactManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
contactManager = new ContactManagerImpl(db, keyManager,
|
||||
identityManager, pendingContactFactory);
|
||||
}
|
||||
private final ContactManagerImpl contactManager =
|
||||
new ContactManagerImpl(db, keyManager, identityManager,
|
||||
pendingContactFactory);
|
||||
|
||||
@Test
|
||||
public void testAddContact() throws Exception {
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.briarproject.bramble.data;
|
||||
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -17,14 +16,8 @@ import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
public class BdfWriterImplTest extends BrambleTestCase {
|
||||
|
||||
private ByteArrayOutputStream out = null;
|
||||
private BdfWriterImpl w = null;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
out = new ByteArrayOutputStream();
|
||||
w = new BdfWriterImpl(out);
|
||||
}
|
||||
private final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
private final BdfWriterImpl w = new BdfWriterImpl(out);
|
||||
|
||||
@Test
|
||||
public void testWriteNull() throws IOException {
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.db;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
@@ -10,10 +11,18 @@ import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import static org.briarproject.bramble.test.TestUtils.isCryptoStrengthUnlimited;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
public class BasicHyperSqlTest extends BasicDatabaseTest {
|
||||
|
||||
private final SecretKey key = TestUtils.getSecretKey();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
assumeTrue(isCryptoStrengthUnlimited());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBinaryType() {
|
||||
return "BINARY(32)";
|
||||
|
||||
@@ -303,11 +303,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the contact is in the DB (which it's not)
|
||||
exactly(25).of(database).startTransaction();
|
||||
exactly(27).of(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
exactly(25).of(database).containsContact(txn, contactId);
|
||||
exactly(27).of(database).containsContact(txn, contactId);
|
||||
will(returnValue(false));
|
||||
exactly(25).of(database).abortTransaction(txn);
|
||||
exactly(27).of(database).abortTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
eventExecutor, shutdownManager);
|
||||
@@ -321,6 +321,23 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(true, transaction ->
|
||||
db.containsAcksToSend(transaction, contactId));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(true, transaction ->
|
||||
db.containsMessagesToSend(transaction, contactId,
|
||||
123, true));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.generateAck(transaction, contactId, 123));
|
||||
|
||||
@@ -3,9 +3,18 @@ package org.briarproject.bramble.db;
|
||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.junit.Before;
|
||||
|
||||
import static org.briarproject.bramble.test.TestUtils.isCryptoStrengthUnlimited;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
public class HyperSqlDatabaseTest extends JdbcDatabaseTest {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
assumeTrue(isCryptoStrengthUnlimited());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JdbcDatabase createDatabase(DatabaseConfig config,
|
||||
MessageFactory messageFactory, Clock clock) {
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
|
||||
import static org.briarproject.bramble.test.TestUtils.isCryptoStrengthUnlimited;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
@NotNullByDefault
|
||||
public class HyperSqlMigrationTest extends DatabaseMigrationTest {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
assumeTrue(isCryptoStrengthUnlimited());
|
||||
}
|
||||
|
||||
@Override
|
||||
Database<Connection> createDatabase(
|
||||
List<Migration<Connection>> migrations) {
|
||||
|
||||
@@ -378,9 +378,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Initially there should be nothing to send
|
||||
assertFalse(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false));
|
||||
assertFalse(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true));
|
||||
|
||||
// Add some messages to ack
|
||||
Message message1 = getMessage(groupId);
|
||||
@@ -389,10 +389,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.addMessage(txn, message1, DELIVERED, true, false, contactId);
|
||||
|
||||
// Both message IDs should be returned
|
||||
assertTrue(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||
assertTrue(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||
assertTrue(db.containsAcksToSend(txn, contactId));
|
||||
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||
assertEquals(asList(messageId, messageId1), ids);
|
||||
|
||||
@@ -400,10 +397,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
|
||||
|
||||
// No message IDs should be returned
|
||||
assertFalse(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||
assertFalse(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||
assertFalse(db.containsAcksToSend(txn, contactId));
|
||||
assertEquals(emptyList(), db.getMessagesToAck(txn, contactId, 1234));
|
||||
|
||||
// Raise the ack flag again
|
||||
@@ -411,10 +405,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.raiseAckFlag(txn, contactId, messageId1);
|
||||
|
||||
// Both message IDs should be returned
|
||||
assertTrue(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||
assertTrue(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||
assertTrue(db.containsAcksToSend(txn, contactId));
|
||||
ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||
assertEquals(asList(messageId, messageId1), ids);
|
||||
|
||||
@@ -2029,37 +2020,51 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.addMessage(txn, message, UNKNOWN, false, false, null);
|
||||
|
||||
// There should be no messages to send
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
assertEquals(Long.MAX_VALUE,
|
||||
db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Share the group with the contact - still no messages to send
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
assertEquals(Long.MAX_VALUE,
|
||||
db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Set the message's state to DELIVERED - still no messages to send
|
||||
db.setMessageState(txn, messageId, DELIVERED);
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
assertEquals(Long.MAX_VALUE,
|
||||
db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Share the message - now it should be sendable immediately
|
||||
db.setMessageShared(txn, messageId, true);
|
||||
assertEquals(0, db.getNextSendTime(txn, contactId));
|
||||
assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Mark the message as requested - it should still be sendable
|
||||
db.raiseRequestedFlag(txn, contactId, messageId);
|
||||
assertEquals(0, db.getNextSendTime(txn, contactId));
|
||||
assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Update the message's expiry time as though we sent it - now the
|
||||
// message should be sendable after one round-trip
|
||||
db.updateRetransmissionData(txn, contactId, messageId, 1000);
|
||||
assertEquals(now + 2000, db.getNextSendTime(txn, contactId));
|
||||
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||
assertEquals(now + MAX_LATENCY * 2,
|
||||
db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// The message should be sendable immediately over a transport with
|
||||
// lower latency
|
||||
assertEquals(0L, db.getNextSendTime(txn, contactId, MAX_LATENCY - 1));
|
||||
|
||||
// Update the message's expiry time again - now it should be sendable
|
||||
// after two round-trips
|
||||
db.updateRetransmissionData(txn, contactId, messageId, 1000);
|
||||
assertEquals(now + 4000, db.getNextSendTime(txn, contactId));
|
||||
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||
assertEquals(now + MAX_LATENCY * 4,
|
||||
db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// The message should be sendable immediately over a transport with
|
||||
// lower latency
|
||||
assertEquals(0L, db.getNextSendTime(txn, contactId, MAX_LATENCY - 1));
|
||||
|
||||
// Delete the message - there should be no messages to send
|
||||
db.deleteMessage(txn, messageId);
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
assertEquals(Long.MAX_VALUE,
|
||||
db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -2124,7 +2129,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||
|
||||
// The message should expire after 2 * MAX_LATENCY
|
||||
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
|
||||
assertEquals(now + MAX_LATENCY * 2,
|
||||
db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Time: now + MAX_LATENCY * 2 - 1
|
||||
// The message should not yet be sendable
|
||||
@@ -2167,7 +2173,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||
|
||||
// The message should expire after 2 * MAX_LATENCY
|
||||
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
|
||||
assertEquals(now + MAX_LATENCY * 2,
|
||||
db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// The message should not be sendable via the same transport
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
@@ -2214,7 +2221,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||
|
||||
// The message should expire after 2 * MAX_LATENCY
|
||||
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
|
||||
assertEquals(now + MAX_LATENCY * 2,
|
||||
db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Time: now + MAX_LATENCY * 2 - 1
|
||||
// The message should not yet be sendable
|
||||
@@ -2225,8 +2233,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
// Reset the retransmission times
|
||||
db.resetUnackedMessagesToSend(txn, contactId);
|
||||
|
||||
// The message should have infinitely short expiry
|
||||
assertEquals(0, db.getNextSendTime(txn, contactId));
|
||||
// The message should be sendable immediately
|
||||
assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// The message should be sendable
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
@@ -2579,7 +2587,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
private void assertNothingToSendLazily(Database<Connection> db,
|
||||
Connection txn) throws Exception {
|
||||
assertFalse(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false));
|
||||
Collection<MessageId> ids =
|
||||
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
@@ -2590,7 +2598,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
private void assertOneMessageToSendLazily(Database<Connection> db,
|
||||
Connection txn) throws Exception {
|
||||
assertTrue(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false));
|
||||
Collection<MessageId> ids =
|
||||
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
@@ -2601,7 +2609,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
private void assertNothingToSendEagerly(Database<Connection> db,
|
||||
Connection txn) throws Exception {
|
||||
assertFalse(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true));
|
||||
Collection<MessageId> unacked =
|
||||
db.getUnackedMessagesToSend(txn, contactId);
|
||||
assertTrue(unacked.isEmpty());
|
||||
@@ -2611,7 +2619,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
private void assertOneMessageToSendEagerly(Database<Connection> db,
|
||||
Connection txn) throws Exception {
|
||||
assertTrue(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true));
|
||||
Collection<MessageId> unacked =
|
||||
db.getUnackedMessagesToSend(txn, contactId);
|
||||
assertEquals(singletonList(messageId), unacked);
|
||||
|
||||
@@ -14,7 +14,6 @@ import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
@@ -41,13 +40,8 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
|
||||
private final KeyPair handshakeKeyPair =
|
||||
new KeyPair(handshakePublicKey, handshakePrivateKey);
|
||||
|
||||
private IdentityManagerImpl identityManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
identityManager =
|
||||
new IdentityManagerImpl(db, crypto, authorFactory, clock);
|
||||
}
|
||||
private final IdentityManagerImpl identityManager =
|
||||
new IdentityManagerImpl(db, crypto, authorFactory, clock);
|
||||
|
||||
@Test
|
||||
public void testOpenDatabaseIdentityRegistered() throws Exception {
|
||||
|
||||
@@ -9,12 +9,15 @@ import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.junit.Before;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
|
||||
import static org.briarproject.bramble.api.system.Clock.MAX_REASONABLE_TIME_MS;
|
||||
@@ -30,12 +33,8 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final SecretKey dbKey = getSecretKey();
|
||||
|
||||
private LifecycleManagerImpl lifecycleManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
lifecycleManager = new LifecycleManagerImpl(db, eventBus, clock);
|
||||
}
|
||||
private final LifecycleManagerImpl lifecycleManager =
|
||||
new LifecycleManagerImpl(db, eventBus, clock);
|
||||
|
||||
@Test
|
||||
public void testOpenDatabaseHooksAreCalledAtStartup() throws Exception {
|
||||
@@ -57,6 +56,7 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
||||
lifecycleManager.registerOpenDatabaseHook(hook);
|
||||
|
||||
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
|
||||
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
|
||||
assertTrue(called.get());
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
assertEquals(CLOCK_ERROR, lifecycleManager.startServices(dbKey));
|
||||
assertEquals(STARTING, lifecycleManager.getLifecycleState());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -80,5 +81,40 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
assertEquals(CLOCK_ERROR, lifecycleManager.startServices(dbKey));
|
||||
assertEquals(STARTING, lifecycleManager.getLifecycleState());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecondCallToStopServicesReturnsEarly() throws Exception {
|
||||
long now = System.currentTimeMillis();
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(db).open(dbKey, lifecycleManager);
|
||||
will(returnValue(false));
|
||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||
oneOf(db).removeTemporaryMessages(txn);
|
||||
exactly(2).of(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
||||
}});
|
||||
|
||||
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
|
||||
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
|
||||
context.assertIsSatisfied();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
||||
oneOf(db).close();
|
||||
}});
|
||||
|
||||
lifecycleManager.stopServices();
|
||||
assertEquals(STOPPING, lifecycleManager.getLifecycleState());
|
||||
context.assertIsSatisfied();
|
||||
|
||||
// Calling stopServices() again should not broadcast another event or
|
||||
// try to close the DB again
|
||||
lifecycleManager.stopServices();
|
||||
assertEquals(STOPPING, lifecycleManager.getLifecycleState());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import org.junit.Test;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
||||
import static org.briarproject.bramble.mailbox.ConnectivityCheckerImpl.CONNECTIVITY_CHECK_FRESHNESS_MS;
|
||||
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||
|
||||
public class ConnectivityCheckerImplTest extends BrambleMockTestCase {
|
||||
@@ -187,6 +187,32 @@ public class ConnectivityCheckerImplTest extends BrambleMockTestCase {
|
||||
checker.onConnectivityCheckSucceeded(now);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckIsCancelledWhenObserverIsRemoved() {
|
||||
ConnectivityCheckerImpl checker = createChecker();
|
||||
|
||||
// When checkConnectivity() is called a check should be started
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(mailboxApiCaller).retryWithBackoff(apiCall);
|
||||
will(returnValue(task));
|
||||
}});
|
||||
|
||||
checker.checkConnectivity(properties, observer1);
|
||||
|
||||
// When the observer is removed the check should be cancelled
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(task).cancel();
|
||||
}});
|
||||
|
||||
checker.removeObserver(observer1);
|
||||
|
||||
// If the check runs anyway (cancellation came too late) the observer
|
||||
// should not be called
|
||||
checker.onConnectivityCheckSucceeded(now);
|
||||
}
|
||||
|
||||
private ConnectivityCheckerImpl createChecker() {
|
||||
|
||||
return new ConnectivityCheckerImpl(clock, mailboxApiCaller) {
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||
|
||||
public class ContactMailboxClientTest extends BrambleMockTestCase {
|
||||
|
||||
private final MailboxWorkerFactory workerFactory =
|
||||
context.mock(MailboxWorkerFactory.class);
|
||||
private final ConnectivityChecker connectivityChecker =
|
||||
context.mock(ConnectivityChecker.class);
|
||||
private final TorReachabilityMonitor reachabilityMonitor =
|
||||
context.mock(TorReachabilityMonitor.class);
|
||||
private final MailboxWorker uploadWorker =
|
||||
context.mock(MailboxWorker.class, "uploadWorker");
|
||||
private final MailboxWorker downloadWorker =
|
||||
context.mock(MailboxWorker.class, "downloadWorker");
|
||||
|
||||
private final MailboxProperties properties =
|
||||
getMailboxProperties(false, CLIENT_SUPPORTS);
|
||||
private final MailboxFolderId inboxId =
|
||||
requireNonNull(properties.getInboxId());
|
||||
private final MailboxFolderId outboxId =
|
||||
requireNonNull(properties.getOutboxId());
|
||||
private final ContactId contactId = getContactId();
|
||||
|
||||
private final ContactMailboxClient client =
|
||||
new ContactMailboxClient(workerFactory, connectivityChecker,
|
||||
reachabilityMonitor);
|
||||
|
||||
@Test
|
||||
public void testStartAndDestroyWithNoContactsAssigned() {
|
||||
client.start();
|
||||
client.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAssignContactForUploadAndDestroyClient() {
|
||||
client.start();
|
||||
|
||||
// When the contact is assigned, the worker should be created and
|
||||
// started
|
||||
expectCreateAndStartUploadWorker();
|
||||
client.assignContactForUpload(contactId, properties, outboxId);
|
||||
|
||||
// When the client is destroyed, the worker should be destroyed
|
||||
expectDestroyWorker(uploadWorker);
|
||||
client.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAssignAndDeassignContactForUpload() {
|
||||
client.start();
|
||||
|
||||
// When the contact is assigned, the worker should be created and
|
||||
// started
|
||||
expectCreateAndStartUploadWorker();
|
||||
client.assignContactForUpload(contactId, properties, outboxId);
|
||||
|
||||
// When the contact is deassigned, the worker should be destroyed
|
||||
expectDestroyWorker(uploadWorker);
|
||||
client.deassignContactForUpload(contactId);
|
||||
context.assertIsSatisfied();
|
||||
|
||||
client.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assignContactForDownloadAndDestroyClient() {
|
||||
client.start();
|
||||
|
||||
// When the contact is assigned, the worker should be created and
|
||||
// started
|
||||
expectCreateAndStartDownloadWorker();
|
||||
client.assignContactForDownload(contactId, properties, inboxId);
|
||||
|
||||
// When the client is destroyed, the worker should be destroyed
|
||||
expectDestroyWorker(downloadWorker);
|
||||
client.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assignAndDeassignContactForDownload() {
|
||||
client.start();
|
||||
|
||||
// When the contact is assigned, the worker should be created and
|
||||
// started
|
||||
expectCreateAndStartDownloadWorker();
|
||||
client.assignContactForDownload(contactId, properties, inboxId);
|
||||
|
||||
// When the contact is deassigned, the worker should be destroyed
|
||||
expectDestroyWorker(downloadWorker);
|
||||
client.deassignContactForDownload(contactId);
|
||||
context.assertIsSatisfied();
|
||||
|
||||
client.destroy();
|
||||
}
|
||||
|
||||
private void expectCreateAndStartUploadWorker() {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(workerFactory).createUploadWorker(connectivityChecker,
|
||||
properties, outboxId, contactId);
|
||||
will(returnValue(uploadWorker));
|
||||
oneOf(uploadWorker).start();
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectCreateAndStartDownloadWorker() {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(workerFactory).createDownloadWorkerForContactMailbox(
|
||||
connectivityChecker, reachabilityMonitor, properties);
|
||||
will(returnValue(downloadWorker));
|
||||
oneOf(downloadWorker).start();
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectDestroyWorker(MailboxWorker worker) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(worker).destroy();
|
||||
}});
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import org.junit.Test;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.Cancellable;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFileId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.lib.action.DoAllAction;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class ContactMailboxDownloadWorkerTest extends BrambleMockTestCase {
|
||||
|
||||
private final ConnectivityChecker connectivityChecker =
|
||||
context.mock(ConnectivityChecker.class);
|
||||
private final TorReachabilityMonitor torReachabilityMonitor =
|
||||
context.mock(TorReachabilityMonitor.class);
|
||||
private final MailboxApiCaller mailboxApiCaller =
|
||||
context.mock(MailboxApiCaller.class);
|
||||
private final MailboxApi mailboxApi = context.mock(MailboxApi.class);
|
||||
private final MailboxFileManager mailboxFileManager =
|
||||
context.mock(MailboxFileManager.class);
|
||||
private final Cancellable apiCall = context.mock(Cancellable.class);
|
||||
|
||||
private final MailboxProperties mailboxProperties =
|
||||
getMailboxProperties(false, CLIENT_SUPPORTS);
|
||||
private final long now = System.currentTimeMillis();
|
||||
private final MailboxFile file1 =
|
||||
new MailboxFile(new MailboxFileId(getRandomId()), now - 1);
|
||||
private final MailboxFile file2 =
|
||||
new MailboxFile(new MailboxFileId(getRandomId()), now);
|
||||
private final List<MailboxFile> files = asList(file1, file2);
|
||||
|
||||
private File testDir, tempFile;
|
||||
private ContactMailboxDownloadWorker worker;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testDir = getTestDirectory();
|
||||
tempFile = new File(testDir, "temp");
|
||||
worker = new ContactMailboxDownloadWorker(connectivityChecker,
|
||||
torReachabilityMonitor, mailboxApiCaller, mailboxApi,
|
||||
mailboxFileManager, mailboxProperties);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
deleteTestDirectory(testDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChecksConnectivityWhenStartedAndRemovesObserverWhenDestroyed() {
|
||||
// When the worker is started it should start a connectivity check
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(connectivityChecker).checkConnectivity(
|
||||
with(mailboxProperties), with(worker));
|
||||
}});
|
||||
|
||||
worker.start();
|
||||
|
||||
// When the worker is destroyed it should remove the connectivity
|
||||
// and reachability observers
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(connectivityChecker).removeObserver(worker);
|
||||
oneOf(torReachabilityMonitor).removeObserver(worker);
|
||||
}});
|
||||
|
||||
worker.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDownloadsFilesWhenConnectivityCheckSucceeds()
|
||||
throws Exception {
|
||||
// When the worker is started it should start a connectivity check
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(connectivityChecker).checkConnectivity(
|
||||
with(mailboxProperties), with(worker));
|
||||
}});
|
||||
|
||||
worker.start();
|
||||
|
||||
// When the connectivity check succeeds, a list-inbox task should be
|
||||
// started for the first download cycle
|
||||
AtomicReference<ApiCall> listTask = new AtomicReference<>();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(listTask, ApiCall.class, 0),
|
||||
returnValue(apiCall)
|
||||
));
|
||||
}});
|
||||
|
||||
worker.onConnectivityCheckSucceeded();
|
||||
|
||||
// When the list-inbox tasks runs and finds some files to download,
|
||||
// it should start a download task for the first file
|
||||
AtomicReference<ApiCall> downloadTask = new AtomicReference<>();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(mailboxApi).getFiles(mailboxProperties,
|
||||
requireNonNull(mailboxProperties.getInboxId()));
|
||||
will(returnValue(files));
|
||||
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(downloadTask, ApiCall.class, 0),
|
||||
returnValue(apiCall)
|
||||
));
|
||||
}});
|
||||
|
||||
assertFalse(listTask.get().callApi());
|
||||
|
||||
// When the first download task runs it should download the file to the
|
||||
// location provided by the file manager and start a delete task
|
||||
AtomicReference<ApiCall> deleteTask = new AtomicReference<>();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(mailboxFileManager).createTempFileForDownload();
|
||||
will(returnValue(tempFile));
|
||||
oneOf(mailboxApi).getFile(mailboxProperties,
|
||||
requireNonNull(mailboxProperties.getInboxId()),
|
||||
file1.name, tempFile);
|
||||
oneOf(mailboxFileManager).handleDownloadedFile(tempFile);
|
||||
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(deleteTask, ApiCall.class, 0),
|
||||
returnValue(apiCall)
|
||||
));
|
||||
}});
|
||||
|
||||
assertFalse(downloadTask.get().callApi());
|
||||
|
||||
// When the first delete task runs it should delete the file, ignore
|
||||
// the tolerable failure, and start a download task for the next file
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(mailboxApi).deleteFile(mailboxProperties,
|
||||
requireNonNull(mailboxProperties.getInboxId()), file1.name);
|
||||
will(throwException(new TolerableFailureException()));
|
||||
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(downloadTask, ApiCall.class, 0),
|
||||
returnValue(apiCall)
|
||||
));
|
||||
}});
|
||||
|
||||
assertFalse(deleteTask.get().callApi());
|
||||
|
||||
// When the second download task runs it should download the file to
|
||||
// the location provided by the file manager and start a delete task
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(mailboxFileManager).createTempFileForDownload();
|
||||
will(returnValue(tempFile));
|
||||
oneOf(mailboxApi).getFile(mailboxProperties,
|
||||
requireNonNull(mailboxProperties.getInboxId()),
|
||||
file2.name, tempFile);
|
||||
oneOf(mailboxFileManager).handleDownloadedFile(tempFile);
|
||||
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(deleteTask, ApiCall.class, 0),
|
||||
returnValue(apiCall)
|
||||
));
|
||||
}});
|
||||
|
||||
assertFalse(downloadTask.get().callApi());
|
||||
|
||||
// When the second delete task runs it should delete the file and
|
||||
// start a list-inbox task to check for files that may have arrived
|
||||
// since the first download cycle started
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(mailboxApi).deleteFile(mailboxProperties,
|
||||
requireNonNull(mailboxProperties.getInboxId()), file2.name);
|
||||
will(throwException(new TolerableFailureException()));
|
||||
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(listTask, ApiCall.class, 0),
|
||||
returnValue(apiCall)
|
||||
));
|
||||
}});
|
||||
|
||||
assertFalse(deleteTask.get().callApi());
|
||||
|
||||
// When the list-inbox tasks runs and finds no more files to download,
|
||||
// it should add a Tor reachability observer
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(mailboxApi).getFiles(mailboxProperties,
|
||||
requireNonNull(mailboxProperties.getInboxId()));
|
||||
will(returnValue(emptyList()));
|
||||
oneOf(torReachabilityMonitor).addOneShotObserver(worker);
|
||||
}});
|
||||
|
||||
assertFalse(listTask.get().callApi());
|
||||
|
||||
// When the reachability observer is called, a list-inbox task should
|
||||
// be started for the second download cycle
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(listTask, ApiCall.class, 0),
|
||||
returnValue(apiCall)
|
||||
));
|
||||
}});
|
||||
|
||||
worker.onTorReachable();
|
||||
|
||||
// When the list-inbox tasks runs and finds no more files to download,
|
||||
// it should finish the second download cycle
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(mailboxApi).getFiles(mailboxProperties,
|
||||
requireNonNull(mailboxProperties.getInboxId()));
|
||||
will(returnValue(emptyList()));
|
||||
}});
|
||||
|
||||
assertFalse(listTask.get().callApi());
|
||||
|
||||
// When the worker is destroyed it should remove the connectivity
|
||||
// and reachability observers
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(connectivityChecker).removeObserver(worker);
|
||||
oneOf(torReachabilityMonitor).removeObserver(worker);
|
||||
}});
|
||||
|
||||
worker.destroy();
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ import okio.Buffer;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
||||
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||
import org.briarproject.bramble.api.connection.ConnectionManager.TagController;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||
import org.briarproject.bramble.test.ConsumeArgumentAction;
|
||||
import org.briarproject.bramble.test.RunAction;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.lib.action.DoAllAction;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
|
||||
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
|
||||
import static org.briarproject.bramble.mailbox.MailboxFileManagerImpl.DOWNLOAD_DIR_NAME;
|
||||
import static org.briarproject.bramble.mailbox.MailboxFileManagerImpl.UPLOAD_DIR_NAME;
|
||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class MailboxFileManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final Executor ioExecutor = context.mock(Executor.class);
|
||||
private final PluginManager pluginManager =
|
||||
context.mock(PluginManager.class);
|
||||
private final ConnectionManager connectionManager =
|
||||
context.mock(ConnectionManager.class);
|
||||
private final LifecycleManager lifecycleManager =
|
||||
context.mock(LifecycleManager.class);
|
||||
private final EventBus eventBus = context.mock(EventBus.class);
|
||||
private final SimplexPlugin plugin = context.mock(SimplexPlugin.class);
|
||||
private final TransportConnectionReader transportConnectionReader =
|
||||
context.mock(TransportConnectionReader.class);
|
||||
private final TransportConnectionWriter transportConnectionWriter =
|
||||
context.mock(TransportConnectionWriter.class);
|
||||
|
||||
private final ContactId contactId = getContactId();
|
||||
|
||||
private File mailboxDir;
|
||||
private MailboxFileManagerImpl manager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mailboxDir = getTestDirectory();
|
||||
manager = new MailboxFileManagerImpl(ioExecutor, pluginManager,
|
||||
connectionManager, lifecycleManager, mailboxDir, eventBus);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
deleteTestDirectory(mailboxDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandlesOrphanedFilesAtStartup() throws Exception {
|
||||
// Create an orphaned upload, left behind at the previous shutdown
|
||||
File uploadDir = new File(mailboxDir, UPLOAD_DIR_NAME);
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
uploadDir.mkdirs();
|
||||
File orphanedUpload = new File(uploadDir, "orphan");
|
||||
assertTrue(orphanedUpload.createNewFile());
|
||||
|
||||
// Create an orphaned download, left behind at the previous shutdown
|
||||
File downloadDir = new File(mailboxDir, DOWNLOAD_DIR_NAME);
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
downloadDir.mkdirs();
|
||||
File orphanedDownload = new File(downloadDir, "orphan");
|
||||
assertTrue(orphanedDownload.createNewFile());
|
||||
|
||||
TransportProperties props = new TransportProperties();
|
||||
props.put(PROP_PATH, orphanedDownload.getAbsolutePath());
|
||||
|
||||
// When the plugin becomes active the orphaned upload should be deleted
|
||||
// and the orphaned download should be handled
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(ioExecutor).execute(with(any(Runnable.class)));
|
||||
will(new RunAction());
|
||||
oneOf(eventBus).removeListener(manager);
|
||||
oneOf(pluginManager).getPlugin(ID);
|
||||
will(returnValue(plugin));
|
||||
oneOf(plugin).createReader(props);
|
||||
will(returnValue(transportConnectionReader));
|
||||
oneOf(connectionManager).manageIncomingConnection(with(ID),
|
||||
with(any(TransportConnectionReader.class)),
|
||||
with(any(TagController.class)));
|
||||
}});
|
||||
|
||||
manager.eventOccurred(new TransportActiveEvent(ID));
|
||||
|
||||
assertFalse(orphanedUpload.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeletesDownloadedFileWhenReadSucceeds() throws Exception {
|
||||
expectCheckForOrphans();
|
||||
manager.eventOccurred(new TransportActiveEvent(ID));
|
||||
|
||||
File f = manager.createTempFileForDownload();
|
||||
AtomicReference<TransportConnectionReader> reader =
|
||||
new AtomicReference<>(null);
|
||||
AtomicReference<TagController> controller = new AtomicReference<>(null);
|
||||
|
||||
expectPassDownloadedFileToConnectionManager(f, reader, controller);
|
||||
manager.handleDownloadedFile(f);
|
||||
|
||||
// The read is successful, so the tag controller should allow the tag
|
||||
// to be marked as read and the reader should delete the file
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(transportConnectionReader).dispose(false, true);
|
||||
}});
|
||||
|
||||
assertTrue(controller.get().shouldMarkTagAsRecognised(false));
|
||||
reader.get().dispose(false, true);
|
||||
assertFalse(f.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeletesDownloadedFileWhenTagIsNotRecognised()
|
||||
throws Exception {
|
||||
testDeletesDownloadedFile(false, RUNNING, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeletesDownloadedFileWhenReadFails() throws Exception {
|
||||
testDeletesDownloadedFile(true, RUNNING, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotDeleteDownloadedFileWhenTagIsNotRecognisedAtShutdown()
|
||||
throws Exception {
|
||||
testDeletesDownloadedFile(false, STOPPING, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotDeleteDownloadedFileWhenReadFailsAtShutdown()
|
||||
throws Exception {
|
||||
testDeletesDownloadedFile(true, STOPPING, true);
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testThrowsExceptionIfPluginFailsToCreateWriter()
|
||||
throws Exception {
|
||||
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
|
||||
|
||||
expectCheckForOrphans();
|
||||
manager.eventOccurred(new TransportActiveEvent(ID));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(pluginManager).getPlugin(ID);
|
||||
will(returnValue(plugin));
|
||||
oneOf(plugin).createWriter(with(any(TransportProperties.class)));
|
||||
will(returnValue(null));
|
||||
}});
|
||||
|
||||
manager.createAndWriteTempFileForUpload(contactId, sessionRecord);
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testThrowsExceptionIfSessionFailsWithException()
|
||||
throws Exception {
|
||||
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
|
||||
|
||||
expectCheckForOrphans();
|
||||
manager.eventOccurred(new TransportActiveEvent(ID));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(pluginManager).getPlugin(ID);
|
||||
will(returnValue(plugin));
|
||||
oneOf(plugin).createWriter(with(any(TransportProperties.class)));
|
||||
will(returnValue(transportConnectionWriter));
|
||||
oneOf(transportConnectionWriter).dispose(true);
|
||||
oneOf(connectionManager).manageOutgoingConnection(with(contactId),
|
||||
with(ID), with(any(TransportConnectionWriter.class)),
|
||||
with(sessionRecord));
|
||||
// The session fails with an exception. We need to use an action
|
||||
// for this, as createAndWriteTempFileForUpload() waits for it to
|
||||
// happen before returning
|
||||
will(new ConsumeArgumentAction<>(TransportConnectionWriter.class, 2,
|
||||
writer -> {
|
||||
try {
|
||||
writer.dispose(true);
|
||||
} catch (IOException e) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
));
|
||||
}});
|
||||
|
||||
manager.createAndWriteTempFileForUpload(contactId, sessionRecord);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnsFileIfSessionSucceeds() throws Exception {
|
||||
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
|
||||
|
||||
expectCheckForOrphans();
|
||||
manager.eventOccurred(new TransportActiveEvent(ID));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(pluginManager).getPlugin(ID);
|
||||
will(returnValue(plugin));
|
||||
oneOf(plugin).createWriter(with(any(TransportProperties.class)));
|
||||
will(returnValue(transportConnectionWriter));
|
||||
oneOf(transportConnectionWriter).dispose(false);
|
||||
oneOf(connectionManager).manageOutgoingConnection(with(contactId),
|
||||
with(ID), with(any(TransportConnectionWriter.class)),
|
||||
with(sessionRecord));
|
||||
// The session succeeds. We need to use an action for this, as
|
||||
// createAndWriteTempFileForUpload() waits for it to happen before
|
||||
// returning
|
||||
will(new ConsumeArgumentAction<>(TransportConnectionWriter.class, 2,
|
||||
writer -> {
|
||||
try {
|
||||
writer.dispose(false);
|
||||
} catch (IOException e) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
));
|
||||
}});
|
||||
|
||||
File f = manager.createAndWriteTempFileForUpload(contactId,
|
||||
sessionRecord);
|
||||
assertTrue(f.exists());
|
||||
}
|
||||
|
||||
private void testDeletesDownloadedFile(boolean recognised,
|
||||
LifecycleState state, boolean fileExists) throws Exception {
|
||||
expectCheckForOrphans();
|
||||
manager.eventOccurred(new TransportActiveEvent(ID));
|
||||
|
||||
File f = manager.createTempFileForDownload();
|
||||
AtomicReference<TransportConnectionReader> reader =
|
||||
new AtomicReference<>(null);
|
||||
AtomicReference<TagController> controller = new AtomicReference<>(null);
|
||||
|
||||
expectPassDownloadedFileToConnectionManager(f, reader, controller);
|
||||
manager.handleDownloadedFile(f);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(transportConnectionReader).dispose(true, recognised);
|
||||
oneOf(lifecycleManager).getLifecycleState();
|
||||
will(returnValue(state));
|
||||
}});
|
||||
|
||||
reader.get().dispose(true, recognised);
|
||||
assertEquals(fileExists, f.exists());
|
||||
}
|
||||
|
||||
private void expectCheckForOrphans() {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(ioExecutor).execute(with(any(Runnable.class)));
|
||||
will(new RunAction());
|
||||
oneOf(eventBus).removeListener(manager);
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectPassDownloadedFileToConnectionManager(File f,
|
||||
AtomicReference<TransportConnectionReader> reader,
|
||||
AtomicReference<TagController> controller) {
|
||||
TransportProperties props = new TransportProperties();
|
||||
props.put(PROP_PATH, f.getAbsolutePath());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(pluginManager).getPlugin(ID);
|
||||
will(returnValue(plugin));
|
||||
oneOf(plugin).createReader(props);
|
||||
will(returnValue(transportConnectionReader));
|
||||
oneOf(connectionManager).manageIncomingConnection(with(ID),
|
||||
with(any(TransportConnectionReader.class)),
|
||||
with(any(TagController.class)));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(reader,
|
||||
TransportConnectionReader.class, 1),
|
||||
new CaptureArgumentAction<>(controller,
|
||||
TagController.class, 2)
|
||||
));
|
||||
}});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||
import static org.briarproject.bramble.test.TestUtils.hasEvent;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class MailboxManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final Executor ioExecutor = context.mock(Executor.class);
|
||||
private final MailboxApi api = context.mock(MailboxApi.class);
|
||||
private final TransactionManager db =
|
||||
context.mock(TransactionManager.class);
|
||||
private final MailboxSettingsManager mailboxSettingsManager =
|
||||
context.mock(MailboxSettingsManager.class);
|
||||
private final MailboxPairingTaskFactory pairingTaskFactory =
|
||||
context.mock(MailboxPairingTaskFactory.class);
|
||||
private final Clock clock =
|
||||
context.mock(Clock.class);
|
||||
|
||||
private final MailboxManagerImpl manager = new MailboxManagerImpl(
|
||||
ioExecutor, api, db, mailboxSettingsManager, pairingTaskFactory,
|
||||
clock);
|
||||
|
||||
@Test
|
||||
public void testDbExceptionDoesNotRecordFailure() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithNullableResult(with(true),
|
||||
withNullableDbCallable(txn));
|
||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||
will(throwException(new DbException()));
|
||||
}});
|
||||
|
||||
assertFalse(manager.checkConnection());
|
||||
assertFalse(hasEvent(txn, OwnMailboxConnectionStatusEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIOExceptionDoesRecordFailure() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
Transaction txn2 = new Transaction(null, false);
|
||||
MailboxProperties props = getMailboxProperties(true, CLIENT_SUPPORTS);
|
||||
long now = new Random().nextLong();
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithNullableResult(with(true),
|
||||
withNullableDbCallable(txn));
|
||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||
will(returnValue(props));
|
||||
oneOf(api).getServerSupports(props);
|
||||
will(throwException(new IOException()));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(db).transaction(with(false), withDbRunnable(txn2));
|
||||
oneOf(mailboxSettingsManager)
|
||||
.recordFailedConnectionAttempt(txn2, now);
|
||||
}});
|
||||
|
||||
assertFalse(manager.checkConnection());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApiExceptionDoesRecordFailure() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
Transaction txn2 = new Transaction(null, false);
|
||||
MailboxProperties props = getMailboxProperties(true, CLIENT_SUPPORTS);
|
||||
long now = new Random().nextLong();
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithNullableResult(with(true),
|
||||
withNullableDbCallable(txn));
|
||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||
will(returnValue(props));
|
||||
oneOf(api).getServerSupports(props);
|
||||
will(throwException(new MailboxApi.ApiException()));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(db).transaction(with(false), withDbRunnable(txn2));
|
||||
oneOf(mailboxSettingsManager)
|
||||
.recordFailedConnectionAttempt(txn2, now);
|
||||
}});
|
||||
|
||||
assertFalse(manager.checkConnection());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectionSuccess() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
Transaction txn2 = new Transaction(null, false);
|
||||
MailboxProperties props = getMailboxProperties(true, CLIENT_SUPPORTS);
|
||||
long now = new Random().nextLong();
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithNullableResult(with(true),
|
||||
withNullableDbCallable(txn));
|
||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||
will(returnValue(props));
|
||||
oneOf(api).getServerSupports(props);
|
||||
will(returnValue(CLIENT_SUPPORTS));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(db).transaction(with(false), withDbRunnable(txn2));
|
||||
oneOf(mailboxSettingsManager)
|
||||
.recordSuccessfulConnection(txn2, now, CLIENT_SUPPORTS);
|
||||
}});
|
||||
|
||||
assertTrue(manager.checkConnection());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,7 +13,6 @@ import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
@@ -33,7 +32,6 @@ import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.hasEvent;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -138,7 +136,6 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
||||
i.getAndIncrement();
|
||||
});
|
||||
task.run();
|
||||
hasEvent(txn, OwnMailboxConnectionStatusEvent.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -18,6 +18,7 @@ import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ATTEMPTS;
|
||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_ATTEMPT;
|
||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_SUCCESS;
|
||||
@@ -146,17 +147,44 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testRecordsSuccess() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Settings oldSettings = new Settings();
|
||||
oldSettings
|
||||
.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, serverSupportsInts);
|
||||
Settings expectedSettings = new Settings();
|
||||
expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
||||
expectedSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
|
||||
expectedSettings.putInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
|
||||
will(returnValue(oldSettings));
|
||||
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
|
||||
SETTINGS_NAMESPACE);
|
||||
}});
|
||||
|
||||
manager.recordSuccessfulConnection(txn, now);
|
||||
assertTrue(hasEvent(txn, OwnMailboxConnectionStatusEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecordsSuccessWithVersions() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
List<MailboxVersion> versions = singletonList(new MailboxVersion(2, 1));
|
||||
Settings expectedSettings = new Settings();
|
||||
expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
||||
expectedSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
|
||||
expectedSettings.putInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||
expectedSettings.putInt(SETTINGS_KEY_SERVER_SUPPORTS, 0);
|
||||
int[] newVersionsInts = {2, 1};
|
||||
expectedSettings
|
||||
.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, newVersionsInts);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
|
||||
SETTINGS_NAMESPACE);
|
||||
}});
|
||||
|
||||
manager.recordSuccessfulConnection(txn, now, versions);
|
||||
hasEvent(txn, OwnMailboxConnectionStatusEvent.class);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,465 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.Cancellable;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||
import org.briarproject.bramble.api.sync.event.MessageSharedEvent;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||
import org.briarproject.bramble.test.ConsumeArgumentAction;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.briarproject.bramble.test.RunAction;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.lib.action.DoAllAction;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_LATENCY;
|
||||
import static org.briarproject.bramble.mailbox.MailboxUploadWorker.CHECK_DELAY_MS;
|
||||
import static org.briarproject.bramble.mailbox.MailboxUploadWorker.RETRY_DELAY_MS;
|
||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class MailboxUploadWorkerTest extends BrambleMockTestCase {
|
||||
|
||||
private final Executor ioExecutor = context.mock(Executor.class);
|
||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
private final Clock clock = context.mock(Clock.class);
|
||||
private final TaskScheduler taskScheduler =
|
||||
context.mock(TaskScheduler.class);
|
||||
private final EventBus eventBus = context.mock(EventBus.class);
|
||||
private final ConnectivityChecker connectivityChecker =
|
||||
context.mock(ConnectivityChecker.class);
|
||||
private final MailboxApiCaller mailboxApiCaller =
|
||||
context.mock(MailboxApiCaller.class);
|
||||
private final MailboxApi mailboxApi = context.mock(MailboxApi.class);
|
||||
private final MailboxFileManager mailboxFileManager =
|
||||
context.mock(MailboxFileManager.class);
|
||||
private final Cancellable apiCall =
|
||||
context.mock(Cancellable.class, "apiCall");
|
||||
private final Cancellable wakeupTask =
|
||||
context.mock(Cancellable.class, "wakeupTask");
|
||||
private final Cancellable checkTask =
|
||||
context.mock(Cancellable.class, "checkTask");
|
||||
|
||||
private final MailboxProperties mailboxProperties =
|
||||
getMailboxProperties(false, CLIENT_SUPPORTS);
|
||||
private final long now = System.currentTimeMillis();
|
||||
private final MailboxFolderId folderId = new MailboxFolderId(getRandomId());
|
||||
private final ContactId contactId = getContactId();
|
||||
private final MessageId ackedId = new MessageId(getRandomId());
|
||||
private final MessageId sentId = new MessageId(getRandomId());
|
||||
private final MessageId newMessageId = new MessageId(getRandomId());
|
||||
|
||||
private File testDir, tempFile;
|
||||
private MailboxUploadWorker worker;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testDir = getTestDirectory();
|
||||
tempFile = new File(testDir, "temp");
|
||||
worker = new MailboxUploadWorker(ioExecutor, db, clock, taskScheduler,
|
||||
eventBus, connectivityChecker, mailboxApiCaller, mailboxApi,
|
||||
mailboxFileManager, mailboxProperties, folderId, contactId);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
deleteTestDirectory(testDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChecksForDataWhenStartedAndRemovesObserverWhenDestroyed()
|
||||
throws Exception {
|
||||
// When the worker is started it should check for data to send
|
||||
expectRunTaskOnIoExecutor();
|
||||
expectCheckForDataToSendNoDataWaiting();
|
||||
|
||||
worker.start();
|
||||
|
||||
// When the worker is destroyed it should remove the connectivity
|
||||
// observer and event listener
|
||||
expectRemoveObserverAndListener();
|
||||
|
||||
worker.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChecksConnectivityWhenStartedIfDataIsReady()
|
||||
throws Exception {
|
||||
Transaction recordTxn = new Transaction(null, false);
|
||||
|
||||
// When the worker is started it should check for data to send. As
|
||||
// there's data ready to send immediately, the worker should start a
|
||||
// connectivity check
|
||||
expectRunTaskOnIoExecutor();
|
||||
expectCheckForDataToSendAndStartConnectivityCheck();
|
||||
|
||||
worker.start();
|
||||
|
||||
// Create the temporary file so we can test that it gets deleted
|
||||
assertTrue(testDir.mkdirs());
|
||||
assertTrue(tempFile.createNewFile());
|
||||
|
||||
// When the connectivity check succeeds, the worker should write a file
|
||||
// and start an upload task
|
||||
expectRunTaskOnIoExecutor();
|
||||
AtomicReference<ApiCall> upload = new AtomicReference<>();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(mailboxFileManager).createAndWriteTempFileForUpload(
|
||||
with(contactId), with(any(OutgoingSessionRecord.class)));
|
||||
will(new DoAllAction(
|
||||
// Record some IDs as acked and sent
|
||||
new ConsumeArgumentAction<>(OutgoingSessionRecord.class, 1,
|
||||
record -> {
|
||||
record.onAckSent(singletonList(ackedId));
|
||||
record.onMessageSent(sentId);
|
||||
}),
|
||||
returnValue(tempFile)
|
||||
));
|
||||
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(upload, ApiCall.class, 0),
|
||||
returnValue(apiCall)
|
||||
));
|
||||
}});
|
||||
|
||||
worker.onConnectivityCheckSucceeded();
|
||||
|
||||
// When the upload task runs, it should upload the file, record
|
||||
// the acked/sent messages in the DB, and check for more data to send
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(mailboxApi).addFile(mailboxProperties, folderId, tempFile);
|
||||
oneOf(db).transaction(with(false), withDbRunnable(recordTxn));
|
||||
oneOf(db).setAckSent(recordTxn, contactId, singletonList(ackedId));
|
||||
oneOf(db).setMessagesSent(recordTxn, contactId,
|
||||
singletonList(sentId), MAX_LATENCY);
|
||||
}});
|
||||
expectCheckForDataToSendNoDataWaiting();
|
||||
|
||||
assertFalse(upload.get().callApi());
|
||||
|
||||
// When the worker is destroyed it should remove the connectivity
|
||||
// observer and event listener
|
||||
expectRemoveObserverAndListener();
|
||||
|
||||
worker.destroy();
|
||||
|
||||
// The file should have been deleted
|
||||
assertFalse(tempFile.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelsApiCallWhenDestroyed() throws Exception {
|
||||
// When the worker is started it should check for data to send. As
|
||||
// there's data ready to send immediately, the worker should start a
|
||||
// connectivity check
|
||||
expectRunTaskOnIoExecutor();
|
||||
expectCheckForDataToSendAndStartConnectivityCheck();
|
||||
|
||||
worker.start();
|
||||
|
||||
// Create the temporary file so we can test that it gets deleted
|
||||
assertTrue(testDir.mkdirs());
|
||||
assertTrue(tempFile.createNewFile());
|
||||
|
||||
// When the connectivity check succeeds, the worker should write a file
|
||||
// and start an upload task
|
||||
expectRunTaskOnIoExecutor();
|
||||
AtomicReference<ApiCall> upload = new AtomicReference<>();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(mailboxFileManager).createAndWriteTempFileForUpload(
|
||||
with(contactId), with(any(OutgoingSessionRecord.class)));
|
||||
will(new DoAllAction(
|
||||
// Record some IDs as acked and sent
|
||||
new ConsumeArgumentAction<>(OutgoingSessionRecord.class, 1,
|
||||
record -> {
|
||||
record.onAckSent(singletonList(ackedId));
|
||||
record.onMessageSent(sentId);
|
||||
}),
|
||||
returnValue(tempFile)
|
||||
));
|
||||
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(upload, ApiCall.class, 0),
|
||||
returnValue(apiCall)
|
||||
));
|
||||
}});
|
||||
|
||||
worker.onConnectivityCheckSucceeded();
|
||||
|
||||
// When the worker is destroyed it should remove the connectivity
|
||||
// observer and event listener and cancel the upload task
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(apiCall).cancel();
|
||||
}});
|
||||
expectRemoveObserverAndListener();
|
||||
|
||||
worker.destroy();
|
||||
|
||||
// The file should have been deleted
|
||||
assertFalse(tempFile.exists());
|
||||
|
||||
// If the upload task runs anyway (cancellation came too late), it
|
||||
// should return early when it finds the state has changed
|
||||
assertFalse(upload.get().callApi());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchedulesWakeupWhenStartedIfDataIsNotReady()
|
||||
throws Exception {
|
||||
// When the worker is started it should check for data to send. As
|
||||
// the data isn't ready to send immediately, the worker should
|
||||
// schedule a wakeup
|
||||
expectRunTaskOnIoExecutor();
|
||||
AtomicReference<Runnable> wakeup = new AtomicReference<>();
|
||||
expectCheckForDataToSendAndScheduleWakeup(wakeup);
|
||||
|
||||
worker.start();
|
||||
|
||||
// When the wakeup task runs it should check for data to send
|
||||
expectCheckForDataToSendNoDataWaiting();
|
||||
|
||||
wakeup.get().run();
|
||||
|
||||
// When the worker is destroyed it should remove the connectivity
|
||||
// observer and event listener
|
||||
expectRemoveObserverAndListener();
|
||||
|
||||
worker.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelsWakeupIfDestroyedBeforeWakingUp() throws Exception {
|
||||
// When the worker is started it should check for data to send. As
|
||||
// the data isn't ready to send immediately, the worker should
|
||||
// schedule a wakeup
|
||||
expectRunTaskOnIoExecutor();
|
||||
AtomicReference<Runnable> wakeup = new AtomicReference<>();
|
||||
expectCheckForDataToSendAndScheduleWakeup(wakeup);
|
||||
|
||||
worker.start();
|
||||
|
||||
// When the worker is destroyed it should cancel the wakeup and
|
||||
// remove the connectivity observer and event listener
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(wakeupTask).cancel();
|
||||
}});
|
||||
expectRemoveObserverAndListener();
|
||||
|
||||
worker.destroy();
|
||||
|
||||
// If the wakeup task runs anyway (cancellation came too late), it
|
||||
// should return early without doing anything
|
||||
wakeup.get().run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelsWakeupIfEventIsReceivedBeforeWakingUp()
|
||||
throws Exception {
|
||||
// When the worker is started it should check for data to send. As
|
||||
// the data isn't ready to send immediately, the worker should
|
||||
// schedule a wakeup
|
||||
expectRunTaskOnIoExecutor();
|
||||
AtomicReference<Runnable> wakeup = new AtomicReference<>();
|
||||
expectCheckForDataToSendAndScheduleWakeup(wakeup);
|
||||
|
||||
worker.start();
|
||||
|
||||
// Before the wakeup task runs, the worker receives an event that
|
||||
// indicates new data may be available. The worker should cancel the
|
||||
// wakeup task and schedule a check for new data after a short delay
|
||||
AtomicReference<Runnable> check = new AtomicReference<>();
|
||||
expectScheduleCheck(check, CHECK_DELAY_MS);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(wakeupTask).cancel();
|
||||
}});
|
||||
|
||||
worker.eventOccurred(new MessageSharedEvent(newMessageId));
|
||||
|
||||
// If the wakeup task runs anyway (cancellation came too late), it
|
||||
// should return early when it finds the state has changed
|
||||
wakeup.get().run();
|
||||
|
||||
// Before the check task runs, the worker receives another event that
|
||||
// indicates new data may be available. The event should be ignored,
|
||||
// as a check for new data has already been scheduled
|
||||
worker.eventOccurred(new MessageSharedEvent(newMessageId));
|
||||
|
||||
// When the check task runs, it should check for new data
|
||||
expectCheckForDataToSendNoDataWaiting();
|
||||
|
||||
check.get().run();
|
||||
|
||||
// When the worker is destroyed it should remove the connectivity
|
||||
// observer and event listener
|
||||
expectRemoveObserverAndListener();
|
||||
|
||||
worker.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelsCheckWhenDestroyed() throws Exception {
|
||||
// When the worker is started it should check for data to send
|
||||
expectRunTaskOnIoExecutor();
|
||||
expectCheckForDataToSendNoDataWaiting();
|
||||
|
||||
worker.start();
|
||||
|
||||
// The worker receives an event that indicates new data may be
|
||||
// available. The worker should schedule a check for new data after
|
||||
// a short delay
|
||||
AtomicReference<Runnable> check = new AtomicReference<>();
|
||||
expectScheduleCheck(check, CHECK_DELAY_MS);
|
||||
|
||||
worker.eventOccurred(new MessageSharedEvent(newMessageId));
|
||||
|
||||
// When the worker is destroyed it should cancel the check and
|
||||
// remove the connectivity observer and event listener
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(checkTask).cancel();
|
||||
}});
|
||||
expectRemoveObserverAndListener();
|
||||
|
||||
worker.destroy();
|
||||
|
||||
// If the check runs anyway (cancellation came too late), it should
|
||||
// return early when it finds the state has changed
|
||||
check.get().run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRetriesAfterDelayIfExceptionOccursWhileWritingFile()
|
||||
throws Exception {
|
||||
// When the worker is started it should check for data to send. As
|
||||
// there's data ready to send immediately, the worker should start a
|
||||
// connectivity check
|
||||
expectRunTaskOnIoExecutor();
|
||||
expectCheckForDataToSendAndStartConnectivityCheck();
|
||||
|
||||
worker.start();
|
||||
|
||||
// When the connectivity check succeeds, the worker should try to
|
||||
// write a file. This fails with an exception, so the worker should
|
||||
// retry by scheduling a check for new data after a short delay
|
||||
expectRunTaskOnIoExecutor();
|
||||
AtomicReference<Runnable> check = new AtomicReference<>();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(mailboxFileManager).createAndWriteTempFileForUpload(
|
||||
with(contactId), with(any(OutgoingSessionRecord.class)));
|
||||
will(throwException(new IOException())); // Oh noes!
|
||||
}});
|
||||
expectScheduleCheck(check, RETRY_DELAY_MS);
|
||||
|
||||
worker.onConnectivityCheckSucceeded();
|
||||
|
||||
// When the check task runs it should check for new data
|
||||
expectCheckForDataToSendNoDataWaiting();
|
||||
|
||||
check.get().run();
|
||||
|
||||
// When the worker is destroyed it should remove the connectivity
|
||||
// observer and event listener
|
||||
expectRemoveObserverAndListener();
|
||||
|
||||
worker.destroy();
|
||||
}
|
||||
|
||||
private void expectRunTaskOnIoExecutor() {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(ioExecutor).execute(with(any(Runnable.class)));
|
||||
will(new RunAction());
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectCheckForDataToSendNoDataWaiting() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transaction(with(true), withDbRunnable(txn));
|
||||
oneOf(db).containsAcksToSend(txn, contactId);
|
||||
will(returnValue(false));
|
||||
oneOf(db).getNextSendTime(txn, contactId, MAX_LATENCY);
|
||||
will(returnValue(Long.MAX_VALUE)); // No data waiting
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectCheckForDataToSendAndScheduleWakeup(
|
||||
AtomicReference<Runnable> wakeup) throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transaction(with(true), withDbRunnable(txn));
|
||||
oneOf(db).containsAcksToSend(txn, contactId);
|
||||
will(returnValue(false));
|
||||
oneOf(db).getNextSendTime(txn, contactId, MAX_LATENCY);
|
||||
will(returnValue(now + 1234L)); // Data waiting but not ready
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
|
||||
with(ioExecutor), with(1234L), with(MILLISECONDS));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(wakeup, Runnable.class, 0),
|
||||
returnValue(wakeupTask)
|
||||
));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectCheckForDataToSendAndStartConnectivityCheck()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transaction(with(true), withDbRunnable(txn));
|
||||
oneOf(db).containsAcksToSend(txn, contactId);
|
||||
will(returnValue(false));
|
||||
oneOf(db).getNextSendTime(txn, contactId, MAX_LATENCY);
|
||||
will(returnValue(0L)); // Data ready to send
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(connectivityChecker).checkConnectivity(mailboxProperties,
|
||||
worker);
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectScheduleCheck(AtomicReference<Runnable> check,
|
||||
long delay) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
|
||||
with(ioExecutor), with(delay), with(MILLISECONDS));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(check, Runnable.class, 0),
|
||||
returnValue(checkTask)
|
||||
));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectRemoveObserverAndListener() {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(connectivityChecker).removeObserver(worker);
|
||||
oneOf(eventBus).removeListener(worker);
|
||||
}});
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
@@ -15,9 +16,11 @@ import org.jmock.lib.action.DoAllAction;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -39,6 +42,8 @@ public class OwnMailboxConnectivityCheckerTest extends BrambleMockTestCase {
|
||||
private final MailboxProperties properties =
|
||||
getMailboxProperties(true, CLIENT_SUPPORTS);
|
||||
private final long now = System.currentTimeMillis();
|
||||
private final List<MailboxVersion> serverSupports =
|
||||
singletonList(new MailboxVersion(123, 456));
|
||||
|
||||
@Test
|
||||
public void testObserverIsCalledWhenCheckSucceeds() throws Exception {
|
||||
@@ -62,12 +67,13 @@ public class OwnMailboxConnectivityCheckerTest extends BrambleMockTestCase {
|
||||
// When the check succeeds, the success should be recorded in the DB
|
||||
// and the observer should be called
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(mailboxApi).checkStatus(properties);
|
||||
will(returnValue(true));
|
||||
oneOf(mailboxApi).getServerSupports(properties);
|
||||
will(returnValue(serverSupports));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, now);
|
||||
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, now,
|
||||
serverSupports);
|
||||
oneOf(observer).onConnectivityCheckSucceeded();
|
||||
}});
|
||||
|
||||
@@ -97,7 +103,7 @@ public class OwnMailboxConnectivityCheckerTest extends BrambleMockTestCase {
|
||||
// When the check fails, the failure should be recorded in the DB and
|
||||
// the observer should not be called
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(mailboxApi).checkStatus(properties);
|
||||
oneOf(mailboxApi).getServerSupports(properties);
|
||||
will(throwException(new IOException()));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
|
||||
@@ -0,0 +1,406 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.Cancellable;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.briarproject.bramble.test.RunAction;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.lib.action.DoAllAction;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class OwnMailboxContactListWorkerTest extends BrambleMockTestCase {
|
||||
|
||||
private final Executor ioExecutor = context.mock(Executor.class);
|
||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
private final EventBus eventBus = context.mock(EventBus.class);
|
||||
private final ConnectivityChecker connectivityChecker =
|
||||
context.mock(ConnectivityChecker.class);
|
||||
private final MailboxApiCaller mailboxApiCaller =
|
||||
context.mock(MailboxApiCaller.class);
|
||||
private final MailboxApi mailboxApi = context.mock(MailboxApi.class);
|
||||
private final MailboxUpdateManager mailboxUpdateManager =
|
||||
context.mock(MailboxUpdateManager.class);
|
||||
private final Cancellable apiCall = context.mock(Cancellable.class);
|
||||
|
||||
private final MailboxProperties mailboxProperties =
|
||||
getMailboxProperties(true, CLIENT_SUPPORTS);
|
||||
private final Contact contact1 = getContact(), contact2 = getContact();
|
||||
private final MailboxProperties contactProperties1 =
|
||||
getMailboxProperties(false, CLIENT_SUPPORTS);
|
||||
private final MailboxProperties contactProperties2 =
|
||||
getMailboxProperties(false, CLIENT_SUPPORTS);
|
||||
private final MailboxUpdateWithMailbox update1 =
|
||||
new MailboxUpdateWithMailbox(CLIENT_SUPPORTS, contactProperties1);
|
||||
private final MailboxUpdateWithMailbox update2 =
|
||||
new MailboxUpdateWithMailbox(CLIENT_SUPPORTS, contactProperties2);
|
||||
|
||||
private final OwnMailboxContactListWorker worker =
|
||||
new OwnMailboxContactListWorker(ioExecutor, db, eventBus,
|
||||
connectivityChecker, mailboxApiCaller, mailboxApi,
|
||||
mailboxUpdateManager, mailboxProperties);
|
||||
|
||||
@Test
|
||||
public void testChecksConnectivityWhenStartedAndRemovesObserverWhenDestroyed() {
|
||||
// When the worker is started it should start a connectivity check
|
||||
expectStartConnectivityCheck();
|
||||
|
||||
worker.start();
|
||||
|
||||
// When the worker is destroyed it should remove the connectivity
|
||||
// observer and event listener
|
||||
expectRemoveConnectivityObserverAndEventListener();
|
||||
|
||||
worker.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdatesContactListWhenConnectivityCheckSucceeds()
|
||||
throws Exception {
|
||||
// When the worker is started it should start a connectivity check
|
||||
expectStartConnectivityCheck();
|
||||
|
||||
worker.start();
|
||||
|
||||
// When the connectivity check succeeds, the worker should start a
|
||||
// task to fetch the remote contact list
|
||||
AtomicReference<ApiCall> fetchList = new AtomicReference<>();
|
||||
expectStartTaskToFetchRemoteContactList(fetchList);
|
||||
|
||||
worker.onConnectivityCheckSucceeded();
|
||||
|
||||
// When the fetch task runs it should fetch the remote contact list,
|
||||
// load the local contact list, and find the differences. Contact 2
|
||||
// needs to be added and contact 1 needs to be removed. The worker
|
||||
// should load the mailbox update for contact 2 and start a task to
|
||||
// add contact 2 to the mailbox
|
||||
expectFetchRemoteContactList(singletonList(contact1.getId()));
|
||||
expectRunTaskOnIoExecutor();
|
||||
expectLoadLocalContactList(singletonList(contact2));
|
||||
expectRunTaskOnIoExecutor();
|
||||
expectLoadMailboxUpdate(contact2, update2);
|
||||
AtomicReference<ApiCall> addContact = new AtomicReference<>();
|
||||
expectStartTaskToAddContact(addContact);
|
||||
|
||||
assertFalse(fetchList.get().callApi());
|
||||
|
||||
// When the add-contact task runs it should add contact 2 to the
|
||||
// mailbox, then continue with the next update
|
||||
AtomicReference<MailboxContact> added = new AtomicReference<>();
|
||||
expectAddContactToMailbox(added);
|
||||
AtomicReference<ApiCall> removeContact = new AtomicReference<>();
|
||||
expectStartTaskToRemoveContact(removeContact);
|
||||
|
||||
assertFalse(addContact.get().callApi());
|
||||
|
||||
// Check that the added contact has the expected properties
|
||||
MailboxContact expected = new MailboxContact(contact2.getId(),
|
||||
contactProperties2.getAuthToken(),
|
||||
requireNonNull(contactProperties2.getInboxId()),
|
||||
requireNonNull(contactProperties2.getOutboxId()));
|
||||
assertMailboxContactEquals(expected, added.get());
|
||||
|
||||
// When the remove-contact task runs it should remove contact 1 from
|
||||
// the mailbox
|
||||
expectRemoveContactFromMailbox(contact1);
|
||||
|
||||
assertFalse(removeContact.get().callApi());
|
||||
|
||||
// When the worker is destroyed it should remove the connectivity
|
||||
// observer and event listener
|
||||
expectRemoveConnectivityObserverAndEventListener();
|
||||
|
||||
worker.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandlesEventsAfterMakingInitialUpdates() throws Exception {
|
||||
// When the worker is started it should start a connectivity check
|
||||
expectStartConnectivityCheck();
|
||||
|
||||
worker.start();
|
||||
|
||||
// When the connectivity check succeeds, the worker should start a
|
||||
// task to fetch the remote contact list
|
||||
AtomicReference<ApiCall> fetchList = new AtomicReference<>();
|
||||
expectStartTaskToFetchRemoteContactList(fetchList);
|
||||
|
||||
worker.onConnectivityCheckSucceeded();
|
||||
|
||||
// When the fetch task runs it should fetch the remote contact list,
|
||||
// load the local contact list, and find the differences. The lists
|
||||
// are the same, so the worker should wait for changes
|
||||
expectFetchRemoteContactList(emptyList());
|
||||
expectRunTaskOnIoExecutor();
|
||||
expectLoadLocalContactList(emptyList());
|
||||
|
||||
assertFalse(fetchList.get().callApi());
|
||||
|
||||
// When a contact is added, the worker should load the contact's
|
||||
// mailbox update and start a task to add the contact to the mailbox
|
||||
expectRunTaskOnIoExecutor();
|
||||
expectLoadMailboxUpdate(contact1, update1);
|
||||
AtomicReference<ApiCall> addContact = new AtomicReference<>();
|
||||
expectStartTaskToAddContact(addContact);
|
||||
|
||||
worker.eventOccurred(new ContactAddedEvent(contact1.getId(), true));
|
||||
|
||||
// When the add-contact task runs it should add contact 1 to the
|
||||
// mailbox
|
||||
AtomicReference<MailboxContact> added = new AtomicReference<>();
|
||||
expectAddContactToMailbox(added);
|
||||
|
||||
assertFalse(addContact.get().callApi());
|
||||
|
||||
// Check that the added contact has the expected properties
|
||||
MailboxContact expected = new MailboxContact(contact1.getId(),
|
||||
contactProperties1.getAuthToken(),
|
||||
requireNonNull(contactProperties1.getInboxId()),
|
||||
requireNonNull(contactProperties1.getOutboxId()));
|
||||
assertMailboxContactEquals(expected, added.get());
|
||||
|
||||
// When the contact is removed again, the worker should start a task
|
||||
// to remove the contact from the mailbox
|
||||
expectRunTaskOnIoExecutor();
|
||||
AtomicReference<ApiCall> removeContact = new AtomicReference<>();
|
||||
expectStartTaskToRemoveContact(removeContact);
|
||||
|
||||
worker.eventOccurred(new ContactRemovedEvent(contact1.getId()));
|
||||
|
||||
// When the remove-contact task runs it should remove the contact from
|
||||
// the mailbox
|
||||
expectRemoveContactFromMailbox(contact1);
|
||||
|
||||
assertFalse(removeContact.get().callApi());
|
||||
|
||||
// When the worker is destroyed it should remove the connectivity
|
||||
// observer and event listener
|
||||
expectRemoveConnectivityObserverAndEventListener();
|
||||
|
||||
worker.destroy();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHandlesNoSuchContactException() throws Exception {
|
||||
// When the worker is started it should start a connectivity check
|
||||
expectStartConnectivityCheck();
|
||||
|
||||
worker.start();
|
||||
|
||||
// When the connectivity check succeeds, the worker should start a
|
||||
// task to fetch the remote contact list
|
||||
AtomicReference<ApiCall> fetchList = new AtomicReference<>();
|
||||
expectStartTaskToFetchRemoteContactList(fetchList);
|
||||
|
||||
worker.onConnectivityCheckSucceeded();
|
||||
|
||||
// When the fetch task runs it should fetch the remote contact list,
|
||||
// load the local contact list, and find the differences. Contact 1
|
||||
// needs to be added, so the worker should submit a task to the
|
||||
// IO executor to load the contact's mailbox update
|
||||
expectFetchRemoteContactList(emptyList());
|
||||
expectRunTaskOnIoExecutor();
|
||||
expectLoadLocalContactList(singletonList(contact1));
|
||||
AtomicReference<Runnable> loadUpdate = new AtomicReference<>();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(ioExecutor).execute(with(any(Runnable.class)));
|
||||
will(new CaptureArgumentAction<>(loadUpdate, Runnable.class, 0));
|
||||
}});
|
||||
|
||||
assertFalse(fetchList.get().callApi());
|
||||
|
||||
// Before the contact's mailbox update can be loaded, the contact
|
||||
// is removed
|
||||
worker.eventOccurred(new ContactRemovedEvent(contact1.getId()));
|
||||
|
||||
// When the load-update task runs, a NoSuchContactException is thrown.
|
||||
// The worker should abandon adding the contact and move on to the
|
||||
// next update, which is the removal of the same contact. The worker
|
||||
// should start a task to remove the contact from the mailbox
|
||||
Transaction txn = new Transaction(null, false);
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||
oneOf(mailboxUpdateManager).getLocalUpdate(txn, contact1.getId());
|
||||
will(throwException(new NoSuchContactException()));
|
||||
}});
|
||||
AtomicReference<ApiCall> removeContact = new AtomicReference<>();
|
||||
expectStartTaskToRemoveContact(removeContact);
|
||||
|
||||
loadUpdate.get().run();
|
||||
|
||||
// When the remove-contact task runs it should remove the contact from
|
||||
// the mailbox. The contact was never added, so the call throws a
|
||||
// TolerableFailureException
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(mailboxApi).deleteContact(mailboxProperties,
|
||||
contact1.getId());
|
||||
will(throwException(new TolerableFailureException()));
|
||||
}});
|
||||
|
||||
assertFalse(removeContact.get().callApi());
|
||||
|
||||
// When the worker is destroyed it should remove the connectivity
|
||||
// observer and event listener
|
||||
expectRemoveConnectivityObserverAndEventListener();
|
||||
|
||||
worker.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelsApiCallWhenDestroyed() {
|
||||
// When the worker is started it should start a connectivity check
|
||||
expectStartConnectivityCheck();
|
||||
|
||||
worker.start();
|
||||
|
||||
// When the connectivity check succeeds, the worker should start a
|
||||
// task to fetch the remote contact list
|
||||
AtomicReference<ApiCall> fetchList = new AtomicReference<>();
|
||||
expectStartTaskToFetchRemoteContactList(fetchList);
|
||||
|
||||
worker.onConnectivityCheckSucceeded();
|
||||
|
||||
// The worker is destroyed before the task runs. The worker should
|
||||
// cancel the task remove the connectivity observer and event listener
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(apiCall).cancel();
|
||||
}});
|
||||
expectRemoveConnectivityObserverAndEventListener();
|
||||
|
||||
worker.destroy();
|
||||
}
|
||||
|
||||
private void expectStartConnectivityCheck() {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(connectivityChecker).checkConnectivity(
|
||||
with(mailboxProperties), with(worker));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectRemoveConnectivityObserverAndEventListener() {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(connectivityChecker).removeObserver(worker);
|
||||
oneOf(eventBus).removeListener(worker);
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectStartTaskToFetchRemoteContactList(
|
||||
AtomicReference<ApiCall> task) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(task, ApiCall.class, 0),
|
||||
returnValue(apiCall)
|
||||
));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectFetchRemoteContactList(List<ContactId> remote)
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(mailboxApi).getContacts(mailboxProperties);
|
||||
will(returnValue(remote));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectLoadLocalContactList(List<Contact> local)
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transaction(with(true), withDbRunnable(txn));
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(local));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectLoadMailboxUpdate(Contact c, MailboxUpdate update)
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(true),
|
||||
withDbCallable(txn));
|
||||
oneOf(mailboxUpdateManager).getLocalUpdate(txn, c.getId());
|
||||
will(returnValue(update));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectStartTaskToAddContact(AtomicReference<ApiCall> task) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(task, ApiCall.class, 0),
|
||||
returnValue(apiCall)
|
||||
));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectAddContactToMailbox(
|
||||
AtomicReference<MailboxContact> added) throws Exception {
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(mailboxApi).addContact(with(mailboxProperties),
|
||||
with(any(MailboxContact.class)));
|
||||
will(new CaptureArgumentAction<>(added, MailboxContact.class, 1));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectStartTaskToRemoveContact(AtomicReference<ApiCall> task) {
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(task, ApiCall.class, 0),
|
||||
returnValue(apiCall)
|
||||
));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectRemoveContactFromMailbox(Contact c) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(mailboxApi).deleteContact(mailboxProperties, c.getId());
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectRunTaskOnIoExecutor() {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(ioExecutor).execute(with(any(Runnable.class)));
|
||||
will(new RunAction());
|
||||
}});
|
||||
}
|
||||
|
||||
private void assertMailboxContactEquals(MailboxContact expected,
|
||||
MailboxContact actual) {
|
||||
assertEquals(expected.contactId, actual.contactId);
|
||||
assertEquals(expected.token, actual.token);
|
||||
assertEquals(expected.inboxId, actual.inboxId);
|
||||
assertEquals(expected.outboxId, actual.outboxId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.Cancellable;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.plugin.Plugin;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||
import org.briarproject.bramble.mailbox.TorReachabilityMonitor.TorReachabilityObserver;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.lib.action.DoAllAction;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
|
||||
import static org.briarproject.bramble.mailbox.TorReachabilityMonitor.REACHABILITY_PERIOD_MS;
|
||||
|
||||
public class TorReachabilityMonitorImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final Executor ioExecutor = context.mock(Executor.class);
|
||||
private final TaskScheduler taskScheduler =
|
||||
context.mock(TaskScheduler.class);
|
||||
private final PluginManager pluginManager =
|
||||
context.mock(PluginManager.class);
|
||||
private final EventBus eventBus = context.mock(EventBus.class);
|
||||
private final Plugin plugin = context.mock(Plugin.class);
|
||||
private final Cancellable scheduledTask = context.mock(Cancellable.class);
|
||||
private final TorReachabilityObserver observer =
|
||||
context.mock(TorReachabilityObserver.class);
|
||||
|
||||
private final TorReachabilityMonitorImpl monitor =
|
||||
new TorReachabilityMonitorImpl(ioExecutor, taskScheduler,
|
||||
pluginManager, eventBus);
|
||||
|
||||
@Test
|
||||
public void testSchedulesTaskWhenStartedIfTorIsActive() {
|
||||
// Starting the monitor should schedule a task
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(eventBus).addListener(monitor);
|
||||
oneOf(pluginManager).getPlugin(ID);
|
||||
will(returnValue(plugin));
|
||||
oneOf(plugin).getState();
|
||||
will(returnValue(ACTIVE));
|
||||
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
|
||||
with(ioExecutor), with(REACHABILITY_PERIOD_MS),
|
||||
with(MILLISECONDS));
|
||||
will(returnValue(scheduledTask));
|
||||
}});
|
||||
|
||||
monitor.start();
|
||||
|
||||
// If Tor has only just become active and the TransportActiveEvent
|
||||
// arrives after the task has already been scheduled, a second task
|
||||
// should not be scheduled
|
||||
monitor.eventOccurred(new TransportActiveEvent(ID));
|
||||
|
||||
// Destroying the monitor should cancel the task
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(eventBus).removeListener(monitor);
|
||||
oneOf(scheduledTask).cancel();
|
||||
}});
|
||||
|
||||
monitor.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchedulesTaskWhenTorBecomesActive() {
|
||||
// Starting the monitor should not schedule a task as Tor is inactive
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(eventBus).addListener(monitor);
|
||||
oneOf(pluginManager).getPlugin(ID);
|
||||
will(returnValue(plugin));
|
||||
oneOf(plugin).getState();
|
||||
will(returnValue(ENABLING));
|
||||
}});
|
||||
|
||||
monitor.start();
|
||||
|
||||
// When Tor becomes active, a task should be scheduled
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
|
||||
with(ioExecutor), with(REACHABILITY_PERIOD_MS),
|
||||
with(MILLISECONDS));
|
||||
will(returnValue(scheduledTask));
|
||||
}});
|
||||
|
||||
monitor.eventOccurred(new TransportActiveEvent(ID));
|
||||
|
||||
// Destroying the monitor should cancel the task
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(eventBus).removeListener(monitor);
|
||||
oneOf(scheduledTask).cancel();
|
||||
}});
|
||||
|
||||
monitor.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelsTaskWhenTorBecomesInactive() {
|
||||
// Starting the monitor should schedule a task
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(eventBus).addListener(monitor);
|
||||
oneOf(pluginManager).getPlugin(ID);
|
||||
will(returnValue(plugin));
|
||||
oneOf(plugin).getState();
|
||||
will(returnValue(ACTIVE));
|
||||
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
|
||||
with(ioExecutor), with(REACHABILITY_PERIOD_MS),
|
||||
with(MILLISECONDS));
|
||||
will(returnValue(scheduledTask));
|
||||
}});
|
||||
|
||||
monitor.start();
|
||||
|
||||
// When Tor becomes inactive, the task should be cancelled
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(scheduledTask).cancel();
|
||||
}});
|
||||
|
||||
monitor.eventOccurred(new TransportInactiveEvent(ID));
|
||||
|
||||
// Destroying the monitor should not affect the task, which has
|
||||
// already been cancelled
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(eventBus).removeListener(monitor);
|
||||
}});
|
||||
|
||||
monitor.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testObserverRegisteredBeforeTorBecomesActiveIsCalled() {
|
||||
// Starting the monitor should not schedule a task as Tor is inactive
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(eventBus).addListener(monitor);
|
||||
oneOf(pluginManager).getPlugin(ID);
|
||||
will(returnValue(plugin));
|
||||
oneOf(plugin).getState();
|
||||
will(returnValue(DISABLED));
|
||||
}});
|
||||
|
||||
monitor.start();
|
||||
|
||||
// Register an observer
|
||||
monitor.addOneShotObserver(observer);
|
||||
|
||||
// When Tor becomes active, a task should be scheduled
|
||||
AtomicReference<Runnable> runnable = new AtomicReference<>(null);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
|
||||
with(ioExecutor), with(REACHABILITY_PERIOD_MS),
|
||||
with(MILLISECONDS));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(runnable, Runnable.class, 0),
|
||||
returnValue(scheduledTask)
|
||||
));
|
||||
}});
|
||||
|
||||
monitor.eventOccurred(new TransportActiveEvent(ID));
|
||||
|
||||
// When the task runs, the observer should be called
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(observer).onTorReachable();
|
||||
}});
|
||||
|
||||
runnable.get().run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testObserverRegisteredBeforeTorBecomesReachableIsCalled() {
|
||||
// Starting the monitor should schedule a task
|
||||
AtomicReference<Runnable> runnable = new AtomicReference<>(null);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(eventBus).addListener(monitor);
|
||||
oneOf(pluginManager).getPlugin(ID);
|
||||
will(returnValue(plugin));
|
||||
oneOf(plugin).getState();
|
||||
will(returnValue(ACTIVE));
|
||||
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
|
||||
with(ioExecutor), with(REACHABILITY_PERIOD_MS),
|
||||
with(MILLISECONDS));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(runnable, Runnable.class, 0),
|
||||
returnValue(scheduledTask)
|
||||
));
|
||||
}});
|
||||
|
||||
monitor.start();
|
||||
|
||||
// Register an observer
|
||||
monitor.addOneShotObserver(observer);
|
||||
|
||||
// When the task runs, the observer should be called
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(observer).onTorReachable();
|
||||
}});
|
||||
|
||||
runnable.get().run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testObserverRegisteredAfterTorBecomesReachableIsCalled() {
|
||||
// Starting the monitor should schedule a task
|
||||
AtomicReference<Runnable> runnable = new AtomicReference<>(null);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(eventBus).addListener(monitor);
|
||||
oneOf(pluginManager).getPlugin(ID);
|
||||
will(returnValue(plugin));
|
||||
oneOf(plugin).getState();
|
||||
will(returnValue(ACTIVE));
|
||||
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
|
||||
with(ioExecutor), with(REACHABILITY_PERIOD_MS),
|
||||
with(MILLISECONDS));
|
||||
will(new DoAllAction(
|
||||
new CaptureArgumentAction<>(runnable, Runnable.class, 0),
|
||||
returnValue(scheduledTask)
|
||||
));
|
||||
}});
|
||||
|
||||
monitor.start();
|
||||
|
||||
// When the task runs, no observers have been registered yet
|
||||
runnable.get().run();
|
||||
|
||||
// When an observer is registered, it should be called immediately
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(observer).onTorReachable();
|
||||
}});
|
||||
|
||||
monitor.addOneShotObserver(observer);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import org.briarproject.bramble.test.ImmediateExecutor;
|
||||
import org.briarproject.bramble.test.RunAction;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.imposters.ByteBuddyClassImposteriser;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
@@ -59,22 +58,18 @@ public class PollerImplTest extends BrambleMockTestCase {
|
||||
private final SecureRandom random;
|
||||
|
||||
private final Executor ioExecutor = new ImmediateExecutor();
|
||||
private final Executor wakefulIoExecutor = new ImmediateExecutor();
|
||||
private final TransportId transportId = getTransportId();
|
||||
private final ContactId contactId = getContactId();
|
||||
private final TransportProperties properties = new TransportProperties();
|
||||
private final int pollingInterval = 60 * 1000;
|
||||
private final long now = System.currentTimeMillis();
|
||||
|
||||
private PollerImpl poller;
|
||||
private final PollerImpl poller;
|
||||
|
||||
public PollerImplTest() {
|
||||
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
|
||||
random = context.mock(SecureRandom.class);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
Executor wakefulIoExecutor = new ImmediateExecutor();
|
||||
poller = new PollerImpl(ioExecutor, wakefulIoExecutor, scheduler,
|
||||
connectionManager, connectionRegistry, pluginManager,
|
||||
transportPropertyManager, random, clock);
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule;
|
||||
import org.briarproject.bramble.system.TimeTravelModule;
|
||||
import org.briarproject.bramble.test.TestDatabaseConfigModule;
|
||||
import org.briarproject.bramble.test.TestFeatureFlagModule;
|
||||
import org.briarproject.bramble.test.TestMailboxDirectoryModule;
|
||||
import org.briarproject.bramble.test.TestSecureRandomModule;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
@@ -27,6 +28,7 @@ import dagger.Component;
|
||||
DefaultWakefulIoExecutorModule.class,
|
||||
TestDatabaseConfigModule.class,
|
||||
TestFeatureFlagModule.class,
|
||||
TestMailboxDirectoryModule.class,
|
||||
RemovableDriveIntegrationTestModule.class,
|
||||
RemovableDriveModule.class,
|
||||
TestSecureRandomModule.class,
|
||||
|
||||
@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -46,13 +45,11 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
|
||||
private final Backoff backoff = new TestBackoff();
|
||||
private final ExecutorService ioExecutor = newCachedThreadPool();
|
||||
private final Callback callback = new Callback();
|
||||
|
||||
private Callback callback = null;
|
||||
private LanTcpPlugin plugin = null;
|
||||
private final LanTcpPlugin plugin;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
callback = new Callback();
|
||||
public LanTcpPluginTest() {
|
||||
plugin = new LanTcpPlugin(ioExecutor, ioExecutor, backoff, callback,
|
||||
0, 0, 1000) {
|
||||
@Override
|
||||
|
||||
@@ -7,7 +7,6 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BLOCKED;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BRIDGES;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
|
||||
@@ -15,7 +14,7 @@ import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeTy
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.DEFAULT_BRIDGES;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.MEEK_BRIDGES;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.DPI_BRIDGES;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.NON_DEFAULT_BRIDGES;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -32,18 +31,18 @@ public class CircumventionProviderTest extends BrambleTestCase {
|
||||
Set<String> defaultBridges = new HashSet<>(asList(DEFAULT_BRIDGES));
|
||||
Set<String> nonDefaultBridges =
|
||||
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
|
||||
Set<String> meekBridges = new HashSet<>(asList(MEEK_BRIDGES));
|
||||
Set<String> dpiBridges = new HashSet<>(asList(DPI_BRIDGES));
|
||||
// BRIDGES should be a subset of BLOCKED
|
||||
assertTrue(blocked.containsAll(bridges));
|
||||
// BRIDGES should be the union of the bridge type sets
|
||||
Set<String> union = new HashSet<>(defaultBridges);
|
||||
union.addAll(nonDefaultBridges);
|
||||
union.addAll(meekBridges);
|
||||
union.addAll(dpiBridges);
|
||||
assertEquals(bridges, union);
|
||||
// The bridge type sets should not overlap
|
||||
assertEmptyIntersection(defaultBridges, nonDefaultBridges);
|
||||
assertEmptyIntersection(defaultBridges, meekBridges);
|
||||
assertEmptyIntersection(nonDefaultBridges, meekBridges);
|
||||
assertEmptyIntersection(defaultBridges, dpiBridges);
|
||||
assertEmptyIntersection(nonDefaultBridges, dpiBridges);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -56,8 +55,8 @@ public class CircumventionProviderTest extends BrambleTestCase {
|
||||
assertEquals(asList(NON_DEFAULT_OBFS4, VANILLA),
|
||||
provider.getSuitableBridgeTypes(country));
|
||||
}
|
||||
for (String country : MEEK_BRIDGES) {
|
||||
assertEquals(singletonList(MEEK),
|
||||
for (String country : DPI_BRIDGES) {
|
||||
assertEquals(asList(NON_DEFAULT_OBFS4, MEEK),
|
||||
provider.getSuitableBridgeTypes(country));
|
||||
}
|
||||
assertEquals(asList(DEFAULT_OBFS4, VANILLA),
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||
import org.briarproject.bramble.test.PredicateMatcher;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Random;
|
||||
@@ -93,14 +92,10 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
|
||||
getTransportProperties(3);
|
||||
private final boolean alice = new Random().nextBoolean();
|
||||
|
||||
private RendezvousPollerImpl rendezvousPoller;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
rendezvousPoller = new RendezvousPollerImpl(ioExecutor, scheduler, db,
|
||||
identityManager, transportCrypto, rendezvousCrypto,
|
||||
pluginManager, connectionManager, eventBus, clock);
|
||||
}
|
||||
private final RendezvousPollerImpl rendezvousPoller =
|
||||
new RendezvousPollerImpl(ioExecutor, scheduler, db,
|
||||
identityManager, transportCrypto, rendezvousCrypto,
|
||||
pluginManager, connectionManager, eventBus, clock);
|
||||
|
||||
@Test
|
||||
public void testAddsPendingContactsAndSchedulesPollingAtStartup()
|
||||
|
||||
@@ -6,10 +6,10 @@ import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.DeferredSendHandler;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||
import org.briarproject.bramble.api.sync.Versions;
|
||||
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||
@@ -30,6 +30,7 @@ import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
|
||||
|
||||
@@ -40,8 +41,6 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
|
||||
private final StreamWriter streamWriter = context.mock(StreamWriter.class);
|
||||
private final SyncRecordWriter recordWriter =
|
||||
context.mock(SyncRecordWriter.class);
|
||||
private final DeferredSendHandler deferredSendHandler =
|
||||
context.mock(DeferredSendHandler.class);
|
||||
|
||||
private final ContactId contactId = getContactId();
|
||||
private final TransportId transportId = getTransportId();
|
||||
@@ -53,9 +52,10 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testNothingToSend() throws Exception {
|
||||
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
|
||||
MailboxOutgoingSession session = new MailboxOutgoingSession(db,
|
||||
eventBus, contactId, transportId, MAX_LATENCY,
|
||||
streamWriter, recordWriter, deferredSendHandler,
|
||||
streamWriter, recordWriter, sessionRecord,
|
||||
MAX_FILE_PAYLOAD_BYTES);
|
||||
|
||||
Transaction noAckIdTxn = new Transaction(null, true);
|
||||
@@ -92,13 +92,17 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
session.run();
|
||||
|
||||
assertEquals(emptyList(), sessionRecord.getAckedIds());
|
||||
assertEquals(emptyList(), sessionRecord.getSentIds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSomethingToSend() throws Exception {
|
||||
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
|
||||
MailboxOutgoingSession session = new MailboxOutgoingSession(db,
|
||||
eventBus, contactId, transportId, MAX_LATENCY,
|
||||
streamWriter, recordWriter, deferredSendHandler,
|
||||
streamWriter, recordWriter, sessionRecord,
|
||||
MAX_FILE_PAYLOAD_BYTES);
|
||||
|
||||
Transaction ackIdTxn = new Transaction(null, true);
|
||||
@@ -127,8 +131,6 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
|
||||
oneOf(recordWriter).getBytesWritten();
|
||||
will(returnValue((long) versionRecordBytes));
|
||||
oneOf(recordWriter).writeAck(with(any(Ack.class)));
|
||||
oneOf(deferredSendHandler)
|
||||
.onAckSent(singletonList(message.getId()));
|
||||
// No more messages to ack
|
||||
oneOf(db).transactionWithResult(with(true),
|
||||
withDbCallable(noAckIdTxn));
|
||||
@@ -150,7 +152,6 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
|
||||
MAX_LATENCY, false);
|
||||
will(returnValue(message1));
|
||||
oneOf(recordWriter).writeMessage(message1);
|
||||
oneOf(deferredSendHandler).onMessageSent(message1.getId());
|
||||
// Send the end of stream marker
|
||||
oneOf(streamWriter).sendEndOfStream();
|
||||
// Remove listener
|
||||
@@ -158,6 +159,11 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
session.run();
|
||||
|
||||
assertEquals(singletonList(message.getId()),
|
||||
sessionRecord.getAckedIds());
|
||||
assertEquals(singletonList(message1.getId()),
|
||||
sessionRecord.getSentIds());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -167,9 +173,10 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
|
||||
long capacity = RECORD_HEADER_BYTES + MessageId.LENGTH * MAX_MESSAGE_IDS
|
||||
+ RECORD_HEADER_BYTES + MessageId.LENGTH + MessageId.LENGTH - 1;
|
||||
|
||||
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
|
||||
MailboxOutgoingSession session = new MailboxOutgoingSession(db,
|
||||
eventBus, contactId, transportId, MAX_LATENCY,
|
||||
streamWriter, recordWriter, deferredSendHandler, capacity);
|
||||
streamWriter, recordWriter, sessionRecord, capacity);
|
||||
|
||||
Transaction ackIdTxn1 = new Transaction(null, true);
|
||||
Transaction ackIdTxn2 = new Transaction(null, true);
|
||||
@@ -184,6 +191,9 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
|
||||
}
|
||||
List<MessageId> idsInSecondAck =
|
||||
singletonList(new MessageId(getRandomId()));
|
||||
List<MessageId> allIds = new ArrayList<>(MAX_MESSAGE_IDS + 1);
|
||||
allIds.addAll(idsInFirstAck);
|
||||
allIds.addAll(idsInSecondAck);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
// Add listener
|
||||
@@ -200,7 +210,6 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
|
||||
will(returnValue(idsInFirstAck));
|
||||
// Send the first ack record
|
||||
oneOf(recordWriter).writeAck(with(any(Ack.class)));
|
||||
oneOf(deferredSendHandler).onAckSent(idsInFirstAck);
|
||||
// Calculate remaining capacity for acks
|
||||
oneOf(recordWriter).getBytesWritten();
|
||||
will(returnValue((long) versionRecordBytes + firstAckRecordBytes));
|
||||
@@ -211,7 +220,6 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
|
||||
will(returnValue(idsInSecondAck));
|
||||
// Send the second ack record
|
||||
oneOf(recordWriter).writeAck(with(any(Ack.class)));
|
||||
oneOf(deferredSendHandler).onAckSent(idsInSecondAck);
|
||||
// Not enough capacity left for another ack
|
||||
oneOf(recordWriter).getBytesWritten();
|
||||
will(returnValue((long) versionRecordBytes + firstAckRecordBytes
|
||||
@@ -227,5 +235,8 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
session.run();
|
||||
|
||||
assertEquals(allIds, sessionRecord.getAckedIds());
|
||||
assertEquals(emptyList(), sessionRecord.getSentIds());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import org.briarproject.bramble.api.sync.SyncRecordReader;
|
||||
import org.briarproject.bramble.api.sync.Versions;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -44,12 +43,8 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
|
||||
context.mock(MessageFactory.class);
|
||||
private final RecordReader recordReader = context.mock(RecordReader.class);
|
||||
|
||||
private SyncRecordReader reader;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
reader = new SyncRecordReaderImpl(messageFactory, recordReader);
|
||||
}
|
||||
private final SyncRecordReader reader =
|
||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
||||
|
||||
@Test
|
||||
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
|
||||
|
||||
@@ -20,7 +20,6 @@ import org.briarproject.bramble.api.sync.validation.MessageValidator;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -70,11 +69,10 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
private final MessageContext validResultWithDependencies =
|
||||
new MessageContext(metadata, singletonList(messageId1));
|
||||
|
||||
private ValidationManagerImpl vm;
|
||||
private final ValidationManagerImpl vm =
|
||||
new ValidationManagerImpl(db, dbExecutor, validationExecutor);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
vm = new ValidationManagerImpl(db, dbExecutor, validationExecutor);
|
||||
public ValidationManagerImplTest() {
|
||||
vm.registerMessageValidator(clientId, majorVersion, validator);
|
||||
vm.registerIncomingMessageHook(clientId, majorVersion, hook);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import dagger.Module;
|
||||
DefaultWakefulIoExecutorModule.class,
|
||||
TestDatabaseConfigModule.class,
|
||||
TestFeatureFlagModule.class,
|
||||
TestMailboxDirectoryModule.class,
|
||||
TestPluginConfigModule.class,
|
||||
TestSecureRandomModule.class,
|
||||
TimeTravelModule.class
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.api.Consumer;
|
||||
import org.hamcrest.Description;
|
||||
import org.jmock.api.Action;
|
||||
import org.jmock.api.Invocation;
|
||||
|
||||
public class ConsumeArgumentAction<T> implements Action {
|
||||
|
||||
private final Class<T> capturedClass;
|
||||
private final int index;
|
||||
private final Consumer<T> consumer;
|
||||
|
||||
public ConsumeArgumentAction(Class<T> capturedClass, int index,
|
||||
Consumer<T> consumer) {
|
||||
this.capturedClass = capturedClass;
|
||||
this.index = index;
|
||||
this.consumer = consumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Invocation invocation) {
|
||||
consumer.accept(capturedClass.cast(invocation.getParameter(index)));
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("passes an argument to a consumer");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.api.mailbox.MailboxDirectory;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class TestMailboxDirectoryModule {
|
||||
|
||||
@Provides
|
||||
@MailboxDirectory
|
||||
File provideMailboxDirectory() {
|
||||
return new File("mailbox");
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import org.hamcrest.Description;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.api.Action;
|
||||
import org.jmock.api.Invocation;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -72,13 +71,9 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
private final SecretKey rootKey = getSecretKey();
|
||||
private final Random random = new Random();
|
||||
|
||||
private TransportKeyManager transportKeyManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
transportKeyManager = new TransportKeyManagerImpl(db, transportCrypto,
|
||||
dbExecutor, scheduler, clock, transportId, maxLatency);
|
||||
}
|
||||
private final TransportKeyManager transportKeyManager =
|
||||
new TransportKeyManagerImpl(db, transportCrypto, dbExecutor,
|
||||
scheduler, clock, transportId, maxLatency);
|
||||
|
||||
@Test
|
||||
public void testKeysAreUpdatedAtStartup() throws Exception {
|
||||
|
||||
@@ -18,7 +18,9 @@ dependencies {
|
||||
implementation "net.java.dev.jna:jna:$jna_version"
|
||||
implementation "net.java.dev.jna:jna-platform:$jna_version"
|
||||
tor "org.briarproject:tor-linux:$tor_version"
|
||||
tor "org.briarproject:tor-windows:$tor_version"
|
||||
tor "org.briarproject:obfs4proxy-linux:$obfs4proxy_version"
|
||||
tor "org.briarproject:obfs4proxy-windows:$obfs4proxy_version"
|
||||
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.network.NetworkManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.PluginException;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
|
||||
@NotNullByDefault
|
||||
class WindowsTorPlugin extends JavaTorPlugin {
|
||||
|
||||
WindowsTorPlugin(Executor ioExecutor,
|
||||
Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
SocketFactory torSocketFactory,
|
||||
Clock clock,
|
||||
ResourceProvider resourceProvider,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto,
|
||||
PluginCallback callback,
|
||||
String architecture,
|
||||
long maxLatency,
|
||||
int maxIdleTime,
|
||||
File torDirectory,
|
||||
int torSocksPort,
|
||||
int torControlPort) {
|
||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
||||
torSocketFactory, clock, resourceProvider,
|
||||
circumventionProvider, batteryManager, backoff,
|
||||
torRendezvousCrypto, callback, architecture,
|
||||
maxLatency, maxIdleTime, torDirectory, torSocksPort,
|
||||
torControlPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getProcessId() {
|
||||
return Kernel32.INSTANCE.GetCurrentProcessId();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void waitForTorToStart(Process torProcess)
|
||||
throws InterruptedException, PluginException {
|
||||
// On Windows the RunAsDaemon option has no effect, so Tor won't detach.
|
||||
// Wait for the control port to be opened, then continue to read its
|
||||
// stdout and stderr in a background thread until it exits.
|
||||
BlockingQueue<Boolean> success = new ArrayBlockingQueue<>(1);
|
||||
ioExecutor.execute(() -> {
|
||||
boolean started = false;
|
||||
// Read the process's stdout (and redirected stderr)
|
||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||
// Log the first line of stdout (contains Tor and library versions)
|
||||
if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
|
||||
// Startup has succeeded when the control port is open
|
||||
while (stdout.hasNextLine()) {
|
||||
String line = stdout.nextLine();
|
||||
if (!started && line.contains("Opened Control listener")) {
|
||||
success.add(true);
|
||||
started = true;
|
||||
}
|
||||
}
|
||||
stdout.close();
|
||||
// If the control port wasn't opened, startup has failed
|
||||
if (!started) success.add(false);
|
||||
// Wait for the process to exit
|
||||
try {
|
||||
int exit = torProcess.waitFor();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Tor exited with value " + exit);
|
||||
} catch (InterruptedException e1) {
|
||||
LOG.warning("Interrupted while waiting for Tor to exit");
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
});
|
||||
// Wait for the startup result
|
||||
if (!success.take()) throw new PluginException();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.network.NetworkManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.TorControlPort;
|
||||
import org.briarproject.bramble.api.plugin.TorDirectory;
|
||||
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static org.briarproject.bramble.util.OsUtils.isWindows;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class WindowsTorPluginFactory extends TorPluginFactory {
|
||||
|
||||
@Inject
|
||||
WindowsTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
EventBus eventBus,
|
||||
SocketFactory torSocketFactory,
|
||||
BackoffFactory backoffFactory,
|
||||
ResourceProvider resourceProvider,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
Clock clock,
|
||||
CryptoComponent crypto,
|
||||
@TorDirectory File torDirectory,
|
||||
@TorSocksPort int torSocksPort,
|
||||
@TorControlPort int torControlPort) {
|
||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
||||
eventBus, torSocketFactory, backoffFactory, resourceProvider,
|
||||
circumventionProvider, batteryManager, clock, crypto,
|
||||
torDirectory, torSocksPort, torControlPort);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
String getArchitectureForTorBinary() {
|
||||
if (!isWindows()) return null;
|
||||
String arch = System.getProperty("os.arch");
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("System's os.arch is " + arch);
|
||||
}
|
||||
if (arch.equals("amd64")) return "windows-x86_64";
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
TorPlugin createPluginInstance(Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
||||
String architecture) {
|
||||
return new WindowsTorPlugin(ioExecutor, wakefulIoExecutor,
|
||||
networkManager, locationUtils, torSocketFactory, clock,
|
||||
resourceProvider, circumventionProvider, batteryManager,
|
||||
backoff, torRendezvousCrypto, callback, architecture,
|
||||
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
|
||||
torControlPort);
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,7 @@ public class DesktopSecureRandomModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
SecureRandomProvider provideSecureRandomProvider() {
|
||||
if (isLinux() || isMac())
|
||||
return new UnixSecureRandomProvider();
|
||||
// TODO: Create a secure random provider for Windows
|
||||
throw new UnsupportedOperationException();
|
||||
if (isLinux() || isMac()) return new UnixSecureRandomProvider();
|
||||
return () -> null; // Use system default
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -25,12 +24,8 @@ public class ModemPluginTest extends BrambleMockTestCase {
|
||||
private final PluginCallback callback = context.mock(PluginCallback.class);
|
||||
private final Modem modem = context.mock(Modem.class);
|
||||
|
||||
private ModemPlugin plugin;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
plugin = new ModemPlugin(modemFactory, serialPortList, callback, 0);
|
||||
}
|
||||
private final ModemPlugin plugin =
|
||||
new ModemPlugin(modemFactory, serialPortList, callback, 0);
|
||||
|
||||
@Test
|
||||
public void testModemCreation() throws Exception {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user