mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-14 03:39:05 +01:00
Compare commits
157 Commits
1528-log-t
...
beta-1.4.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2aa39e43ef | ||
|
|
efb294de53 | ||
|
|
99755619c5 | ||
|
|
6d26db3d66 | ||
|
|
51301968a5 | ||
|
|
feb1c1b655 | ||
|
|
15d29f6189 | ||
|
|
217a6dbf1c | ||
|
|
dfcd626081 | ||
|
|
347895f6b2 | ||
|
|
7a6d075984 | ||
|
|
68ab3b0e97 | ||
|
|
16fc4f4527 | ||
|
|
8657216345 | ||
|
|
42e2926d61 | ||
|
|
a261b8e739 | ||
|
|
1699d6b5f8 | ||
|
|
848872a803 | ||
|
|
04ed3a652a | ||
|
|
d20457f338 | ||
|
|
ab29aacce0 | ||
|
|
46bb2b8ec2 | ||
|
|
6b6880c1ff | ||
|
|
5defd500ae | ||
|
|
37ff06d192 | ||
|
|
85aa21ebf6 | ||
|
|
e448699895 | ||
|
|
200f83bcfe | ||
|
|
89cce89650 | ||
|
|
8982964fbf | ||
|
|
f3a3fa0ea8 | ||
|
|
0865a06ac8 | ||
|
|
f2738c8bc4 | ||
|
|
1321f8775e | ||
|
|
9764aba47d | ||
|
|
913e5da2f5 | ||
|
|
f2ce7a386b | ||
|
|
7607b65e82 | ||
|
|
c13c2d62f5 | ||
|
|
8ea7204cf6 | ||
|
|
6ec382cfc4 | ||
|
|
ad0b28a684 | ||
|
|
0ae94e9579 | ||
|
|
57bd5789d4 | ||
|
|
f7dde1250c | ||
|
|
13d96651b4 | ||
|
|
65029982ce | ||
|
|
380921ce25 | ||
|
|
87ee8cd653 | ||
|
|
d4810a6f71 | ||
|
|
aa56aba1a5 | ||
|
|
35438dbac1 | ||
|
|
543b1178a1 | ||
|
|
7f1071f5cd | ||
|
|
e8c694fe00 | ||
|
|
b58b0c74a9 | ||
|
|
9e5029917e | ||
|
|
12ca74f86a | ||
|
|
622683f45e | ||
|
|
a5563ead28 | ||
|
|
e15f49fde7 | ||
|
|
e66f92f27e | ||
|
|
44acda2045 | ||
|
|
afd92dd916 | ||
|
|
2a969f8e0b | ||
|
|
ddc6606ccf | ||
|
|
1531a24b2d | ||
|
|
2298818af5 | ||
|
|
a19a4f36c6 | ||
|
|
6765de992d | ||
|
|
0ae5361281 | ||
|
|
d8e26eebbe | ||
|
|
692e353046 | ||
|
|
b9ba7aded5 | ||
|
|
4bca9decc1 | ||
|
|
7bbe9068bb | ||
|
|
63060679a3 | ||
|
|
ddb759dbb8 | ||
|
|
592daf9c20 | ||
|
|
3922270db1 | ||
|
|
feb8854678 | ||
|
|
4ba4e41e69 | ||
|
|
1f699238a9 | ||
|
|
b8e91a12e8 | ||
|
|
06eb01ab0a | ||
|
|
d82509f3ce | ||
|
|
b01c306500 | ||
|
|
61e7635b9f | ||
|
|
f2f356cbd4 | ||
|
|
28f3ab1310 | ||
|
|
1af52b21d5 | ||
|
|
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 | ||
|
|
b24a18b231 | ||
|
|
e2a63ee361 | ||
|
|
ff9f706670 | ||
|
|
10ab60569b | ||
|
|
d77d1d67aa | ||
|
|
924425522a | ||
|
|
356e0ee07b | ||
|
|
8e83743dd7 | ||
|
|
61658655ff | ||
|
|
40086ffde2 | ||
|
|
1551142e98 | ||
|
|
1c6fb6491a | ||
|
|
cfd4e85e77 | ||
|
|
4d6abfabf7 | ||
|
|
a38933df66 | ||
|
|
6a91d18003 | ||
|
|
e481a02126 | ||
|
|
825dff27fc | ||
|
|
de3a87fff5 | ||
|
|
85d1addd04 | ||
|
|
4993873ae2 | ||
|
|
02b805ce42 | ||
|
|
1a6ba16a59 | ||
|
|
654a05df8a | ||
|
|
ffe1876337 | ||
|
|
98963955b1 | ||
|
|
d83efce002 | ||
|
|
efb1b8c1ad | ||
|
|
3f36db8b3a | ||
|
|
a2f4e70a48 | ||
|
|
01e72eff40 | ||
|
|
6288577daa | ||
|
|
5d363496bd | ||
|
|
713be403eb | ||
|
|
2fd948b81d | ||
|
|
97d11cc602 | ||
|
|
79f41064e4 | ||
|
|
9aacd9d3d8 | ||
|
|
2b4a1cf54b |
10
CONTRIBUTING.md
Normal file
10
CONTRIBUTING.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Folder-Description:
|
||||||
|
===================
|
||||||
|
* `briar-*`: Specifically for the Briar app (Phone/Desktop/Headless)
|
||||||
|
* `bramble-*`: The protocol stack - not necessarily Briar-dependent
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
* `*-api`: public stuff that can be referenced from other packages and modules - mostly interfaces + a few utility classes
|
||||||
|
* `*-core`: implementations of api that are portable across Android/Desktop/Headless
|
||||||
|
* `*-java`: implementations of api that are specific to Desktop & Headless
|
||||||
@@ -15,8 +15,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 10408
|
versionCode 10411
|
||||||
versionName "1.4.8"
|
versionName "1.4.11"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|||||||
@@ -18,3 +18,7 @@
|
|||||||
-dontnote com.google.common.**
|
-dontnote com.google.common.**
|
||||||
|
|
||||||
-dontwarn com.fasterxml.jackson.databind.ext.Java7SupportImpl
|
-dontwarn com.fasterxml.jackson.databind.ext.Java7SupportImpl
|
||||||
|
|
||||||
|
# Keep all Jackson-serialisable classes and their members
|
||||||
|
-keep interface com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||||
|
-keep @com.fasterxml.jackson.databind.annotation.JsonSerialize class * { *; }
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
|||||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
|
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface ConnectionManager {
|
public interface ConnectionManager {
|
||||||
@@ -45,6 +46,14 @@ public interface ConnectionManager {
|
|||||||
void manageOutgoingConnection(ContactId c, TransportId t,
|
void manageOutgoingConnection(ContactId c, TransportId t,
|
||||||
TransportConnectionWriter w);
|
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.
|
* Manages an outgoing connection to a contact over a duplex transport.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -126,16 +126,11 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
TransportKeys k) throws DbException;
|
TransportKeys k) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if there are any acks or messages to send to the given
|
* Returns true if there are any acks to send to the given contact.
|
||||||
* contact over a transport with the given maximum latency.
|
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*
|
|
||||||
* @param eager True if messages that are not yet due for retransmission
|
|
||||||
* should be included
|
|
||||||
*/
|
*/
|
||||||
boolean containsAnythingToSend(Transaction txn, ContactId c,
|
boolean containsAcksToSend(Transaction txn, ContactId c) throws DbException;
|
||||||
long maxLatency, boolean eager) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given contact for the given
|
* 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;
|
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.
|
* Returns true if the database contains the given pending contact.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -342,13 +349,13 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
Metadata query) throws DbException;
|
Metadata query) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of some messages received from the given contact that
|
* Returns the IDs of all messages received from the given contact that
|
||||||
* need to be acknowledged, up to the given number of messages.
|
* need to be acknowledged.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getMessagesToAck(Transaction txn, ContactId c,
|
Collection<MessageId> getMessagesToAck(Transaction txn, ContactId c)
|
||||||
int maxMessages) throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of some messages that are eligible to be sent to the
|
* Returns the IDs of some messages that are eligible to be sent to the
|
||||||
@@ -485,6 +492,8 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
* Returns the message with the given ID for transmission to the given
|
* Returns the message with the given ID for transmission to the given
|
||||||
* contact over a transport with the given maximum latency. Returns null
|
* contact over a transport with the given maximum latency. Returns null
|
||||||
* if the message is no longer visible to the contact.
|
* if the message is no longer visible to the contact.
|
||||||
|
* <p/>
|
||||||
|
* Read-only if {@code markAsSent} is false.
|
||||||
*
|
*
|
||||||
* @param markAsSent True if the message should be marked as sent.
|
* @param markAsSent True if the message should be marked as sent.
|
||||||
* If false it can be marked as sent by calling
|
* If false it can be marked as sent by calling
|
||||||
@@ -534,15 +543,18 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
*/
|
*/
|
||||||
long getNextCleanupDeadline(Transaction txn) throws DbException;
|
long getNextCleanupDeadline(Transaction txn) throws DbException;
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
* 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
|
* message is due to be sent to the given contact over a transport with
|
||||||
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE if
|
* the given latency.
|
||||||
* no messages are scheduled to be sent.
|
* <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/>
|
* <p/>
|
||||||
* Read-only.
|
* 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.
|
* Returns the pending contact with the given ID.
|
||||||
|
|||||||
@@ -37,8 +37,14 @@ public interface LifecycleManager {
|
|||||||
*/
|
*/
|
||||||
enum LifecycleState {
|
enum LifecycleState {
|
||||||
|
|
||||||
STARTING, MIGRATING_DATABASE, COMPACTING_DATABASE, STARTING_SERVICES,
|
CREATED,
|
||||||
RUNNING, STOPPING;
|
STARTING,
|
||||||
|
MIGRATING_DATABASE,
|
||||||
|
COMPACTING_DATABASE,
|
||||||
|
STARTING_SERVICES,
|
||||||
|
RUNNING,
|
||||||
|
STOPPING,
|
||||||
|
STOPPED;
|
||||||
|
|
||||||
public boolean isAfter(LifecycleState state) {
|
public boolean isAfter(LifecycleState state) {
|
||||||
return ordinal() > state.ordinal();
|
return ordinal() > state.ordinal();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
|
import static java.util.concurrent.TimeUnit.DAYS;
|
||||||
import static java.util.concurrent.TimeUnit.HOURS;
|
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_FRAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||||
@@ -65,4 +66,8 @@ public interface MailboxConstants {
|
|||||||
*/
|
*/
|
||||||
long PROBLEM_MS_SINCE_LAST_SUCCESS = HOURS.toMillis(1);
|
long PROBLEM_MS_SINCE_LAST_SUCCESS = HOURS.toMillis(1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum latency of the mailbox transport in milliseconds.
|
||||||
|
*/
|
||||||
|
long MAX_LATENCY = DAYS.toMillis(14);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.briarproject.bramble.api.mailbox;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonValue;
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.UniqueId;
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ public abstract class MailboxId extends UniqueId {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return fromHexString(token);
|
return fromHexString(token);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (FormatException e) {
|
||||||
throw new InvalidMailboxIdException();
|
throw new InvalidMailboxIdException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.bramble.api.mailbox;
|
package org.briarproject.bramble.api.mailbox;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NullSafety;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -11,7 +12,7 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class MailboxProperties {
|
public class MailboxProperties {
|
||||||
|
|
||||||
private final String baseUrl;
|
private final String onion;
|
||||||
private final MailboxAuthToken authToken;
|
private final MailboxAuthToken authToken;
|
||||||
private final boolean owner;
|
private final boolean owner;
|
||||||
private final List<MailboxVersion> serverSupports;
|
private final List<MailboxVersion> serverSupports;
|
||||||
@@ -23,9 +24,9 @@ public class MailboxProperties {
|
|||||||
/**
|
/**
|
||||||
* Constructor for properties used by the mailbox's owner.
|
* Constructor for properties used by the mailbox's owner.
|
||||||
*/
|
*/
|
||||||
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
|
public MailboxProperties(String onion, MailboxAuthToken authToken,
|
||||||
List<MailboxVersion> serverSupports) {
|
List<MailboxVersion> serverSupports) {
|
||||||
this.baseUrl = baseUrl;
|
this.onion = onion;
|
||||||
this.authToken = authToken;
|
this.authToken = authToken;
|
||||||
this.owner = true;
|
this.owner = true;
|
||||||
this.serverSupports = serverSupports;
|
this.serverSupports = serverSupports;
|
||||||
@@ -36,10 +37,10 @@ public class MailboxProperties {
|
|||||||
/**
|
/**
|
||||||
* Constructor for properties used by a contact of the mailbox's owner.
|
* Constructor for properties used by a contact of the mailbox's owner.
|
||||||
*/
|
*/
|
||||||
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
|
public MailboxProperties(String onion, MailboxAuthToken authToken,
|
||||||
List<MailboxVersion> serverSupports, MailboxFolderId inboxId,
|
List<MailboxVersion> serverSupports, MailboxFolderId inboxId,
|
||||||
MailboxFolderId outboxId) {
|
MailboxFolderId outboxId) {
|
||||||
this.baseUrl = baseUrl;
|
this.onion = onion;
|
||||||
this.authToken = authToken;
|
this.authToken = authToken;
|
||||||
this.owner = false;
|
this.owner = false;
|
||||||
this.serverSupports = serverSupports;
|
this.serverSupports = serverSupports;
|
||||||
@@ -47,13 +48,11 @@ public class MailboxProperties {
|
|||||||
this.outboxId = outboxId;
|
this.outboxId = outboxId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBaseUrl() {
|
/**
|
||||||
return baseUrl;
|
* Returns the onion address of the mailbox, excluding the .onion suffix.
|
||||||
}
|
*/
|
||||||
|
|
||||||
public String getOnion() {
|
public String getOnion() {
|
||||||
return baseUrl.replaceFirst("^http://", "")
|
return onion;
|
||||||
.replaceFirst("\\.onion$", "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MailboxAuthToken getAuthToken() {
|
public MailboxAuthToken getAuthToken() {
|
||||||
@@ -77,4 +76,23 @@ public class MailboxProperties {
|
|||||||
public MailboxFolderId getOutboxId() {
|
public MailboxFolderId getOutboxId() {
|
||||||
return outboxId;
|
return outboxId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o instanceof MailboxProperties) {
|
||||||
|
MailboxProperties m = (MailboxProperties) o;
|
||||||
|
return owner == m.owner &&
|
||||||
|
onion.equals(m.onion) &&
|
||||||
|
authToken.equals(m.authToken) &&
|
||||||
|
NullSafety.equals(inboxId, m.inboxId) &&
|
||||||
|
NullSafety.equals(outboxId, m.outboxId) &&
|
||||||
|
serverSupports.equals(m.serverSupports);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return authToken.hashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ public interface MailboxSettingsManager {
|
|||||||
|
|
||||||
MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException;
|
MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException;
|
||||||
|
|
||||||
void recordSuccessfulConnection(Transaction txn, long now)
|
void recordSuccessfulConnection(Transaction txn, long now,
|
||||||
throws DbException;
|
List<MailboxVersion> versions) throws DbException;
|
||||||
|
|
||||||
void recordFailedConnectionAttempt(Transaction txn, long now)
|
void recordFailedConnectionAttempt(Transaction txn, long now)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
@@ -49,10 +49,8 @@ public interface MailboxSettingsManager {
|
|||||||
* Called when Briar is paired with a mailbox
|
* Called when Briar is paired with a mailbox
|
||||||
*
|
*
|
||||||
* @param txn A read-write transaction
|
* @param txn A read-write transaction
|
||||||
* @param ownOnion Our new mailbox's onion (56 base32 chars)
|
|
||||||
*/
|
*/
|
||||||
void mailboxPaired(Transaction txn, String ownOnion,
|
void mailboxPaired(Transaction txn, MailboxProperties p)
|
||||||
List<MailboxVersion> serverSupports)
|
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -67,6 +67,13 @@ public class MailboxStatus {
|
|||||||
return attemptsSinceSuccess;
|
return attemptsSinceSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mailbox's supported API versions.
|
||||||
|
*/
|
||||||
|
public List<MailboxVersion> getServerSupports() {
|
||||||
|
return serverSupports;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if this status indicates a problem with the mailbox.
|
* @return true if this status indicates a problem with the mailbox.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -29,19 +29,35 @@ public interface MailboxUpdateManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of properties required for an update message with a mailbox.
|
* The number of properties required for an update message with a mailbox.
|
||||||
|
* <p>
|
||||||
|
* The required properties are {@link #PROP_KEY_ONION},
|
||||||
|
* {@link #PROP_KEY_AUTHTOKEN}, {@link #PROP_KEY_INBOXID} and
|
||||||
|
* {@link #PROP_KEY_OUTBOXID}.
|
||||||
*/
|
*/
|
||||||
int PROP_COUNT = 4;
|
int PROP_COUNT = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The required properties of an update message with a mailbox.
|
* The onion address of the mailbox, excluding the .onion suffix.
|
||||||
*/
|
*/
|
||||||
String PROP_KEY_ONION = "onion";
|
String PROP_KEY_ONION = "onion";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bearer token for accessing the mailbox (64 hex digits).
|
||||||
|
*/
|
||||||
String PROP_KEY_AUTHTOKEN = "authToken";
|
String PROP_KEY_AUTHTOKEN = "authToken";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A folder ID for downloading messages (64 hex digits).
|
||||||
|
*/
|
||||||
String PROP_KEY_INBOXID = "inboxId";
|
String PROP_KEY_INBOXID = "inboxId";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A folder ID for uploading messages (64 hex digits).
|
||||||
|
*/
|
||||||
String PROP_KEY_OUTBOXID = "outboxId";
|
String PROP_KEY_OUTBOXID = "outboxId";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Length of the Onion property.
|
* Length of the {@link #PROP_KEY_ONION} property.
|
||||||
*/
|
*/
|
||||||
int PROP_ONION_LENGTH = 56;
|
int PROP_ONION_LENGTH = 56;
|
||||||
|
|
||||||
@@ -63,9 +79,26 @@ public interface MailboxUpdateManager {
|
|||||||
*/
|
*/
|
||||||
String GROUP_KEY_SENT_CLIENT_SUPPORTS = "sentClientSupports";
|
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)
|
MailboxUpdate getLocalUpdate(Transaction txn, ContactId c)
|
||||||
throws DbException;
|
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
|
@Nullable
|
||||||
MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
|
MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.briarproject.bramble.api.mailbox.event;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that is broadcast when a mailbox is paired.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class MailboxPairedEvent extends Event {
|
||||||
|
|
||||||
|
private final MailboxProperties properties;
|
||||||
|
private final Map<ContactId, MailboxUpdateWithMailbox> localUpdates;
|
||||||
|
|
||||||
|
public MailboxPairedEvent(MailboxProperties properties,
|
||||||
|
Map<ContactId, MailboxUpdateWithMailbox> localUpdates) {
|
||||||
|
this.properties = properties;
|
||||||
|
this.localUpdates = localUpdates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MailboxProperties getProperties() {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<ContactId, MailboxUpdateWithMailbox> getLocalUpdates() {
|
||||||
|
return localUpdates;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package org.briarproject.bramble.api.mailbox.event;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that is broadcast when a mailbox is unpaired.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class MailboxUnpairedEvent extends Event {
|
||||||
|
|
||||||
|
private final Map<ContactId, MailboxUpdate> localUpdates;
|
||||||
|
|
||||||
|
public MailboxUnpairedEvent(Map<ContactId, MailboxUpdate> localUpdates) {
|
||||||
|
this.localUpdates = localUpdates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<ContactId, MailboxUpdate> getLocalUpdates() {
|
||||||
|
return localUpdates;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package org.briarproject.bramble.api.mailbox.event;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that is broadcast when a mailbox update is sent to a contact.
|
||||||
|
* <p>
|
||||||
|
* Note that this event is not broadcast when a mailbox is paired or
|
||||||
|
* unpaired, although updates are sent to all contacts in those situations.
|
||||||
|
*
|
||||||
|
* @see MailboxPairedEvent
|
||||||
|
* @see MailboxUnpairedEvent
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class MailboxUpdateSentEvent extends Event {
|
||||||
|
|
||||||
|
private final ContactId contactId;
|
||||||
|
private final MailboxUpdate mailboxUpdate;
|
||||||
|
|
||||||
|
public MailboxUpdateSentEvent(ContactId contactId,
|
||||||
|
MailboxUpdate mailboxUpdate) {
|
||||||
|
this.contactId = contactId;
|
||||||
|
this.mailboxUpdate = mailboxUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactId getContactId() {
|
||||||
|
return contactId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MailboxUpdate getMailboxUpdate() {
|
||||||
|
return mailboxUpdate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
@NotNullByDefault
|
||||||
public interface SyncSessionFactory {
|
public interface SyncSessionFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a session for receiving data from a contact.
|
||||||
|
*/
|
||||||
SyncSession createIncomingSession(ContactId c, InputStream in,
|
SyncSession createIncomingSession(ContactId c, InputStream in,
|
||||||
PriorityHandler handler);
|
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,
|
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||||
long maxLatency, boolean eager, StreamWriter streamWriter);
|
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,
|
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
||||||
long maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
long maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
||||||
@Nullable Priority priority);
|
@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.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
@@ -15,12 +16,19 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class GroupVisibilityUpdatedEvent extends Event {
|
public class GroupVisibilityUpdatedEvent extends Event {
|
||||||
|
|
||||||
|
private final Visibility visibility;
|
||||||
private final Collection<ContactId> affected;
|
private final Collection<ContactId> affected;
|
||||||
|
|
||||||
public GroupVisibilityUpdatedEvent(Collection<ContactId> affected) {
|
public GroupVisibilityUpdatedEvent(Visibility visibility,
|
||||||
|
Collection<ContactId> affected) {
|
||||||
|
this.visibility = visibility;
|
||||||
this.affected = affected;
|
this.affected = affected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Visibility getVisibility() {
|
||||||
|
return visibility;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the contacts affected by the update.
|
* 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))
|
if (!f.delete() && LOG.isLoggable(WARNING))
|
||||||
LOG.warning("Could not delete " + f.getAbsolutePath());
|
LOG.warning("Could not delete " + f.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.util;
|
package org.briarproject.bramble.util;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
@@ -95,10 +96,10 @@ public class StringUtils {
|
|||||||
/**
|
/**
|
||||||
* Converts the given hex string to a byte array.
|
* Converts the given hex string to a byte array.
|
||||||
*/
|
*/
|
||||||
public static byte[] fromHexString(String hex) {
|
public static byte[] fromHexString(String hex) throws FormatException {
|
||||||
int len = hex.length();
|
int len = hex.length();
|
||||||
if (len % 2 != 0)
|
if (len % 2 != 0)
|
||||||
throw new IllegalArgumentException("Not a hex string");
|
throw new FormatException();
|
||||||
byte[] bytes = new byte[len / 2];
|
byte[] bytes = new byte[len / 2];
|
||||||
for (int i = 0, j = 0; i < len; i += 2, j++) {
|
for (int i = 0, j = 0; i < len; i += 2, j++) {
|
||||||
int high = hexDigitToInt(hex.charAt(i));
|
int high = hexDigitToInt(hex.charAt(i));
|
||||||
@@ -108,11 +109,11 @@ public class StringUtils {
|
|||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int hexDigitToInt(char c) {
|
private static int hexDigitToInt(char c) throws FormatException {
|
||||||
if (c >= '0' && c <= '9') return c - '0';
|
if (c >= '0' && c <= '9') return c - '0';
|
||||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||||
throw new IllegalArgumentException("Not a hex digit: " + c);
|
throw new FormatException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String trim(String s) {
|
public static String trim(String s) {
|
||||||
@@ -130,13 +131,13 @@ public class StringUtils {
|
|||||||
return MAC.matcher(mac).matches();
|
return MAC.matcher(mac).matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] macToBytes(String mac) {
|
public static byte[] macToBytes(String mac) throws FormatException {
|
||||||
if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException();
|
if (!MAC.matcher(mac).matches()) throw new FormatException();
|
||||||
return fromHexString(mac.replaceAll(":", ""));
|
return fromHexString(mac.replaceAll(":", ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String macToString(byte[] mac) {
|
public static String macToString(byte[] mac) throws FormatException {
|
||||||
if (mac.length != 6) throw new IllegalArgumentException();
|
if (mac.length != 6) throw new FormatException();
|
||||||
StringBuilder s = new StringBuilder();
|
StringBuilder s = new StringBuilder();
|
||||||
for (byte b : mac) {
|
for (byte b : mac) {
|
||||||
if (s.length() > 0) s.append(':');
|
if (s.length() > 0) s.append(':');
|
||||||
|
|||||||
@@ -230,14 +230,14 @@ public class TestUtils {
|
|||||||
|
|
||||||
public static MailboxProperties getMailboxProperties(boolean owner,
|
public static MailboxProperties getMailboxProperties(boolean owner,
|
||||||
List<MailboxVersion> serverSupports) {
|
List<MailboxVersion> serverSupports) {
|
||||||
String baseUrl = "http://" + getRandomString(56) + ".onion"; // TODO
|
String onion = getRandomString(56);
|
||||||
MailboxAuthToken authToken = new MailboxAuthToken(getRandomId());
|
MailboxAuthToken authToken = new MailboxAuthToken(getRandomId());
|
||||||
if (owner) {
|
if (owner) {
|
||||||
return new MailboxProperties(baseUrl, authToken, serverSupports);
|
return new MailboxProperties(onion, authToken, serverSupports);
|
||||||
}
|
}
|
||||||
MailboxFolderId inboxId = new MailboxFolderId(getRandomId());
|
MailboxFolderId inboxId = new MailboxFolderId(getRandomId());
|
||||||
MailboxFolderId outboxId = new MailboxFolderId(getRandomId());
|
MailboxFolderId outboxId = new MailboxFolderId(getRandomId());
|
||||||
return new MailboxProperties(baseUrl, authToken, serverSupports,
|
return new MailboxProperties(onion, authToken, serverSupports,
|
||||||
inboxId, outboxId);
|
inboxId, outboxId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,6 +338,17 @@ public class TestUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <E extends Event> E getEvent(Transaction txn,
|
||||||
|
Class<E> eventClass) {
|
||||||
|
for (CommitAction action : txn.getActions()) {
|
||||||
|
if (action instanceof EventAction) {
|
||||||
|
Event event = ((EventAction) action).getEvent();
|
||||||
|
if (eventClass.isInstance(event)) return eventClass.cast(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isCryptoStrengthUnlimited() {
|
public static boolean isCryptoStrengthUnlimited() {
|
||||||
try {
|
try {
|
||||||
return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding")
|
return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding")
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ dependencies {
|
|||||||
implementation 'org.bitlet:weupnp:0.1.4'
|
implementation 'org.bitlet:weupnp:0.1.4'
|
||||||
implementation 'net.i2p.crypto:eddsa:0.2.0'
|
implementation 'net.i2p.crypto:eddsa:0.2.0'
|
||||||
implementation 'org.whispersystems:curve25519-java:0.5.0'
|
implementation 'org.whispersystems:curve25519-java:0.5.0'
|
||||||
implementation 'org.briarproject:jtorctl:0.4'
|
implementation 'org.briarproject:jtorctl:0.5'
|
||||||
|
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.account;
|
package org.briarproject.bramble.account;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.account.AccountManager;
|
import org.briarproject.bramble.api.account.AccountManager;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.DecryptionException;
|
import org.briarproject.bramble.api.crypto.DecryptionException;
|
||||||
@@ -209,7 +210,13 @@ class AccountManagerImpl implements AccountManager {
|
|||||||
LOG.warning("Failed to load encrypted database key");
|
LOG.warning("Failed to load encrypted database key");
|
||||||
throw new DecryptionException(INVALID_CIPHERTEXT);
|
throw new DecryptionException(INVALID_CIPHERTEXT);
|
||||||
}
|
}
|
||||||
byte[] ciphertext = fromHexString(hex);
|
byte[] ciphertext;
|
||||||
|
try {
|
||||||
|
ciphertext = fromHexString(hex);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
LOG.warning("Encrypted database key has invalid format");
|
||||||
|
throw new DecryptionException(INVALID_CIPHERTEXT);
|
||||||
|
}
|
||||||
KeyStrengthener keyStrengthener = databaseConfig.getKeyStrengthener();
|
KeyStrengthener keyStrengthener = databaseConfig.getKeyStrengthener();
|
||||||
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password,
|
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password,
|
||||||
keyStrengthener);
|
keyStrengthener);
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import java.util.Map.Entry;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.Collections.sort;
|
||||||
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
|
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
|
||||||
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
@@ -456,8 +457,7 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
checkLength(inboxId, UniqueId.LENGTH);
|
checkLength(inboxId, UniqueId.LENGTH);
|
||||||
byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID);
|
byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID);
|
||||||
checkLength(outboxId, UniqueId.LENGTH);
|
checkLength(outboxId, UniqueId.LENGTH);
|
||||||
String baseUrl = "http://" + onion + ".onion"; // TODO
|
MailboxProperties props = new MailboxProperties(onion,
|
||||||
MailboxProperties props = new MailboxProperties(baseUrl,
|
|
||||||
new MailboxAuthToken(authToken), serverSupportsList,
|
new MailboxAuthToken(authToken), serverSupportsList,
|
||||||
new MailboxFolderId(inboxId), new MailboxFolderId(outboxId));
|
new MailboxFolderId(inboxId), new MailboxFolderId(outboxId));
|
||||||
return new MailboxUpdateWithMailbox(clientSupportsList, props);
|
return new MailboxUpdateWithMailbox(clientSupportsList, props);
|
||||||
@@ -475,6 +475,8 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
list.add(new MailboxVersion(element.getLong(0).intValue(),
|
list.add(new MailboxVersion(element.getLong(0).intValue(),
|
||||||
element.getLong(1).intValue()));
|
element.getLong(1).intValue()));
|
||||||
}
|
}
|
||||||
|
// Sort the list of versions for easier comparison
|
||||||
|
sort(list);
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
|||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
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.sync.SyncSessionFactory;
|
||||||
import org.briarproject.bramble.api.transport.KeyManager;
|
import org.briarproject.bramble.api.transport.KeyManager;
|
||||||
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
||||||
@@ -100,7 +101,16 @@ class ConnectionManagerImpl implements ConnectionManager {
|
|||||||
TransportConnectionWriter w) {
|
TransportConnectionWriter w) {
|
||||||
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
|
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
|
||||||
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
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
|
@Override
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
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.SyncSession;
|
||||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||||
import org.briarproject.bramble.api.transport.KeyManager;
|
import org.briarproject.bramble.api.transport.KeyManager;
|
||||||
@@ -16,6 +17,8 @@ import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
@@ -26,6 +29,8 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
private final TransportId transportId;
|
private final TransportId transportId;
|
||||||
private final TransportConnectionWriter writer;
|
private final TransportConnectionWriter writer;
|
||||||
|
@Nullable
|
||||||
|
private final OutgoingSessionRecord sessionRecord;
|
||||||
|
|
||||||
OutgoingSimplexSyncConnection(KeyManager keyManager,
|
OutgoingSimplexSyncConnection(KeyManager keyManager,
|
||||||
ConnectionRegistry connectionRegistry,
|
ConnectionRegistry connectionRegistry,
|
||||||
@@ -34,13 +39,15 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
SyncSessionFactory syncSessionFactory,
|
SyncSessionFactory syncSessionFactory,
|
||||||
TransportPropertyManager transportPropertyManager,
|
TransportPropertyManager transportPropertyManager,
|
||||||
ContactId contactId, TransportId transportId,
|
ContactId contactId, TransportId transportId,
|
||||||
TransportConnectionWriter writer) {
|
TransportConnectionWriter writer,
|
||||||
|
@Nullable OutgoingSessionRecord sessionRecord) {
|
||||||
super(keyManager, connectionRegistry, streamReaderFactory,
|
super(keyManager, connectionRegistry, streamReaderFactory,
|
||||||
streamWriterFactory, syncSessionFactory,
|
streamWriterFactory, syncSessionFactory,
|
||||||
transportPropertyManager);
|
transportPropertyManager);
|
||||||
this.contactId = contactId;
|
this.contactId = contactId;
|
||||||
this.transportId = transportId;
|
this.transportId = transportId;
|
||||||
this.writer = writer;
|
this.writer = writer;
|
||||||
|
this.sessionRecord = sessionRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -71,10 +78,16 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
||||||
w.getOutputStream(), ctx);
|
w.getOutputStream(), ctx);
|
||||||
ContactId c = requireNonNull(ctx.getContactId());
|
ContactId c = requireNonNull(ctx.getContactId());
|
||||||
// Use eager retransmission if the transport is lossy and cheap
|
if (sessionRecord == null) {
|
||||||
return syncSessionFactory.createSimplexOutgoingSession(c,
|
// Use eager retransmission if the transport is lossy and cheap
|
||||||
ctx.getTransportId(), w.getMaxLatency(), w.isLossyAndCheap(),
|
return syncSessionFactory.createSimplexOutgoingSession(c,
|
||||||
streamWriter);
|
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;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if there are any acks or messages to send to the given
|
* Returns true if there are any acks to send to the given contact.
|
||||||
* contact over a transport with the given maximum latency.
|
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* 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 containsAcksToSend(T txn, ContactId c) throws DbException;
|
||||||
boolean eager) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given contact for the given
|
* 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;
|
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.
|
* Returns true if the database contains the given pending contact.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -580,13 +587,16 @@ interface Database<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
* 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
|
* message is due to be sent to the given contact over a transport with
|
||||||
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE
|
* the given latency.
|
||||||
* if no messages are scheduled to be sent.
|
* <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/>
|
* <p/>
|
||||||
* Read-only.
|
* 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.
|
* Returns the pending contact with the given ID.
|
||||||
|
|||||||
@@ -342,12 +342,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsAnythingToSend(Transaction transaction, ContactId c,
|
public boolean containsAcksToSend(Transaction transaction, ContactId c)
|
||||||
long maxLatency, boolean eager) throws DbException {
|
throws DbException {
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsContact(txn, c))
|
if (!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
return db.containsAnythingToSend(txn, c, maxLatency, eager);
|
return db.containsAcksToSend(txn, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -373,6 +373,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.containsIdentity(txn, a);
|
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
|
@Override
|
||||||
public boolean containsPendingContact(Transaction transaction,
|
public boolean containsPendingContact(Transaction transaction,
|
||||||
PendingContactId p) throws DbException {
|
PendingContactId p) throws DbException {
|
||||||
@@ -611,11 +620,11 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<MessageId> getMessagesToAck(Transaction transaction,
|
public Collection<MessageId> getMessagesToAck(Transaction transaction,
|
||||||
ContactId c, int maxMessages) throws DbException {
|
ContactId c) throws DbException {
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsContact(txn, c))
|
if (!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
return db.getMessagesToAck(txn, c, maxMessages);
|
return db.getMessagesToAck(txn, c, Integer.MAX_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -737,7 +746,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
public Message getMessageToSend(Transaction transaction, ContactId c,
|
public Message getMessageToSend(Transaction transaction, ContactId c,
|
||||||
MessageId m, long maxLatency, boolean markAsSent)
|
MessageId m, long maxLatency, boolean markAsSent)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
if (markAsSent && transaction.isReadOnly()) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsContact(txn, c))
|
if (!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
@@ -805,10 +816,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getNextSendTime(Transaction transaction, ContactId c)
|
public long getNextSendTime(Transaction transaction, ContactId c,
|
||||||
throws DbException {
|
long maxLatency) throws DbException {
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
return db.getNextSendTime(txn, c);
|
return db.getNextSendTime(txn, c, maxLatency);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1016,7 +1027,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
db.getGroupVisibility(txn, id).keySet();
|
db.getGroupVisibility(txn, id).keySet();
|
||||||
db.removeGroup(txn, id);
|
db.removeGroup(txn, id);
|
||||||
transaction.attach(new GroupRemovedEvent(g));
|
transaction.attach(new GroupRemovedEvent(g));
|
||||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
transaction.attach(new GroupVisibilityUpdatedEvent(INVISIBLE,
|
||||||
|
affected));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1087,11 +1099,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsContact(txn, c))
|
if (!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
List<MessageId> visible = new ArrayList<>(acked.size());
|
db.lowerAckFlag(txn, c, acked);
|
||||||
for (MessageId m : acked) {
|
|
||||||
if (db.containsVisibleMessage(txn, c, m)) visible.add(m);
|
|
||||||
}
|
|
||||||
db.lowerAckFlag(txn, c, visible);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1141,7 +1149,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
|
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
|
||||||
else db.setGroupVisibility(txn, c, g, v == SHARED);
|
else db.setGroupVisibility(txn, c, g, v == SHARED);
|
||||||
List<ContactId> affected = singletonList(c);
|
List<ContactId> affected = singletonList(c);
|
||||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
transaction.attach(new GroupVisibilityUpdatedEvent(v, affected));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1147,8 +1147,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsAnythingToSend(Connection txn, ContactId c,
|
public boolean containsAcksToSend(Connection txn, ContactId c)
|
||||||
long maxLatency, boolean eager) throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
@@ -1160,34 +1160,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
boolean acksToSend = rs.next();
|
boolean acksToSend = rs.next();
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
if (acksToSend) return true;
|
return acksToSend;
|
||||||
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;
|
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs, LOG, WARNING);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps, 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
|
@Override
|
||||||
public boolean containsPendingContact(Connection txn, PendingContactId p)
|
public boolean containsPendingContact(Connection txn, PendingContactId p)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -2477,12 +2490,28 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getNextSendTime(Connection txn, ContactId c)
|
public long getNextSendTime(Connection txn, ContactId c, long maxLatency)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
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 = ?"
|
+ " WHERE contactId = ? AND state = ?"
|
||||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||||
+ " AND deleted = FALSE AND seen = FALSE"
|
+ " AND deleted = FALSE AND seen = FALSE"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import java.util.List;
|
|||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
@@ -29,10 +29,12 @@ import static java.util.logging.Level.INFO;
|
|||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.CREATED;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
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.STARTING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPED;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
|
||||||
@@ -60,12 +62,11 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
private final List<Service> services;
|
private final List<Service> services;
|
||||||
private final List<OpenDatabaseHook> openDatabaseHooks;
|
private final List<OpenDatabaseHook> openDatabaseHooks;
|
||||||
private final List<ExecutorService> executors;
|
private final List<ExecutorService> executors;
|
||||||
private final Semaphore startStopSemaphore = new Semaphore(1);
|
|
||||||
private final CountDownLatch dbLatch = new CountDownLatch(1);
|
private final CountDownLatch dbLatch = new CountDownLatch(1);
|
||||||
private final CountDownLatch startupLatch = new CountDownLatch(1);
|
private final CountDownLatch startupLatch = new CountDownLatch(1);
|
||||||
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
||||||
|
private final AtomicReference<LifecycleState> state =
|
||||||
private volatile LifecycleState state = STARTING;
|
new AtomicReference<>(CREATED);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
|
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
|
||||||
@@ -102,8 +103,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StartResult startServices(SecretKey dbKey) {
|
public StartResult startServices(SecretKey dbKey) {
|
||||||
if (!startStopSemaphore.tryAcquire()) {
|
if (!state.compareAndSet(CREATED, STARTING)) {
|
||||||
LOG.info("Already starting or stopping");
|
LOG.warning("Already running");
|
||||||
return ALREADY_RUNNING;
|
return ALREADY_RUNNING;
|
||||||
}
|
}
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
@@ -135,7 +136,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
});
|
});
|
||||||
|
|
||||||
LOG.info("Starting services");
|
LOG.info("Starting services");
|
||||||
state = STARTING_SERVICES;
|
state.set(STARTING_SERVICES);
|
||||||
dbLatch.countDown();
|
dbLatch.countDown();
|
||||||
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
|
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
|
||||||
|
|
||||||
@@ -148,7 +149,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state = RUNNING;
|
state.set(RUNNING);
|
||||||
startupLatch.countDown();
|
startupLatch.countDown();
|
||||||
eventBus.broadcast(new LifecycleEvent(RUNNING));
|
eventBus.broadcast(new LifecycleEvent(RUNNING));
|
||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
@@ -164,63 +165,58 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
} catch (ServiceException e) {
|
} catch (ServiceException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
return SERVICE_ERROR;
|
return SERVICE_ERROR;
|
||||||
} finally {
|
|
||||||
startStopSemaphore.release();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDatabaseMigration() {
|
public void onDatabaseMigration() {
|
||||||
state = MIGRATING_DATABASE;
|
state.set(MIGRATING_DATABASE);
|
||||||
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
|
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDatabaseCompaction() {
|
public void onDatabaseCompaction() {
|
||||||
state = COMPACTING_DATABASE;
|
state.set(COMPACTING_DATABASE);
|
||||||
eventBus.broadcast(new LifecycleEvent(COMPACTING_DATABASE));
|
eventBus.broadcast(new LifecycleEvent(COMPACTING_DATABASE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stopServices() {
|
public void stopServices() {
|
||||||
try {
|
if (!state.compareAndSet(RUNNING, STOPPING)) {
|
||||||
startStopSemaphore.acquire();
|
LOG.warning("Not running");
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.warning("Interrupted while waiting to stop services");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
LOG.info("Stopping services");
|
||||||
if (state == STOPPING) {
|
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
||||||
LOG.info("Already stopped");
|
for (Service s : services) {
|
||||||
return;
|
try {
|
||||||
}
|
|
||||||
LOG.info("Stopping services");
|
|
||||||
state = STOPPING;
|
|
||||||
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
|
||||||
for (Service s : services) {
|
|
||||||
long start = now();
|
long start = now();
|
||||||
s.stopService();
|
s.stopService();
|
||||||
if (LOG.isLoggable(FINE)) {
|
if (LOG.isLoggable(FINE)) {
|
||||||
logDuration(LOG, "Stopping service "
|
logDuration(LOG, "Stopping service "
|
||||||
+ s.getClass().getSimpleName(), start);
|
+ s.getClass().getSimpleName(), start);
|
||||||
}
|
}
|
||||||
|
} catch (ServiceException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
for (ExecutorService e : executors) {
|
}
|
||||||
if (LOG.isLoggable(FINE)) {
|
for (ExecutorService e : executors) {
|
||||||
LOG.fine("Stopping executor "
|
if (LOG.isLoggable(FINE)) {
|
||||||
+ e.getClass().getSimpleName());
|
LOG.fine("Stopping executor "
|
||||||
}
|
+ e.getClass().getSimpleName());
|
||||||
e.shutdownNow();
|
|
||||||
}
|
}
|
||||||
|
e.shutdownNow();
|
||||||
|
}
|
||||||
|
try {
|
||||||
long start = now();
|
long start = now();
|
||||||
db.close();
|
db.close();
|
||||||
logDuration(LOG, "Closing database", start);
|
logDuration(LOG, "Closing database", start);
|
||||||
shutdownLatch.countDown();
|
} catch (DbException e) {
|
||||||
} catch (DbException | ServiceException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
} finally {
|
|
||||||
startStopSemaphore.release();
|
|
||||||
}
|
}
|
||||||
|
state.set(STOPPED);
|
||||||
|
shutdownLatch.countDown();
|
||||||
|
eventBus.broadcast(new LifecycleEvent(STOPPED));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -240,6 +236,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LifecycleState getLifecycleState() {
|
public LifecycleState getLifecycleState() {
|
||||||
return state;
|
return state.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,21 @@ interface ConnectivityChecker {
|
|||||||
* the check succeeds. If a check is already running then the observer is
|
* the check succeeds. If a check is already running then the observer is
|
||||||
* called when the check succeeds. If a connectivity check has recently
|
* called when the check succeeds. If a connectivity check has recently
|
||||||
* succeeded then the observer is called immediately.
|
* 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,
|
void checkConnectivity(MailboxProperties properties,
|
||||||
ConnectivityObserver o);
|
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 {
|
interface ConnectivityObserver {
|
||||||
void onConnectivityCheckSucceeded();
|
void onConnectivityCheckSucceeded();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,8 +80,7 @@ abstract class ConnectivityCheckerImpl implements ConnectivityChecker {
|
|||||||
> CONNECTIVITY_CHECK_FRESHNESS_MS) {
|
> CONNECTIVITY_CHECK_FRESHNESS_MS) {
|
||||||
// The last connectivity check is stale, start a new one
|
// The last connectivity check is stale, start a new one
|
||||||
connectivityObservers.add(o);
|
connectivityObservers.add(o);
|
||||||
ApiCall task =
|
ApiCall task = createConnectivityCheckTask(properties);
|
||||||
createConnectivityCheckTask(properties);
|
|
||||||
connectivityCheck = mailboxApiCaller.retryWithBackoff(task);
|
connectivityCheck = mailboxApiCaller.retryWithBackoff(task);
|
||||||
} else {
|
} else {
|
||||||
// The last connectivity check is fresh
|
// The last connectivity check is fresh
|
||||||
@@ -108,4 +107,16 @@ abstract class ConnectivityCheckerImpl implements ConnectivityChecker {
|
|||||||
o.onConnectivityCheckSucceeded();
|
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,124 @@
|
|||||||
|
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 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;
|
||||||
|
|
||||||
|
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();
|
||||||
|
// The connectivity checker belongs to the client, so it should be
|
||||||
|
// destroyed. The Tor reachability monitor is shared between clients,
|
||||||
|
// so it should not be destroyed
|
||||||
|
connectivityChecker.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,16 +5,23 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(ContactMailboxConnectivityChecker.class.getName());
|
||||||
|
|
||||||
private final MailboxApi mailboxApi;
|
private final MailboxApi mailboxApi;
|
||||||
|
|
||||||
|
@Inject
|
||||||
ContactMailboxConnectivityChecker(Clock clock,
|
ContactMailboxConnectivityChecker(Clock clock,
|
||||||
MailboxApiCaller mailboxApiCaller, MailboxApi mailboxApi) {
|
MailboxApiCaller mailboxApiCaller, MailboxApi mailboxApi) {
|
||||||
super(clock, mailboxApiCaller);
|
super(clock, mailboxApiCaller);
|
||||||
@@ -24,16 +31,13 @@ class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
|||||||
@Override
|
@Override
|
||||||
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
|
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
|
||||||
if (properties.isOwner()) throw new IllegalArgumentException();
|
if (properties.isOwner()) throw new IllegalArgumentException();
|
||||||
return new SimpleApiCall() {
|
return new SimpleApiCall(() -> {
|
||||||
@Override
|
LOG.info("Checking connectivity of contact's mailbox");
|
||||||
void tryToCallApi() throws IOException, ApiException {
|
if (!mailboxApi.checkStatus(properties)) throw new ApiException();
|
||||||
if (!mailboxApi.checkStatus(properties)) {
|
LOG.info("Contact's mailbox is reachable");
|
||||||
throw new ApiException();
|
// Call the observers and cache the result
|
||||||
}
|
onConnectivityCheckSucceeded(clock.currentTimeMillis());
|
||||||
// Call the observers and cache the result
|
});
|
||||||
onConnectivityCheckSucceeded(clock.currentTimeMillis());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
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.mailbox.MailboxApi.ApiException;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class ContactMailboxDownloadWorker extends MailboxDownloadWorker {
|
||||||
|
|
||||||
|
ContactMailboxDownloadWorker(
|
||||||
|
ConnectivityChecker connectivityChecker,
|
||||||
|
TorReachabilityMonitor torReachabilityMonitor,
|
||||||
|
MailboxApiCaller mailboxApiCaller,
|
||||||
|
MailboxApi mailboxApi,
|
||||||
|
MailboxFileManager mailboxFileManager,
|
||||||
|
MailboxProperties mailboxProperties) {
|
||||||
|
super(connectivityChecker, torReachabilityMonitor, mailboxApiCaller,
|
||||||
|
mailboxApi, mailboxFileManager, mailboxProperties);
|
||||||
|
if (mailboxProperties.isOwner()) throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ApiCall createApiCallForDownloadCycle() {
|
||||||
|
return new SimpleApiCall(this::apiCallListInbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apiCallListInbox() throws IOException, ApiException {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state == State.DESTROYED) return;
|
||||||
|
}
|
||||||
|
LOG.info("Listing inbox");
|
||||||
|
MailboxFolderId folderId =
|
||||||
|
requireNonNull(mailboxProperties.getInboxId());
|
||||||
|
List<MailboxFile> files;
|
||||||
|
try {
|
||||||
|
files = mailboxApi.getFiles(mailboxProperties, folderId);
|
||||||
|
} catch (TolerableFailureException e) {
|
||||||
|
LOG.warning("Inbox folder does not exist");
|
||||||
|
files = emptyList();
|
||||||
|
}
|
||||||
|
if (files.isEmpty()) {
|
||||||
|
onDownloadCycleFinished();
|
||||||
|
} else {
|
||||||
|
Queue<FolderFile> queue = new LinkedList<>();
|
||||||
|
for (MailboxFile file : files) {
|
||||||
|
queue.add(new FolderFile(folderId, file.name));
|
||||||
|
}
|
||||||
|
downloadNextFile(queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,9 +91,13 @@ interface MailboxApi {
|
|||||||
* Used by owner and contacts to list their files to retrieve.
|
* Used by owner and contacts to list their files to retrieve.
|
||||||
* <p>
|
* <p>
|
||||||
* Returns 200 OK with the list of files in JSON.
|
* Returns 200 OK with the list of files in JSON.
|
||||||
|
*
|
||||||
|
* @throws TolerableFailureException if response code is 404 (folder does
|
||||||
|
* not exist or client is not authorised to download from it)
|
||||||
*/
|
*/
|
||||||
List<MailboxFile> getFiles(MailboxProperties properties,
|
List<MailboxFile> getFiles(MailboxProperties properties,
|
||||||
MailboxFolderId folderId) throws IOException, ApiException;
|
MailboxFolderId folderId)
|
||||||
|
throws IOException, ApiException, TolerableFailureException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by owner and contacts to retrieve a file.
|
* Used by owner and contacts to retrieve a file.
|
||||||
@@ -102,17 +106,22 @@ interface MailboxApi {
|
|||||||
* in the response body.
|
* in the response body.
|
||||||
*
|
*
|
||||||
* @param file the empty file the response bytes will be written into.
|
* @param file the empty file the response bytes will be written into.
|
||||||
|
* @throws TolerableFailureException if response code is 404 (folder does
|
||||||
|
* not exist, client is not authorised to download from folder, or file
|
||||||
|
* does not exist)
|
||||||
*/
|
*/
|
||||||
void getFile(MailboxProperties properties, MailboxFolderId folderId,
|
void getFile(MailboxProperties properties, MailboxFolderId folderId,
|
||||||
MailboxFileId fileId, File file) throws IOException, ApiException;
|
MailboxFileId fileId, File file)
|
||||||
|
throws IOException, ApiException, TolerableFailureException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by owner and contacts to delete files.
|
* Used by owner and contacts to delete files.
|
||||||
* <p>
|
* <p>
|
||||||
* Returns 200 OK (no exception) if deletion was successful.
|
* Returns 200 OK (no exception) if deletion was successful.
|
||||||
*
|
*
|
||||||
* @throws TolerableFailureException on 404 response,
|
* @throws TolerableFailureException if response code is 404 (folder does
|
||||||
* because file was most likely deleted already.
|
* not exist, client is not authorised to download from folder, or file
|
||||||
|
* does not exist)
|
||||||
*/
|
*/
|
||||||
void deleteFile(MailboxProperties properties, MailboxFolderId folderId,
|
void deleteFile(MailboxProperties properties, MailboxFolderId folderId,
|
||||||
MailboxFileId fileId)
|
MailboxFileId fileId)
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ interface MailboxApiCaller {
|
|||||||
* Asynchronously calls the given API call on the {@link IoExecutor},
|
* Asynchronously calls the given API call on the {@link IoExecutor},
|
||||||
* automatically retrying at increasing intervals until the API call
|
* automatically retrying at increasing intervals until the API call
|
||||||
* returns false or retries are cancelled.
|
* 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
|
* @return A {@link Cancellable} that can be used to cancel any future
|
||||||
* retries.
|
* retries.
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -35,6 +34,7 @@ import okhttp3.Response;
|
|||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
import static com.fasterxml.jackson.databind.MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES;
|
import static com.fasterxml.jackson.databind.MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES;
|
||||||
|
import static java.util.Collections.sort;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static okhttp3.internal.Util.EMPTY_REQUEST;
|
import static okhttp3.internal.Util.EMPTY_REQUEST;
|
||||||
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
||||||
@@ -42,18 +42,22 @@ import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class MailboxApiImpl implements MailboxApi {
|
class MailboxApiImpl implements MailboxApi {
|
||||||
|
|
||||||
private final WeakSingletonProvider<OkHttpClient> httpClientProvider;
|
|
||||||
private final JsonMapper mapper = JsonMapper.builder()
|
|
||||||
.enable(BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES)
|
|
||||||
.build();
|
|
||||||
private static final MediaType JSON =
|
private static final MediaType JSON =
|
||||||
requireNonNull(MediaType.parse("application/json; charset=utf-8"));
|
requireNonNull(MediaType.parse("application/json; charset=utf-8"));
|
||||||
private static final MediaType FILE =
|
private static final MediaType FILE =
|
||||||
requireNonNull(MediaType.parse("application/octet-stream"));
|
requireNonNull(MediaType.parse("application/octet-stream"));
|
||||||
|
|
||||||
|
private final WeakSingletonProvider<OkHttpClient> httpClientProvider;
|
||||||
|
private final JsonMapper mapper = JsonMapper.builder()
|
||||||
|
.enable(BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES)
|
||||||
|
.build();
|
||||||
|
private final UrlConverter urlConverter;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MailboxApiImpl(WeakSingletonProvider<OkHttpClient> httpClientProvider) {
|
MailboxApiImpl(WeakSingletonProvider<OkHttpClient> httpClientProvider,
|
||||||
|
UrlConverter urlConverter) {
|
||||||
this.httpClientProvider = httpClientProvider;
|
this.httpClientProvider = httpClientProvider;
|
||||||
|
this.urlConverter = urlConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -78,7 +82,7 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
throws IOException, ApiException {
|
throws IOException, ApiException {
|
||||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||||
Request request = getRequestBuilder(properties.getAuthToken())
|
Request request = getRequestBuilder(properties.getAuthToken())
|
||||||
.url(properties.getBaseUrl() + "/setup")
|
.url(getBaseUrl(properties) + "/setup")
|
||||||
.put(EMPTY_REQUEST)
|
.put(EMPTY_REQUEST)
|
||||||
.build();
|
.build();
|
||||||
OkHttpClient client = httpClientProvider.get();
|
OkHttpClient client = httpClientProvider.get();
|
||||||
@@ -93,7 +97,7 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
if (tokenNode == null) {
|
if (tokenNode == null) {
|
||||||
throw new ApiException();
|
throw new ApiException();
|
||||||
}
|
}
|
||||||
return new MailboxProperties(properties.getBaseUrl(),
|
return new MailboxProperties(properties.getOnion(),
|
||||||
MailboxAuthToken.fromString(tokenNode.textValue()),
|
MailboxAuthToken.fromString(tokenNode.textValue()),
|
||||||
parseServerSupports(node));
|
parseServerSupports(node));
|
||||||
} catch (JacksonException | InvalidMailboxIdException e) {
|
} catch (JacksonException | InvalidMailboxIdException e) {
|
||||||
@@ -121,6 +125,8 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
if (major < 0 || minor < 0) throw new ApiException();
|
if (major < 0 || minor < 0) throw new ApiException();
|
||||||
serverSupports.add(new MailboxVersion(major, minor));
|
serverSupports.add(new MailboxVersion(major, minor));
|
||||||
}
|
}
|
||||||
|
// Sort the list of versions for easier comparison
|
||||||
|
sort(serverSupports);
|
||||||
return serverSupports;
|
return serverSupports;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +143,7 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
throws IOException, ApiException {
|
throws IOException, ApiException {
|
||||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||||
Request request = getRequestBuilder(properties.getAuthToken())
|
Request request = getRequestBuilder(properties.getAuthToken())
|
||||||
.url(properties.getBaseUrl() + "/")
|
.url(getBaseUrl(properties) + "/")
|
||||||
.delete()
|
.delete()
|
||||||
.build();
|
.build();
|
||||||
OkHttpClient client = httpClientProvider.get();
|
OkHttpClient client = httpClientProvider.get();
|
||||||
@@ -162,7 +168,7 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
public void deleteContact(MailboxProperties properties, ContactId contactId)
|
public void deleteContact(MailboxProperties properties, ContactId contactId)
|
||||||
throws IOException, ApiException, TolerableFailureException {
|
throws IOException, ApiException, TolerableFailureException {
|
||||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||||
String url = properties.getBaseUrl() + "/contacts/" +
|
String url = getBaseUrl(properties) + "/contacts/" +
|
||||||
contactId.getInt();
|
contactId.getInt();
|
||||||
Request request = getRequestBuilder(properties.getAuthToken())
|
Request request = getRequestBuilder(properties.getAuthToken())
|
||||||
.delete()
|
.delete()
|
||||||
@@ -212,9 +218,11 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<MailboxFile> getFiles(MailboxProperties properties,
|
public List<MailboxFile> getFiles(MailboxProperties properties,
|
||||||
MailboxFolderId folderId) throws IOException, ApiException {
|
MailboxFolderId folderId)
|
||||||
|
throws IOException, ApiException, TolerableFailureException {
|
||||||
String path = "/files/" + folderId;
|
String path = "/files/" + folderId;
|
||||||
Response response = sendGetRequest(properties, path);
|
Response response = sendGetRequest(properties, path);
|
||||||
|
if (response.code() == 404) throw new TolerableFailureException();
|
||||||
if (response.code() != 200) throw new ApiException();
|
if (response.code() != 200) throw new ApiException();
|
||||||
|
|
||||||
ResponseBody body = response.body();
|
ResponseBody body = response.body();
|
||||||
@@ -239,7 +247,7 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
if (time < 1) throw new ApiException();
|
if (time < 1) throw new ApiException();
|
||||||
list.add(new MailboxFile(MailboxFileId.fromString(name), time));
|
list.add(new MailboxFile(MailboxFileId.fromString(name), time));
|
||||||
}
|
}
|
||||||
Collections.sort(list);
|
sort(list);
|
||||||
return list;
|
return list;
|
||||||
} catch (JacksonException | InvalidMailboxIdException e) {
|
} catch (JacksonException | InvalidMailboxIdException e) {
|
||||||
throw new ApiException();
|
throw new ApiException();
|
||||||
@@ -248,9 +256,11 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void getFile(MailboxProperties properties, MailboxFolderId folderId,
|
public void getFile(MailboxProperties properties, MailboxFolderId folderId,
|
||||||
MailboxFileId fileId, File file) throws IOException, ApiException {
|
MailboxFileId fileId, File file)
|
||||||
|
throws IOException, ApiException, TolerableFailureException {
|
||||||
String path = "/files/" + folderId + "/" + fileId;
|
String path = "/files/" + folderId + "/" + fileId;
|
||||||
Response response = sendGetRequest(properties, path);
|
Response response = sendGetRequest(properties, path);
|
||||||
|
if (response.code() == 404) throw new TolerableFailureException();
|
||||||
if (response.code() != 200) throw new ApiException();
|
if (response.code() != 200) throw new ApiException();
|
||||||
|
|
||||||
ResponseBody body = response.body();
|
ResponseBody body = response.body();
|
||||||
@@ -266,7 +276,7 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
String path = "/files/" + folderId + "/" + fileId;
|
String path = "/files/" + folderId + "/" + fileId;
|
||||||
Request request = getRequestBuilder(properties.getAuthToken())
|
Request request = getRequestBuilder(properties.getAuthToken())
|
||||||
.delete()
|
.delete()
|
||||||
.url(properties.getBaseUrl() + path)
|
.url(getBaseUrl(properties) + path)
|
||||||
.build();
|
.build();
|
||||||
OkHttpClient client = httpClientProvider.get();
|
OkHttpClient client = httpClientProvider.get();
|
||||||
Response response = client.newCall(request).execute();
|
Response response = client.newCall(request).execute();
|
||||||
@@ -308,7 +318,7 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
private Response sendGetRequest(MailboxProperties properties, String path)
|
private Response sendGetRequest(MailboxProperties properties, String path)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Request request = getRequestBuilder(properties.getAuthToken())
|
Request request = getRequestBuilder(properties.getAuthToken())
|
||||||
.url(properties.getBaseUrl() + path)
|
.url(getBaseUrl(properties) + path)
|
||||||
.build();
|
.build();
|
||||||
OkHttpClient client = httpClientProvider.get();
|
OkHttpClient client = httpClientProvider.get();
|
||||||
return client.newCall(request).execute();
|
return client.newCall(request).execute();
|
||||||
@@ -317,7 +327,7 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
private Response sendPostRequest(MailboxProperties properties, String path,
|
private Response sendPostRequest(MailboxProperties properties, String path,
|
||||||
RequestBody body) throws IOException {
|
RequestBody body) throws IOException {
|
||||||
Request request = getRequestBuilder(properties.getAuthToken())
|
Request request = getRequestBuilder(properties.getAuthToken())
|
||||||
.url(properties.getBaseUrl() + path)
|
.url(getBaseUrl(properties) + path)
|
||||||
.post(body)
|
.post(body)
|
||||||
.build();
|
.build();
|
||||||
OkHttpClient client = httpClientProvider.get();
|
OkHttpClient client = httpClientProvider.get();
|
||||||
@@ -339,4 +349,7 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
return (ArrayNode) arrayNode;
|
return (ArrayNode) arrayNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getBaseUrl(MailboxProperties properties) {
|
||||||
|
return urlConverter.convertOnionToBaseUrl(properties.getOnion());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
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.
|
||||||
|
*
|
||||||
|
* @param properties Properties for communicating with the mailbox
|
||||||
|
* managed by this client.
|
||||||
|
* @param folderId The ID of the folder to which files will be uploaded.
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @param properties Properties for communicating with the mailbox
|
||||||
|
* managed by this client.
|
||||||
|
* @param folderId The ID of the folder from which files will be
|
||||||
|
* downloaded.
|
||||||
|
*/
|
||||||
|
void assignContactForDownload(ContactId c, MailboxProperties properties,
|
||||||
|
MailboxFolderId folderId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deassigns a contact from the client for download.
|
||||||
|
*/
|
||||||
|
void deassignContactForDownload(ContactId c);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
interface MailboxClientFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a client for communicating with a contact's mailbox.
|
||||||
|
*/
|
||||||
|
MailboxClient createContactMailboxClient(
|
||||||
|
TorReachabilityMonitor reachabilityMonitor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a client for communicating with our own mailbox.
|
||||||
|
*/
|
||||||
|
MailboxClient createOwnMailboxClient(
|
||||||
|
TorReachabilityMonitor reachabilityMonitor,
|
||||||
|
MailboxProperties properties);
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Provider;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class MailboxClientFactoryImpl implements MailboxClientFactory {
|
||||||
|
|
||||||
|
private final MailboxWorkerFactory workerFactory;
|
||||||
|
private final Provider<ContactMailboxConnectivityChecker>
|
||||||
|
contactCheckerProvider;
|
||||||
|
private final Provider<OwnMailboxConnectivityChecker> ownCheckerProvider;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MailboxClientFactoryImpl(MailboxWorkerFactory workerFactory,
|
||||||
|
Provider<ContactMailboxConnectivityChecker> contactCheckerProvider,
|
||||||
|
Provider<OwnMailboxConnectivityChecker> ownCheckerProvider) {
|
||||||
|
this.workerFactory = workerFactory;
|
||||||
|
this.contactCheckerProvider = contactCheckerProvider;
|
||||||
|
this.ownCheckerProvider = ownCheckerProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MailboxClient createContactMailboxClient(
|
||||||
|
TorReachabilityMonitor reachabilityMonitor) {
|
||||||
|
ConnectivityChecker connectivityChecker = contactCheckerProvider.get();
|
||||||
|
return new ContactMailboxClient(workerFactory, connectivityChecker,
|
||||||
|
reachabilityMonitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MailboxClient createOwnMailboxClient(
|
||||||
|
TorReachabilityMonitor reachabilityMonitor,
|
||||||
|
MailboxProperties properties) {
|
||||||
|
ConnectivityChecker connectivityChecker = ownCheckerProvider.get();
|
||||||
|
return new OwnMailboxClient(workerFactory, connectivityChecker,
|
||||||
|
reachabilityMonitor, properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
|
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.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||||
|
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.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.Logger.getLogger;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
abstract class MailboxDownloadWorker implements MailboxWorker,
|
||||||
|
ConnectivityObserver, TorReachabilityObserver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the worker is started it waits for a connectivity check, then
|
||||||
|
* starts its first download cycle: checking for files to download,
|
||||||
|
* downloading and deleting the files, and checking again until all files
|
||||||
|
* have been downloaded and deleted.
|
||||||
|
* <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.
|
||||||
|
*/
|
||||||
|
protected enum State {
|
||||||
|
CREATED,
|
||||||
|
CONNECTIVITY_CHECK,
|
||||||
|
DOWNLOAD_CYCLE_1,
|
||||||
|
WAITING_FOR_TOR,
|
||||||
|
DOWNLOAD_CYCLE_2,
|
||||||
|
FINISHED,
|
||||||
|
DESTROYED
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final Logger LOG =
|
||||||
|
getLogger(MailboxDownloadWorker.class.getName());
|
||||||
|
|
||||||
|
private final ConnectivityChecker connectivityChecker;
|
||||||
|
private final TorReachabilityMonitor torReachabilityMonitor;
|
||||||
|
protected final MailboxApiCaller mailboxApiCaller;
|
||||||
|
protected final MailboxApi mailboxApi;
|
||||||
|
private final MailboxFileManager mailboxFileManager;
|
||||||
|
protected final MailboxProperties mailboxProperties;
|
||||||
|
protected final Object lock = new Object();
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
protected State state = State.CREATED;
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@Nullable
|
||||||
|
protected Cancellable apiCall = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the API call that starts the worker's download cycle.
|
||||||
|
*/
|
||||||
|
protected abstract ApiCall createApiCallForDownloadCycle();
|
||||||
|
|
||||||
|
MailboxDownloadWorker(
|
||||||
|
ConnectivityChecker connectivityChecker,
|
||||||
|
TorReachabilityMonitor torReachabilityMonitor,
|
||||||
|
MailboxApiCaller mailboxApiCaller,
|
||||||
|
MailboxApi mailboxApi,
|
||||||
|
MailboxFileManager mailboxFileManager,
|
||||||
|
MailboxProperties mailboxProperties) {
|
||||||
|
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(
|
||||||
|
createApiCallForDownloadCycle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void downloadNextFile(Queue<FolderFile> queue) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state == State.DESTROYED) return;
|
||||||
|
if (queue.isEmpty()) {
|
||||||
|
// Check for files again, as new files may have arrived while
|
||||||
|
// we were downloading
|
||||||
|
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||||
|
createApiCallForDownloadCycle());
|
||||||
|
} else {
|
||||||
|
FolderFile file = queue.remove();
|
||||||
|
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||||
|
new SimpleApiCall(() ->
|
||||||
|
apiCallDownloadFile(file, queue)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apiCallDownloadFile(FolderFile file, Queue<FolderFile> queue)
|
||||||
|
throws IOException, ApiException {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state == State.DESTROYED) return;
|
||||||
|
}
|
||||||
|
LOG.info("Downloading file");
|
||||||
|
File tempFile = mailboxFileManager.createTempFileForDownload();
|
||||||
|
try {
|
||||||
|
mailboxApi.getFile(mailboxProperties, file.folderId, file.fileId,
|
||||||
|
tempFile);
|
||||||
|
} catch (IOException | ApiException e) {
|
||||||
|
if (!tempFile.delete()) {
|
||||||
|
LOG.warning("Failed to delete temporary file");
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
} catch (TolerableFailureException e) {
|
||||||
|
// File not found - continue to the next file
|
||||||
|
LOG.warning("File does not exist");
|
||||||
|
if (!tempFile.delete()) {
|
||||||
|
LOG.warning("Failed to delete temporary file");
|
||||||
|
}
|
||||||
|
downloadNextFile(queue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mailboxFileManager.handleDownloadedFile(tempFile);
|
||||||
|
deleteFile(file, queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteFile(FolderFile file, Queue<FolderFile> queue) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state == State.DESTROYED) return;
|
||||||
|
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||||
|
new SimpleApiCall(() -> apiCallDeleteFile(file, queue)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apiCallDeleteFile(FolderFile file, Queue<FolderFile> queue)
|
||||||
|
throws IOException, ApiException {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state == State.DESTROYED) return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mailboxApi.deleteFile(mailboxProperties, file.folderId,
|
||||||
|
file.fileId);
|
||||||
|
} catch (TolerableFailureException e) {
|
||||||
|
// File not found - continue to the next file
|
||||||
|
LOG.warning("File does not exist");
|
||||||
|
}
|
||||||
|
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(
|
||||||
|
createApiCallForDownloadCycle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Package access for testing
|
||||||
|
static class FolderFile {
|
||||||
|
|
||||||
|
final MailboxFolderId folderId;
|
||||||
|
final MailboxFileId fileId;
|
||||||
|
|
||||||
|
FolderFile(MailboxFolderId folderId, MailboxFileId fileId) {
|
||||||
|
this.folderId = folderId;
|
||||||
|
this.fileId = fileId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -16,6 +18,14 @@ interface MailboxFileManager {
|
|||||||
*/
|
*/
|
||||||
File createTempFileForDownload() throws IOException;
|
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
|
* Handles a file that has been downloaded. The file should be created
|
||||||
* with {@link #createTempFileForDownload()}.
|
* with {@link #createTempFileForDownload()}.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
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.Event;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
@@ -10,13 +11,18 @@ import org.briarproject.bramble.api.mailbox.MailboxDirectory;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
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.event.TransportActiveEvent;
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
|
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
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.CountDownLatch;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -30,6 +36,7 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleS
|
|||||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
|
||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
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.api.plugin.file.FileConstants.PROP_PATH;
|
||||||
|
import static org.briarproject.bramble.util.IoUtils.delete;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
@@ -41,6 +48,7 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
|
|||||||
|
|
||||||
// Package access for testing
|
// Package access for testing
|
||||||
static final String DOWNLOAD_DIR_NAME = "downloads";
|
static final String DOWNLOAD_DIR_NAME = "downloads";
|
||||||
|
static final String UPLOAD_DIR_NAME = "uploads";
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
private final PluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
@@ -67,14 +75,44 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File createTempFileForDownload() throws IOException {
|
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
|
// Wait for orphaned files to be handled before creating new files
|
||||||
try {
|
try {
|
||||||
orphanLatch.await();
|
orphanLatch.await();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
|
File dir = createDirectoryIfNeeded(dirName);
|
||||||
return File.createTempFile("mailbox", ".tmp", downloadDir);
|
return File.createTempFile("mailbox", ".tmp", dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
private File createDirectoryIfNeeded(String name) throws IOException {
|
private File createDirectoryIfNeeded(String name) throws IOException {
|
||||||
@@ -116,6 +154,8 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void eventOccurred(Event e) {
|
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) {
|
if (e instanceof TransportActiveEvent) {
|
||||||
TransportActiveEvent t = (TransportActiveEvent) e;
|
TransportActiveEvent t = (TransportActiveEvent) e;
|
||||||
if (t.getTransportId().equals(ID)) {
|
if (t.getTransportId().equals(ID)) {
|
||||||
@@ -127,17 +167,25 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called at startup, as soon as the plugin is started, to
|
* This method is called at startup, as soon as the plugin is started, to
|
||||||
* handle any files that were left in the download directory at the last
|
* delete any files that were left in the upload directory at the last
|
||||||
* shutdown.
|
* shutdown and handle any files that were left in the download directory.
|
||||||
*/
|
*/
|
||||||
@IoExecutor
|
@IoExecutor
|
||||||
private void handleOrphanedFiles() {
|
private void handleOrphanedFiles() {
|
||||||
try {
|
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 downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
|
||||||
File[] orphans = downloadDir.listFiles();
|
File[] orphanedDownloads = downloadDir.listFiles();
|
||||||
// Now that we've got the list of orphans, new files can be created
|
// Now that we've got the list of orphaned downloads, new files
|
||||||
|
// can be created in the download directory
|
||||||
orphanLatch.countDown();
|
orphanLatch.countDown();
|
||||||
if (orphans != null) for (File f : orphans) handleDownloadedFile(f);
|
if (orphanedDownloads != null) {
|
||||||
|
for (File f : orphanedDownloads) handleDownloadedFile(f);
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
@@ -165,9 +213,58 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
|
|||||||
delegate.dispose(exception, recognised);
|
delegate.dispose(exception, recognised);
|
||||||
if (isHandlingComplete(exception, recognised)) {
|
if (isHandlingComplete(exception, recognised)) {
|
||||||
LOG.info("Deleting downloaded file");
|
LOG.info("Deleting downloaded file");
|
||||||
if (!file.delete()) {
|
delete(file);
|
||||||
LOG.warning("Failed to delete downloaded 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.MailboxProperties;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
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.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@@ -98,34 +100,35 @@ class MailboxManagerImpl implements MailboxManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean checkConnection() {
|
public boolean checkConnection() {
|
||||||
boolean success;
|
List<MailboxVersion> versions = null;
|
||||||
try {
|
try {
|
||||||
MailboxProperties props = db.transactionWithNullableResult(true,
|
MailboxProperties props = db.transactionWithNullableResult(true,
|
||||||
mailboxSettingsManager::getOwnMailboxProperties);
|
mailboxSettingsManager::getOwnMailboxProperties);
|
||||||
if (props == null) throw new DbException();
|
if (props == null) throw new DbException();
|
||||||
success = api.checkStatus(props);
|
versions = api.getServerSupports(props);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
// we don't treat this is a failure to record
|
// we don't treat this is a failure to record
|
||||||
return false;
|
return false;
|
||||||
} catch (IOException | MailboxApi.ApiException e) {
|
} catch (IOException | MailboxApi.ApiException e) {
|
||||||
// we record this as a failure
|
// we record this as a failure
|
||||||
success = false;
|
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
recordCheckResult(success);
|
recordCheckResult(versions);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, 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();
|
long now = clock.currentTimeMillis();
|
||||||
db.transaction(false, txn -> {
|
db.transaction(false, txn -> {
|
||||||
if (success) {
|
if (versions != null) {
|
||||||
mailboxSettingsManager.recordSuccessfulConnection(txn, now);
|
mailboxSettingsManager
|
||||||
|
.recordSuccessfulConnection(txn, now, versions);
|
||||||
} else {
|
} else {
|
||||||
mailboxSettingsManager.recordFailedConnectionAttempt(txn, now);
|
mailboxSettingsManager.recordFailedConnectionAttempt(txn, now);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,13 +52,19 @@ public class MailboxModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@Singleton
|
||||||
MailboxSettingsManager provideMailboxSettingsManager(
|
MailboxSettingsManager provideMailboxSettingsManager(
|
||||||
MailboxSettingsManagerImpl mailboxSettingsManager) {
|
MailboxSettingsManagerImpl mailboxSettingsManager) {
|
||||||
return mailboxSettingsManager;
|
return mailboxSettingsManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
MailboxApi providesMailboxApi(MailboxApiImpl mailboxApi) {
|
UrlConverter provideUrlConverter(UrlConverterImpl urlConverter) {
|
||||||
|
return urlConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
MailboxApi provideMailboxApi(MailboxApiImpl mailboxApi) {
|
||||||
return mailboxApi;
|
return mailboxApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,4 +120,10 @@ public class MailboxModule {
|
|||||||
}
|
}
|
||||||
return mailboxFileManager;
|
return mailboxFileManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
MailboxWorkerFactory provideMailboxWorkerFactory(
|
||||||
|
MailboxWorkerFactoryImpl mailboxWorkerFactory) {
|
||||||
|
return mailboxWorkerFactory;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,7 +120,8 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
|||||||
db.transaction(false, txn -> {
|
db.transaction(false, txn -> {
|
||||||
mailboxSettingsManager
|
mailboxSettingsManager
|
||||||
.setOwnMailboxProperties(txn, ownerProperties);
|
.setOwnMailboxProperties(txn, ownerProperties);
|
||||||
mailboxSettingsManager.recordSuccessfulConnection(txn, time);
|
mailboxSettingsManager.recordSuccessfulConnection(txn, time,
|
||||||
|
ownerProperties.getServerSupports());
|
||||||
// A (possibly new) mailbox is paired. Reset message retransmission
|
// A (possibly new) mailbox is paired. Reset message retransmission
|
||||||
// timers for contacts who doesn't have their own mailbox. This way,
|
// timers for contacts who doesn't have their own mailbox. This way,
|
||||||
// data stranded on our old mailbox will be re-uploaded to our new.
|
// data stranded on our old mailbox will be re-uploaded to our new.
|
||||||
@@ -177,10 +178,9 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
|||||||
LOG.info("QR code is valid");
|
LOG.info("QR code is valid");
|
||||||
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
|
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
|
||||||
String onion = crypto.encodeOnion(onionPubKey);
|
String onion = crypto.encodeOnion(onionPubKey);
|
||||||
String baseUrl = "http://" + onion + ".onion"; // TODO
|
|
||||||
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
|
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
|
||||||
MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes);
|
MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes);
|
||||||
return new MailboxProperties(baseUrl, setupToken, new ArrayList<>());
|
return new MailboxProperties(onion, setupToken, new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ import java.util.List;
|
|||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
|
|
||||||
@Immutable
|
@ThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||||
|
|
||||||
@@ -74,19 +74,13 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
|||||||
public void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
|
public void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Settings s = new Settings();
|
Settings s = new Settings();
|
||||||
s.put(SETTINGS_KEY_ONION, p.getBaseUrl());
|
s.put(SETTINGS_KEY_ONION, p.getOnion());
|
||||||
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
|
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
|
||||||
List<MailboxVersion> serverSupports = p.getServerSupports();
|
List<MailboxVersion> serverSupports = p.getServerSupports();
|
||||||
int[] ints = new int[serverSupports.size() * 2];
|
encodeServerSupports(serverSupports, s);
|
||||||
int i = 0;
|
|
||||||
for (MailboxVersion v : serverSupports) {
|
|
||||||
ints[i++] = v.getMajor();
|
|
||||||
ints[i++] = v.getMinor();
|
|
||||||
}
|
|
||||||
s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints);
|
|
||||||
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||||
for (MailboxHook hook : hooks) {
|
for (MailboxHook hook : hooks) {
|
||||||
hook.mailboxPaired(txn, p.getOnion(), p.getServerSupports());
|
hook.mailboxPaired(txn, p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +89,10 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
|||||||
Settings s = new Settings();
|
Settings s = new Settings();
|
||||||
s.put(SETTINGS_KEY_ONION, "");
|
s.put(SETTINGS_KEY_ONION, "");
|
||||||
s.put(SETTINGS_KEY_TOKEN, "");
|
s.put(SETTINGS_KEY_TOKEN, "");
|
||||||
|
s.put(SETTINGS_KEY_ATTEMPTS, "");
|
||||||
|
s.put(SETTINGS_KEY_LAST_ATTEMPT, "");
|
||||||
|
s.put(SETTINGS_KEY_LAST_SUCCESS, "");
|
||||||
|
s.put(SETTINGS_KEY_SERVER_SUPPORTS, "");
|
||||||
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||||
for (MailboxHook hook : hooks) {
|
for (MailboxHook hook : hooks) {
|
||||||
hook.mailboxUnpaired(txn);
|
hook.mailboxUnpaired(txn);
|
||||||
@@ -119,17 +117,17 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void recordSuccessfulConnection(Transaction txn, long now)
|
public void recordSuccessfulConnection(Transaction txn, long now,
|
||||||
throws DbException {
|
List<MailboxVersion> versions) throws DbException {
|
||||||
Settings oldSettings =
|
|
||||||
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
|
|
||||||
Settings s = new Settings();
|
Settings s = new Settings();
|
||||||
|
// record the successful connection
|
||||||
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
||||||
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
|
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
|
||||||
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
|
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||||
|
encodeServerSupports(versions, s);
|
||||||
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||||
List<MailboxVersion> serverSupports = parseServerSupports(oldSettings);
|
// broadcast status event
|
||||||
MailboxStatus status = new MailboxStatus(now, now, 0, serverSupports);
|
MailboxStatus status = new MailboxStatus(now, now, 0, versions);
|
||||||
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,6 +169,17 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
|||||||
return filename;
|
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)
|
private List<MailboxVersion> parseServerSupports(Settings s)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
if (!s.containsKey(SETTINGS_KEY_SERVER_SUPPORTS)) return emptyList();
|
if (!s.containsKey(SETTINGS_KEY_SERVER_SUPPORTS)) return emptyList();
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
|||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
|
import org.briarproject.bramble.api.mailbox.event.MailboxPairedEvent;
|
||||||
|
import org.briarproject.bramble.api.mailbox.event.MailboxUnpairedEvent;
|
||||||
|
import org.briarproject.bramble.api.mailbox.event.MailboxUpdateSentEvent;
|
||||||
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
|
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Group;
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
@@ -38,6 +41,7 @@ import org.briarproject.bramble.api.system.Clock;
|
|||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
@@ -111,6 +115,7 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
|
|||||||
}
|
}
|
||||||
Group g = getContactGroup(c);
|
Group g = getContactGroup(c);
|
||||||
storeMessageReplaceLatest(txn, g.getId(), updated);
|
storeMessageReplaceLatest(txn, g.getId(), updated);
|
||||||
|
txn.attach(new MailboxUpdateSentEvent(c.getId(), updated));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
db.addGroup(txn, localGroup);
|
db.addGroup(txn, localGroup);
|
||||||
@@ -143,14 +148,16 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
|
|||||||
clientHelper.setContactId(txn, g.getId(), c.getId());
|
clientHelper.setContactId(txn, g.getId(), c.getId());
|
||||||
MailboxProperties ownProps =
|
MailboxProperties ownProps =
|
||||||
mailboxSettingsManager.getOwnMailboxProperties(txn);
|
mailboxSettingsManager.getOwnMailboxProperties(txn);
|
||||||
|
MailboxUpdate u;
|
||||||
if (ownProps != null) {
|
if (ownProps != null) {
|
||||||
// We are paired, create and send props to the newly added contact
|
// We are paired, create and send props to the newly added contact
|
||||||
createAndSendUpdateWithMailbox(txn, c, ownProps.getServerSupports(),
|
u = createAndSendUpdateWithMailbox(txn, c,
|
||||||
ownProps.getOnion());
|
ownProps.getServerSupports(), ownProps.getOnion());
|
||||||
} else {
|
} else {
|
||||||
// Not paired, but we still want to get our clientSupports sent
|
// Not paired, but we still want to get our clientSupports sent
|
||||||
sendUpdateNoMailbox(txn, c);
|
u = sendUpdateNoMailbox(txn, c);
|
||||||
}
|
}
|
||||||
|
txn.attach(new MailboxUpdateSentEvent(c.getId(), u));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -159,18 +166,25 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mailboxPaired(Transaction txn, String ownOnion,
|
public void mailboxPaired(Transaction txn, MailboxProperties p)
|
||||||
List<MailboxVersion> serverSupports) throws DbException {
|
throws DbException {
|
||||||
|
Map<ContactId, MailboxUpdateWithMailbox> localUpdates = new HashMap<>();
|
||||||
for (Contact c : db.getContacts(txn)) {
|
for (Contact c : db.getContacts(txn)) {
|
||||||
createAndSendUpdateWithMailbox(txn, c, serverSupports, ownOnion);
|
MailboxUpdateWithMailbox u = createAndSendUpdateWithMailbox(txn, c,
|
||||||
|
p.getServerSupports(), p.getOnion());
|
||||||
|
localUpdates.put(c.getId(), u);
|
||||||
}
|
}
|
||||||
|
txn.attach(new MailboxPairedEvent(p, localUpdates));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mailboxUnpaired(Transaction txn) throws DbException {
|
public void mailboxUnpaired(Transaction txn) throws DbException {
|
||||||
|
Map<ContactId, MailboxUpdate> localUpdates = new HashMap<>();
|
||||||
for (Contact c : db.getContacts(txn)) {
|
for (Contact c : db.getContacts(txn)) {
|
||||||
sendUpdateNoMailbox(txn, c);
|
MailboxUpdate u = sendUpdateNoMailbox(txn, c);
|
||||||
|
localUpdates.put(c.getId(), u);
|
||||||
}
|
}
|
||||||
|
txn.attach(new MailboxUnpairedEvent(localUpdates));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -239,19 +253,19 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
|
|||||||
* supported Mailbox API version(s). All of which the contact needs to
|
* supported Mailbox API version(s). All of which the contact needs to
|
||||||
* communicate with our Mailbox.
|
* communicate with our Mailbox.
|
||||||
*/
|
*/
|
||||||
private void createAndSendUpdateWithMailbox(Transaction txn, Contact c,
|
private MailboxUpdateWithMailbox createAndSendUpdateWithMailbox(
|
||||||
List<MailboxVersion> serverSupports, String ownOnion)
|
Transaction txn, Contact c, List<MailboxVersion> serverSupports,
|
||||||
throws DbException {
|
String ownOnion) throws DbException {
|
||||||
String baseUrl = "http://" + ownOnion + ".onion"; // TODO
|
MailboxProperties properties = new MailboxProperties(ownOnion,
|
||||||
MailboxProperties properties = new MailboxProperties(baseUrl,
|
|
||||||
new MailboxAuthToken(crypto.generateUniqueId().getBytes()),
|
new MailboxAuthToken(crypto.generateUniqueId().getBytes()),
|
||||||
serverSupports,
|
serverSupports,
|
||||||
new MailboxFolderId(crypto.generateUniqueId().getBytes()),
|
new MailboxFolderId(crypto.generateUniqueId().getBytes()),
|
||||||
new MailboxFolderId(crypto.generateUniqueId().getBytes()));
|
new MailboxFolderId(crypto.generateUniqueId().getBytes()));
|
||||||
MailboxUpdate u =
|
MailboxUpdateWithMailbox u =
|
||||||
new MailboxUpdateWithMailbox(clientSupports, properties);
|
new MailboxUpdateWithMailbox(clientSupports, properties);
|
||||||
Group g = getContactGroup(c);
|
Group g = getContactGroup(c);
|
||||||
storeMessageReplaceLatest(txn, g.getId(), u);
|
storeMessageReplaceLatest(txn, g.getId(), u);
|
||||||
|
return u;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -260,11 +274,12 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
|
|||||||
* Mailbox that they can use. It still includes the list of Mailbox API
|
* Mailbox that they can use. It still includes the list of Mailbox API
|
||||||
* version(s) that we support as a client.
|
* version(s) that we support as a client.
|
||||||
*/
|
*/
|
||||||
private void sendUpdateNoMailbox(Transaction txn, Contact c)
|
private MailboxUpdate sendUpdateNoMailbox(Transaction txn, Contact c)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Group g = getContactGroup(c);
|
Group g = getContactGroup(c);
|
||||||
MailboxUpdate u = new MailboxUpdate(clientSupports);
|
MailboxUpdate u = new MailboxUpdate(clientSupports);
|
||||||
storeMessageReplaceLatest(txn, g.getId(), u);
|
storeMessageReplaceLatest(txn, g.getId(), u);
|
||||||
|
return u;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@@ -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);
|
||||||
|
delete(file);
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.WRITING_UPLOADING) return;
|
||||||
|
state = State.CHECKING_FOR_DATA;
|
||||||
|
apiCall = null;
|
||||||
|
this.file = null;
|
||||||
|
}
|
||||||
|
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,31 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
MailboxWorker createContactListWorkerForOwnMailbox(
|
||||||
|
ConnectivityChecker connectivityChecker,
|
||||||
|
MailboxProperties properties);
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
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.mailbox.MailboxUpdateManager;
|
||||||
|
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;
|
||||||
|
private final MailboxUpdateManager mailboxUpdateManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MailboxWorkerFactoryImpl(@IoExecutor Executor ioExecutor,
|
||||||
|
DatabaseComponent db,
|
||||||
|
Clock clock,
|
||||||
|
TaskScheduler taskScheduler,
|
||||||
|
EventBus eventBus,
|
||||||
|
MailboxApiCaller mailboxApiCaller,
|
||||||
|
MailboxApi mailboxApi,
|
||||||
|
MailboxFileManager mailboxFileManager,
|
||||||
|
MailboxUpdateManager mailboxUpdateManager) {
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.db = db;
|
||||||
|
this.clock = clock;
|
||||||
|
this.taskScheduler = taskScheduler;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.mailboxApiCaller = mailboxApiCaller;
|
||||||
|
this.mailboxApi = mailboxApi;
|
||||||
|
this.mailboxFileManager = mailboxFileManager;
|
||||||
|
this.mailboxUpdateManager = mailboxUpdateManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
return new OwnMailboxDownloadWorker(connectivityChecker,
|
||||||
|
reachabilityMonitor, mailboxApiCaller, mailboxApi,
|
||||||
|
mailboxFileManager, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MailboxWorker createContactListWorkerForOwnMailbox(
|
||||||
|
ConnectivityChecker connectivityChecker,
|
||||||
|
MailboxProperties properties) {
|
||||||
|
OwnMailboxContactListWorker worker = new OwnMailboxContactListWorker(
|
||||||
|
ioExecutor, db, eventBus, connectivityChecker, mailboxApiCaller,
|
||||||
|
mailboxApi, mailboxUpdateManager, properties);
|
||||||
|
eventBus.addListener(worker);
|
||||||
|
return worker;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
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.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class OwnMailboxClient implements MailboxClient {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(OwnMailboxClient.class.getName());
|
||||||
|
|
||||||
|
private final MailboxWorkerFactory workerFactory;
|
||||||
|
private final ConnectivityChecker connectivityChecker;
|
||||||
|
private final TorReachabilityMonitor reachabilityMonitor;
|
||||||
|
private final MailboxWorker contactListWorker;
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload workers: one worker per contact assigned for upload.
|
||||||
|
*/
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private final Map<ContactId, MailboxWorker> uploadWorkers = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download worker: shared between all contacts assigned for download.
|
||||||
|
* Null if no contacts are assigned for download.
|
||||||
|
*/
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@Nullable
|
||||||
|
private MailboxWorker downloadWorker = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IDs of contacts assigned for download, so that we know when to
|
||||||
|
* create/destroy the download worker.
|
||||||
|
*/
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private final Set<ContactId> assignedForDownload = new HashSet<>();
|
||||||
|
|
||||||
|
OwnMailboxClient(MailboxWorkerFactory workerFactory,
|
||||||
|
ConnectivityChecker connectivityChecker,
|
||||||
|
TorReachabilityMonitor reachabilityMonitor,
|
||||||
|
MailboxProperties properties) {
|
||||||
|
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||||
|
this.workerFactory = workerFactory;
|
||||||
|
this.connectivityChecker = connectivityChecker;
|
||||||
|
this.reachabilityMonitor = reachabilityMonitor;
|
||||||
|
contactListWorker = workerFactory.createContactListWorkerForOwnMailbox(
|
||||||
|
connectivityChecker, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
LOG.info("Started");
|
||||||
|
contactListWorker.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
LOG.info("Destroyed");
|
||||||
|
List<MailboxWorker> uploadWorkers;
|
||||||
|
MailboxWorker downloadWorker;
|
||||||
|
synchronized (lock) {
|
||||||
|
uploadWorkers = new ArrayList<>(this.uploadWorkers.values());
|
||||||
|
this.uploadWorkers.clear();
|
||||||
|
downloadWorker = this.downloadWorker;
|
||||||
|
this.downloadWorker = null;
|
||||||
|
}
|
||||||
|
// Destroy the workers (with apologies to Mr Marx and Mr Engels)
|
||||||
|
for (MailboxWorker worker : uploadWorkers) worker.destroy();
|
||||||
|
if (downloadWorker != null) downloadWorker.destroy();
|
||||||
|
contactListWorker.destroy();
|
||||||
|
// The connectivity checker belongs to the client, so it should be
|
||||||
|
// destroyed. The Tor reachability monitor is shared between clients,
|
||||||
|
// so it should not be destroyed
|
||||||
|
connectivityChecker.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void assignContactForUpload(ContactId contactId,
|
||||||
|
MailboxProperties properties, MailboxFolderId folderId) {
|
||||||
|
LOG.info("Contact assigned for upload");
|
||||||
|
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||||
|
MailboxWorker uploadWorker = workerFactory.createUploadWorker(
|
||||||
|
connectivityChecker, properties, folderId, contactId);
|
||||||
|
synchronized (lock) {
|
||||||
|
MailboxWorker old = uploadWorkers.put(contactId, uploadWorker);
|
||||||
|
if (old != null) throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
uploadWorker.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deassignContactForUpload(ContactId contactId) {
|
||||||
|
LOG.info("Contact deassigned for upload");
|
||||||
|
MailboxWorker uploadWorker;
|
||||||
|
synchronized (lock) {
|
||||||
|
uploadWorker = uploadWorkers.remove(contactId);
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
// Create a download worker if we don't already have one. The worker
|
||||||
|
// will use the API to discover which folders have files to download,
|
||||||
|
// so it doesn't need to track the set of assigned contacts
|
||||||
|
MailboxWorker toStart = null;
|
||||||
|
synchronized (lock) {
|
||||||
|
if (!assignedForDownload.add(contactId)) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
if (downloadWorker == null) {
|
||||||
|
toStart = workerFactory.createDownloadWorkerForOwnMailbox(
|
||||||
|
connectivityChecker, reachabilityMonitor, properties);
|
||||||
|
downloadWorker = toStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toStart != null) toStart.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deassignContactForDownload(ContactId contactId) {
|
||||||
|
LOG.info("Contact deassigned for download");
|
||||||
|
// If there are no more contacts assigned for download, destroy the
|
||||||
|
// download worker
|
||||||
|
MailboxWorker toDestroy = null;
|
||||||
|
synchronized (lock) {
|
||||||
|
if (!assignedForDownload.remove(contactId)) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
if (assignedForDownload.isEmpty()) {
|
||||||
|
toDestroy = downloadWorker;
|
||||||
|
downloadWorker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toDestroy != null) toDestroy.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,14 +4,17 @@ import org.briarproject.bramble.api.db.DbException;
|
|||||||
import org.briarproject.bramble.api.db.TransactionManager;
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
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.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
@@ -28,6 +31,7 @@ class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
|||||||
private final TransactionManager db;
|
private final TransactionManager db;
|
||||||
private final MailboxSettingsManager mailboxSettingsManager;
|
private final MailboxSettingsManager mailboxSettingsManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
OwnMailboxConnectivityChecker(Clock clock,
|
OwnMailboxConnectivityChecker(Clock clock,
|
||||||
MailboxApiCaller mailboxApiCaller,
|
MailboxApiCaller mailboxApiCaller,
|
||||||
MailboxApi mailboxApi,
|
MailboxApi mailboxApi,
|
||||||
@@ -55,11 +59,13 @@ class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
|||||||
private boolean checkConnectivityAndStoreResult(
|
private boolean checkConnectivityAndStoreResult(
|
||||||
MailboxProperties properties) throws DbException {
|
MailboxProperties properties) throws DbException {
|
||||||
try {
|
try {
|
||||||
if (!mailboxApi.checkStatus(properties)) throw new ApiException();
|
LOG.info("Checking whether own mailbox is reachable");
|
||||||
|
List<MailboxVersion> serverSupports =
|
||||||
|
mailboxApi.getServerSupports(properties);
|
||||||
LOG.info("Own mailbox is reachable");
|
LOG.info("Own mailbox is reachable");
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
db.transaction(false, txn -> mailboxSettingsManager
|
db.transaction(false, txn -> mailboxSettingsManager
|
||||||
.recordSuccessfulConnection(txn, now));
|
.recordSuccessfulConnection(txn, now, serverSupports));
|
||||||
// Call the observers and cache the result
|
// Call the observers and cache the result
|
||||||
onConnectivityCheckSucceeded(now);
|
onConnectivityCheckSucceeded(now);
|
||||||
return false; // Don't retry
|
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
|
||||||
|
LOG.warning("Contact does not exist");
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
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.mailbox.MailboxApi.ApiException;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
import static java.util.Collections.shuffle;
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class OwnMailboxDownloadWorker extends MailboxDownloadWorker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of files that will be downloaded before checking
|
||||||
|
* again for folders with available files. This ensures that if a file
|
||||||
|
* arrives during a download cycle, its folder will be checked within a
|
||||||
|
* reasonable amount of time even if some other folder has a very large
|
||||||
|
* number of files.
|
||||||
|
* <p>
|
||||||
|
* Package access for testing.
|
||||||
|
*/
|
||||||
|
static final int MAX_ROUND_ROBIN_FILES = 1000;
|
||||||
|
|
||||||
|
OwnMailboxDownloadWorker(
|
||||||
|
ConnectivityChecker connectivityChecker,
|
||||||
|
TorReachabilityMonitor torReachabilityMonitor,
|
||||||
|
MailboxApiCaller mailboxApiCaller,
|
||||||
|
MailboxApi mailboxApi,
|
||||||
|
MailboxFileManager mailboxFileManager,
|
||||||
|
MailboxProperties mailboxProperties) {
|
||||||
|
super(connectivityChecker, torReachabilityMonitor, mailboxApiCaller,
|
||||||
|
mailboxApi, mailboxFileManager, mailboxProperties);
|
||||||
|
if (!mailboxProperties.isOwner()) throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ApiCall createApiCallForDownloadCycle() {
|
||||||
|
return new SimpleApiCall(this::apiCallListFolders);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apiCallListFolders() throws IOException, ApiException {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state == State.DESTROYED) return;
|
||||||
|
}
|
||||||
|
LOG.info("Listing folders with available files");
|
||||||
|
List<MailboxFolderId> folders =
|
||||||
|
mailboxApi.getFolders(mailboxProperties);
|
||||||
|
if (folders.isEmpty()) onDownloadCycleFinished();
|
||||||
|
else listNextFolder(new LinkedList<>(folders), new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the next folder from `queue` and starts a task to list the
|
||||||
|
* files in the folder and add them to `available`.
|
||||||
|
*/
|
||||||
|
private void listNextFolder(Queue<MailboxFolderId> queue,
|
||||||
|
Map<MailboxFolderId, Queue<MailboxFile>> available) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state == State.DESTROYED) return;
|
||||||
|
MailboxFolderId folder = queue.remove();
|
||||||
|
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
|
||||||
|
apiCallListFolder(folder, queue, available)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apiCallListFolder(MailboxFolderId folder,
|
||||||
|
Queue<MailboxFolderId> queue,
|
||||||
|
Map<MailboxFolderId, Queue<MailboxFile>> available)
|
||||||
|
throws IOException, ApiException {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state == State.DESTROYED) return;
|
||||||
|
}
|
||||||
|
LOG.info("Listing folder");
|
||||||
|
try {
|
||||||
|
List<MailboxFile> files =
|
||||||
|
mailboxApi.getFiles(mailboxProperties, folder);
|
||||||
|
if (!files.isEmpty()) {
|
||||||
|
available.put(folder, new LinkedList<>(files));
|
||||||
|
}
|
||||||
|
} catch (TolerableFailureException e) {
|
||||||
|
LOG.warning("Folder does not exist");
|
||||||
|
}
|
||||||
|
if (queue.isEmpty()) {
|
||||||
|
LOG.info("Finished listing folders");
|
||||||
|
if (available.isEmpty()) onDownloadCycleFinished();
|
||||||
|
else createDownloadQueue(available);
|
||||||
|
} else {
|
||||||
|
listNextFolder(queue, available);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visits the given folders in round-robin order to create a queue of up to
|
||||||
|
* {@link #MAX_ROUND_ROBIN_FILES} to download.
|
||||||
|
*/
|
||||||
|
private void createDownloadQueue(
|
||||||
|
Map<MailboxFolderId, Queue<MailboxFile>> available) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state == State.DESTROYED) return;
|
||||||
|
}
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info(available.size() + " folders have available files");
|
||||||
|
}
|
||||||
|
Queue<FolderFile> queue = createRoundRobinQueue(available);
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Downloading " + queue.size() + " files");
|
||||||
|
}
|
||||||
|
downloadNextFile(queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Package access for testing
|
||||||
|
Queue<FolderFile> createRoundRobinQueue(
|
||||||
|
Map<MailboxFolderId, Queue<MailboxFile>> available) {
|
||||||
|
List<MailboxFolderId> roundRobin = new ArrayList<>(available.keySet());
|
||||||
|
// Shuffle the folders so we don't always favour the same folders
|
||||||
|
shuffle(roundRobin);
|
||||||
|
Queue<FolderFile> queue = new LinkedList<>();
|
||||||
|
while (queue.size() < MAX_ROUND_ROBIN_FILES && !available.isEmpty()) {
|
||||||
|
Iterator<MailboxFolderId> it = roundRobin.iterator();
|
||||||
|
while (queue.size() < MAX_ROUND_ROBIN_FILES && it.hasNext()) {
|
||||||
|
MailboxFolderId folder = it.next();
|
||||||
|
Queue<MailboxFile> files = available.get(folder);
|
||||||
|
MailboxFile file = files.remove();
|
||||||
|
queue.add(new FolderFile(folder, file.name));
|
||||||
|
if (files.isEmpty()) {
|
||||||
|
available.remove(folder);
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,17 +16,20 @@ import static org.briarproject.bramble.util.LogUtils.logException;
|
|||||||
* Convenience class for making simple API calls that don't return values.
|
* Convenience class for making simple API calls that don't return values.
|
||||||
*/
|
*/
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public abstract class SimpleApiCall implements ApiCall {
|
class SimpleApiCall implements ApiCall {
|
||||||
|
|
||||||
private static final Logger LOG = getLogger(SimpleApiCall.class.getName());
|
private static final Logger LOG = getLogger(SimpleApiCall.class.getName());
|
||||||
|
|
||||||
abstract void tryToCallApi()
|
private final Attempt attempt;
|
||||||
throws IOException, ApiException, TolerableFailureException;
|
|
||||||
|
SimpleApiCall(Attempt attempt) {
|
||||||
|
this.attempt = attempt;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean callApi() {
|
public boolean callApi() {
|
||||||
try {
|
try {
|
||||||
tryToCallApi();
|
attempt.tryToCallApi();
|
||||||
return false; // Succeeded, don't retry
|
return false; // Succeeded, don't retry
|
||||||
} catch (IOException | ApiException e) {
|
} catch (IOException | ApiException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
@@ -36,4 +39,17 @@ public abstract class SimpleApiCall implements ApiCall {
|
|||||||
return false; // Failed tolerably, don't retry
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ interface TorReachabilityMonitor {
|
|||||||
*/
|
*/
|
||||||
void addOneShotObserver(TorReachabilityObserver o);
|
void addOneShotObserver(TorReachabilityObserver o);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an observer that was added via
|
||||||
|
* {@link #addOneShotObserver(TorReachabilityObserver)}.
|
||||||
|
*/
|
||||||
|
void removeObserver(TorReachabilityObserver o);
|
||||||
|
|
||||||
interface TorReachabilityObserver {
|
interface TorReachabilityObserver {
|
||||||
|
|
||||||
void onTorReachable();
|
void onTorReachable();
|
||||||
|
|||||||
@@ -87,6 +87,14 @@ class TorReachabilityMonitorImpl
|
|||||||
if (callNow) o.onTorReachable();
|
if (callNow) o.onTorReachable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeObserver(TorReachabilityObserver o) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (destroyed) return;
|
||||||
|
observers.remove(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void eventOccurred(Event e) {
|
public void eventOccurred(Event e) {
|
||||||
if (e instanceof TransportActiveEvent) {
|
if (e instanceof TransportActiveEvent) {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for converting an onion address to an HTTP URL, allowing the
|
||||||
|
* conversion to be customised for integration tests.
|
||||||
|
*/
|
||||||
|
@NotNullByDefault
|
||||||
|
interface UrlConverter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a raw onion address, excluding the .onion suffix, into an
|
||||||
|
* HTTP URL.
|
||||||
|
*/
|
||||||
|
String convertOnionToBaseUrl(String onion);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class UrlConverterImpl implements UrlConverter {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
UrlConverterImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String convertOnionToBaseUrl(String onion) {
|
||||||
|
return "http://" + onion + ".onion";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -427,7 +427,13 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
|
|||||||
BdfList descriptor = new BdfList();
|
BdfList descriptor = new BdfList();
|
||||||
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
||||||
String address = getBluetoothAddress();
|
String address = getBluetoothAddress();
|
||||||
if (address != null) descriptor.add(macToBytes(address));
|
if (address != null) {
|
||||||
|
try {
|
||||||
|
descriptor.add(macToBytes(address));
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
return new BluetoothKeyAgreementListener(descriptor, ss);
|
return new BluetoothKeyAgreementListener(descriptor, ss);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,12 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.DAYS;
|
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_LATENCY;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class MailboxPluginFactory implements SimplexPluginFactory {
|
public class MailboxPluginFactory implements SimplexPluginFactory {
|
||||||
|
|
||||||
private static final long MAX_LATENCY = DAYS.toMillis(14);
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MailboxPluginFactory() {
|
MailboxPluginFactory() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,8 @@ class RemovableDriveManagerImpl
|
|||||||
@Override
|
@Override
|
||||||
public boolean isWriterTaskNeeded(ContactId c) throws DbException {
|
public boolean isWriterTaskNeeded(ContactId c) throws DbException {
|
||||||
return db.transactionWithResult(true, txn ->
|
return db.transactionWithResult(true, txn ->
|
||||||
db.containsAnythingToSend(txn, c, MAX_LATENCY, true));
|
db.containsAcksToSend(txn, c) ||
|
||||||
|
db.containsMessagesToSend(txn, c, MAX_LATENCY, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
if (ip.length == 16) addrs.add(InetAddress.getByAddress(ip));
|
if (ip.length == 16) addrs.add(InetAddress.getByAddress(ip));
|
||||||
}
|
}
|
||||||
return addrs;
|
return addrs;
|
||||||
} catch (IllegalArgumentException | UnknownHostException e) {
|
} catch (FormatException | UnknownHostException e) {
|
||||||
return emptyList();
|
return emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public interface CircumventionProvider {
|
|||||||
* Countries where bridge connections are likely to work.
|
* Countries where bridge connections are likely to work.
|
||||||
* Should be a subset of {@link #BLOCKED} and the union of
|
* Should be a subset of {@link #BLOCKED} and the union of
|
||||||
* {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and
|
* {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and
|
||||||
* {@link #MEEK_BRIDGES}.
|
* {@link #DPI_BRIDGES}.
|
||||||
*/
|
*/
|
||||||
String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
|
String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
|
||||||
|
|
||||||
@@ -44,10 +44,10 @@ public interface CircumventionProvider {
|
|||||||
String[] NON_DEFAULT_BRIDGES = {"BY", "RU", "TM"};
|
String[] NON_DEFAULT_BRIDGES = {"BY", "RU", "TM"};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Countries where obfs4 and vanilla bridges won't work and meek is needed.
|
* Countries where vanilla bridges are blocked via DPI but non-default
|
||||||
* Should be a subset of {@link #BRIDGES}.
|
* 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.
|
* 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 javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
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.api.nullsafety.NullSafety.requireNonNull;
|
||||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
|
||||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
|
||||||
@@ -35,8 +34,8 @@ class CircumventionProviderImpl implements CircumventionProvider {
|
|||||||
new HashSet<>(asList(DEFAULT_BRIDGES));
|
new HashSet<>(asList(DEFAULT_BRIDGES));
|
||||||
private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES =
|
private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES =
|
||||||
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
|
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
|
||||||
private static final Set<String> MEEK_COUNTRIES =
|
private static final Set<String> DPI_COUNTRIES =
|
||||||
new HashSet<>(asList(MEEK_BRIDGES));
|
new HashSet<>(asList(DPI_BRIDGES));
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CircumventionProviderImpl() {
|
CircumventionProviderImpl() {
|
||||||
@@ -58,8 +57,8 @@ class CircumventionProviderImpl implements CircumventionProvider {
|
|||||||
return asList(DEFAULT_OBFS4, VANILLA);
|
return asList(DEFAULT_OBFS4, VANILLA);
|
||||||
} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
|
} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
|
||||||
return asList(NON_DEFAULT_OBFS4, VANILLA);
|
return asList(NON_DEFAULT_OBFS4, VANILLA);
|
||||||
} else if (MEEK_COUNTRIES.contains(countryCode)) {
|
} else if (DPI_COUNTRIES.contains(countryCode)) {
|
||||||
return singletonList(MEEK);
|
return asList(NON_DEFAULT_OBFS4, MEEK);
|
||||||
} else {
|
} else {
|
||||||
return asList(DEFAULT_OBFS4, VANILLA);
|
return asList(DEFAULT_OBFS4, VANILLA);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ import javax.annotation.concurrent.ThreadSafe;
|
|||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
@@ -93,9 +94,7 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
|
|||||||
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
|
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
|
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
|
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
|
||||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
|
|
||||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
|
||||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
|
|
||||||
import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
|
import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
|
||||||
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
||||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||||
@@ -107,7 +106,7 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
|||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
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 = {
|
private static final String[] EVENTS = {
|
||||||
"CIRC",
|
"CIRC",
|
||||||
@@ -124,7 +123,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
|
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
|
||||||
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
|
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 Executor connectionStatusExecutor;
|
||||||
private final NetworkManager networkManager;
|
private final NetworkManager networkManager;
|
||||||
private final LocationUtils locationUtils;
|
private final LocationUtils locationUtils;
|
||||||
@@ -238,8 +238,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
// Load the settings
|
// Load the settings
|
||||||
settings = callback.getSettings();
|
settings = callback.getSettings();
|
||||||
// Install or update the assets if necessary
|
try {
|
||||||
if (!assetsAreUpToDate()) installAssets();
|
// Install or update the assets if necessary
|
||||||
|
if (!assetsAreUpToDate()) installAssets();
|
||||||
|
// Start from the default config every time
|
||||||
|
extract(getConfigInputStream(), configFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new PluginException(e);
|
||||||
|
}
|
||||||
if (cookieFile.exists() && !cookieFile.delete())
|
if (cookieFile.exists() && !cookieFile.delete())
|
||||||
LOG.warning("Old auth cookie not deleted");
|
LOG.warning("Old auth cookie not deleted");
|
||||||
// Start a new Tor process
|
// Start a new Tor process
|
||||||
@@ -254,34 +260,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
Map<String, String> env = pb.environment();
|
Map<String, String> env = pb.environment();
|
||||||
env.put("HOME", torDirectory.getAbsolutePath());
|
env.put("HOME", torDirectory.getAbsolutePath());
|
||||||
pb.directory(torDirectory);
|
pb.directory(torDirectory);
|
||||||
|
pb.redirectErrorStream(true);
|
||||||
try {
|
try {
|
||||||
torProcess = pb.start();
|
torProcess = pb.start();
|
||||||
} catch (SecurityException | IOException e) {
|
} catch (SecurityException | IOException e) {
|
||||||
throw new PluginException(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 {
|
try {
|
||||||
// Wait for the process to detach or exit
|
// Wait for the Tor process to start
|
||||||
int exit = torProcess.waitFor();
|
waitForTorToStart(torProcess);
|
||||||
if (exit != 0) {
|
|
||||||
if (LOG.isLoggable(WARNING))
|
|
||||||
LOG.warning("Tor exited with value " + exit);
|
|
||||||
throw new PluginException();
|
|
||||||
}
|
|
||||||
// Wait for the auth cookie file to be created/updated
|
// Wait for the auth cookie file to be created/updated
|
||||||
long start = clock.currentTimeMillis();
|
long start = clock.currentTimeMillis();
|
||||||
while (cookieFile.length() < 32) {
|
while (cookieFile.length() < 32) {
|
||||||
@@ -320,7 +307,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
info = controlConnection.getInfo("status/circuit-established");
|
info = controlConnection.getInfo("status/circuit-established");
|
||||||
if ("1".equals(info)) {
|
if ("1".equals(info)) {
|
||||||
LOG.info("Tor has already built a circuit");
|
LOG.info("Tor has already built a circuit");
|
||||||
state.getAndSetCircuitBuilt(true);
|
state.setCircuitBuilt(true);
|
||||||
}
|
}
|
||||||
} catch (TorNotRunningException e) {
|
} catch (TorNotRunningException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
@@ -339,25 +326,20 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
return doneFile.lastModified() > getLastUpdateTime();
|
return doneFile.lastModified() > getLastUpdateTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void installAssets() throws PluginException {
|
private void installAssets() throws IOException {
|
||||||
try {
|
// The done file may already exist from a previous installation
|
||||||
// The done file may already exist from a previous installation
|
//noinspection ResultOfMethodCallIgnored
|
||||||
//noinspection ResultOfMethodCallIgnored
|
doneFile.delete();
|
||||||
doneFile.delete();
|
// The GeoIP file may exist from a previous installation - we can
|
||||||
// The GeoIP file may exist from a previous installation - we can
|
// save some space by deleting it.
|
||||||
// save some space by deleting it.
|
// TODO: Remove after a reasonable migration period
|
||||||
// TODO: Remove after a reasonable migration period
|
// (added 2022-03-29)
|
||||||
// (added 2022-03-29)
|
//noinspection ResultOfMethodCallIgnored
|
||||||
//noinspection ResultOfMethodCallIgnored
|
geoIpFile.delete();
|
||||||
geoIpFile.delete();
|
installTorExecutable();
|
||||||
installTorExecutable();
|
installObfs4Executable();
|
||||||
installObfs4Executable();
|
if (!doneFile.createNewFile())
|
||||||
extract(getConfigInputStream(), configFile);
|
LOG.warning("Failed to create done file");
|
||||||
if (!doneFile.createNewFile())
|
|
||||||
LOG.warning("Failed to create done file");
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new PluginException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void extract(InputStream in, File dest) throws IOException {
|
protected void extract(InputStream in, File dest) throws IOException {
|
||||||
@@ -397,7 +379,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
return zin;
|
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(name);
|
||||||
strb.append(" ");
|
strb.append(" ");
|
||||||
strb.append(value);
|
strb.append(value);
|
||||||
@@ -405,13 +387,21 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private InputStream getConfigInputStream() {
|
private InputStream getConfigInputStream() {
|
||||||
|
File dataDirectory = new File(torDirectory, ".tor");
|
||||||
StringBuilder strb = new StringBuilder();
|
StringBuilder strb = new StringBuilder();
|
||||||
append(strb, "ControlPort", torControlPort);
|
append(strb, "ControlPort", torControlPort);
|
||||||
append(strb, "CookieAuthentication", 1);
|
append(strb, "CookieAuthentication", 1);
|
||||||
|
append(strb, "DataDirectory", dataDirectory.getAbsolutePath());
|
||||||
append(strb, "DisableNetwork", 1);
|
append(strb, "DisableNetwork", 1);
|
||||||
append(strb, "RunAsDaemon", 1);
|
append(strb, "RunAsDaemon", 1);
|
||||||
append(strb, "SafeSocks", 1);
|
append(strb, "SafeSocks", 1);
|
||||||
append(strb, "SocksPort", torSocksPort);
|
append(strb, "SocksPort", torSocksPort);
|
||||||
|
strb.append("GeoIPFile\n");
|
||||||
|
strb.append("GeoIPv6File\n");
|
||||||
|
append(strb, "ConnectionPadding", 0);
|
||||||
|
String obfs4Path = getObfs4ExecutableFile().getAbsolutePath();
|
||||||
|
append(strb, "ClientTransportPlugin obfs4 exec", obfs4Path);
|
||||||
|
append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path);
|
||||||
//noinspection CharsetObjectCanBeUsed
|
//noinspection CharsetObjectCanBeUsed
|
||||||
return new ByteArrayInputStream(
|
return new ByteArrayInputStream(
|
||||||
strb.toString().getBytes(Charset.forName("UTF-8")));
|
strb.toString().getBytes(Charset.forName("UTF-8")));
|
||||||
@@ -442,6 +432,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() {
|
private void bind() {
|
||||||
ioExecutor.execute(() -> {
|
ioExecutor.execute(() -> {
|
||||||
// If there's already a port number stored in config, reuse it
|
// If there's already a port number stored in config, reuse it
|
||||||
@@ -544,7 +551,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void enableNetwork(boolean enable) throws IOException {
|
protected void enableNetwork(boolean enable) throws IOException {
|
||||||
state.enableNetwork(enable);
|
if (!state.enableNetwork(enable)) return; // Unchanged
|
||||||
try {
|
try {
|
||||||
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
|
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
|
||||||
} catch (TorNotRunningException e) {
|
} catch (TorNotRunningException e) {
|
||||||
@@ -552,28 +559,20 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enableBridges(boolean enable, List<BridgeType> bridgeTypes)
|
private void enableBridges(List<BridgeType> bridgeTypes)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
if (!state.setBridgeTypes(bridgeTypes)) return; // Unchanged
|
||||||
try {
|
try {
|
||||||
if (enable) {
|
if (bridgeTypes.isEmpty()) {
|
||||||
|
controlConnection.setConf("UseBridges", "0");
|
||||||
|
controlConnection.resetConf(singletonList("Bridge"));
|
||||||
|
} else {
|
||||||
Collection<String> conf = new ArrayList<>();
|
Collection<String> conf = new ArrayList<>();
|
||||||
conf.add("UseBridges 1");
|
conf.add("UseBridges 1");
|
||||||
File obfs4File = getObfs4ExecutableFile();
|
|
||||||
if (bridgeTypes.contains(MEEK)) {
|
|
||||||
conf.add("ClientTransportPlugin meek_lite exec " +
|
|
||||||
obfs4File.getAbsolutePath());
|
|
||||||
}
|
|
||||||
if (bridgeTypes.contains(DEFAULT_OBFS4) ||
|
|
||||||
bridgeTypes.contains(NON_DEFAULT_OBFS4)) {
|
|
||||||
conf.add("ClientTransportPlugin obfs4 exec " +
|
|
||||||
obfs4File.getAbsolutePath());
|
|
||||||
}
|
|
||||||
for (BridgeType bridgeType : bridgeTypes) {
|
for (BridgeType bridgeType : bridgeTypes) {
|
||||||
conf.addAll(circumventionProvider.getBridges(bridgeType));
|
conf.addAll(circumventionProvider.getBridges(bridgeType));
|
||||||
}
|
}
|
||||||
controlConnection.setConf(conf);
|
controlConnection.setConf(conf);
|
||||||
} else {
|
|
||||||
controlConnection.setConf("UseBridges", "0");
|
|
||||||
}
|
}
|
||||||
} catch (TorNotRunningException e) {
|
} catch (TorNotRunningException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
@@ -587,7 +586,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
if (controlSocket != null && controlConnection != null) {
|
if (controlSocket != null && controlConnection != null) {
|
||||||
try {
|
try {
|
||||||
LOG.info("Stopping Tor");
|
LOG.info("Stopping Tor");
|
||||||
controlConnection.setConf("DisableNetwork", "1");
|
|
||||||
controlConnection.shutdownTor("TERM");
|
controlConnection.shutdownTor("TERM");
|
||||||
controlSocket.close();
|
controlSocket.close();
|
||||||
} catch (TorNotRunningException e) {
|
} catch (TorNotRunningException e) {
|
||||||
@@ -755,7 +753,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
public void circuitStatus(String status, String id, String path) {
|
public void circuitStatus(String status, String id, String path) {
|
||||||
// In case of races between receiving CIRCUIT_ESTABLISHED and setting
|
// In case of races between receiving CIRCUIT_ESTABLISHED and setting
|
||||||
// DisableNetwork, set our circuitBuilt flag if not already set
|
// DisableNetwork, set our circuitBuilt flag if not already set
|
||||||
if (status.equals("BUILT") && !state.getAndSetCircuitBuilt(true)) {
|
if (status.equals("BUILT") && state.setCircuitBuilt(true)) {
|
||||||
LOG.info("Circuit built");
|
LOG.info("Circuit built");
|
||||||
backoff.reset();
|
backoff.reset();
|
||||||
}
|
}
|
||||||
@@ -802,6 +800,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void controlConnectionClosed() {
|
||||||
|
if (state.isTorRunning()) {
|
||||||
|
throw new RuntimeException("Control connection closed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String removeSeverity(String msg) {
|
private String removeSeverity(String msg) {
|
||||||
return msg.replaceFirst("[^ ]+ ", "");
|
return msg.replaceFirst("[^ ]+ ", "");
|
||||||
}
|
}
|
||||||
@@ -812,12 +817,12 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
state.setBootstrapped();
|
state.setBootstrapped();
|
||||||
backoff.reset();
|
backoff.reset();
|
||||||
} else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
|
} else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
|
||||||
if (!state.getAndSetCircuitBuilt(true)) {
|
if (state.setCircuitBuilt(true)) {
|
||||||
LOG.info("Circuit built");
|
LOG.info("Circuit built");
|
||||||
backoff.reset();
|
backoff.reset();
|
||||||
}
|
}
|
||||||
} else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
|
} else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
|
||||||
if (state.getAndSetCircuitBuilt(false)) {
|
if (state.setCircuitBuilt(false)) {
|
||||||
LOG.info("Circuit not built");
|
LOG.info("Circuit not built");
|
||||||
// TODO: Disable and re-enable network to prompt Tor to rebuild
|
// TODO: Disable and re-enable network to prompt Tor to rebuild
|
||||||
// its guard/bridge connections? This will also close any
|
// its guard/bridge connections? This will also close any
|
||||||
@@ -908,10 +913,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int reasonsDisabled = 0;
|
int reasonsDisabled = 0;
|
||||||
boolean enableNetwork = false, enableBridges = false;
|
boolean enableNetwork = false, enableConnectionPadding = false;
|
||||||
boolean enableConnectionPadding = false;
|
List<BridgeType> bridgeTypes = emptyList();
|
||||||
List<BridgeType> bridgeTypes =
|
|
||||||
circumventionProvider.getSuitableBridgeTypes(country);
|
|
||||||
|
|
||||||
if (!online) {
|
if (!online) {
|
||||||
LOG.info("Disabling network, device is offline");
|
LOG.info("Disabling network, device is offline");
|
||||||
@@ -940,8 +943,12 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
enableNetwork = true;
|
enableNetwork = true;
|
||||||
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
|
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
|
||||||
(automatic && bridgesWork)) {
|
(automatic && bridgesWork)) {
|
||||||
if (ipv6Only) bridgeTypes = singletonList(MEEK);
|
if (ipv6Only) {
|
||||||
enableBridges = true;
|
bridgeTypes = singletonList(MEEK);
|
||||||
|
} else {
|
||||||
|
bridgeTypes = circumventionProvider
|
||||||
|
.getSuitableBridgeTypes(country);
|
||||||
|
}
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info("Using bridge types " + bridgeTypes);
|
LOG.info("Using bridge types " + bridgeTypes);
|
||||||
}
|
}
|
||||||
@@ -961,9 +968,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (enableNetwork) {
|
if (enableNetwork) {
|
||||||
enableBridges(enableBridges, bridgeTypes);
|
enableBridges(bridgeTypes);
|
||||||
enableConnectionPadding(enableConnectionPadding);
|
enableConnectionPadding(enableConnectionPadding);
|
||||||
useIpv6(ipv6Only);
|
enableIpv6(ipv6Only);
|
||||||
}
|
}
|
||||||
enableNetwork(enableNetwork);
|
enableNetwork(enableNetwork);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -973,6 +980,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void enableConnectionPadding(boolean enable) throws IOException {
|
private void enableConnectionPadding(boolean enable) throws IOException {
|
||||||
|
if (!state.enableConnectionPadding(enable)) return; // Unchanged
|
||||||
try {
|
try {
|
||||||
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
|
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
|
||||||
} catch (TorNotRunningException e) {
|
} catch (TorNotRunningException e) {
|
||||||
@@ -980,10 +988,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void useIpv6(boolean ipv6Only) throws IOException {
|
private void enableIpv6(boolean enable) throws IOException {
|
||||||
|
if (!state.enableIpv6(enable)) return; // Unchanged
|
||||||
try {
|
try {
|
||||||
controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1");
|
controlConnection.setConf("ClientUseIPv4", enable ? "0" : "1");
|
||||||
controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0");
|
controlConnection.setConf("ClientUseIPv6", enable ? "1" : "0");
|
||||||
} catch (TorNotRunningException e) {
|
} catch (TorNotRunningException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@@ -998,6 +1007,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
stopped = false,
|
stopped = false,
|
||||||
networkInitialised = false,
|
networkInitialised = false,
|
||||||
networkEnabled = false,
|
networkEnabled = false,
|
||||||
|
paddingEnabled = false,
|
||||||
|
ipv6Enabled = false,
|
||||||
bootstrapped = false,
|
bootstrapped = false,
|
||||||
circuitBuilt = false,
|
circuitBuilt = false,
|
||||||
settingsChecked = false;
|
settingsChecked = false;
|
||||||
@@ -1012,6 +1023,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private int orConnectionsConnected = 0;
|
private int orConnectionsConnected = 0;
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
private List<BridgeType> bridgeTypes = emptyList();
|
||||||
|
|
||||||
private synchronized void setStarted() {
|
private synchronized void setStarted() {
|
||||||
started = true;
|
started = true;
|
||||||
callback.pluginStateChanged(getState());
|
callback.pluginStateChanged(getState());
|
||||||
@@ -1032,28 +1046,66 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void setBootstrapped() {
|
private synchronized void setBootstrapped() {
|
||||||
|
boolean wasBootstrapped = bootstrapped;
|
||||||
bootstrapped = true;
|
bootstrapped = true;
|
||||||
callback.pluginStateChanged(getState());
|
if (!wasBootstrapped) callback.pluginStateChanged(getState());
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized boolean getAndSetCircuitBuilt(boolean built) {
|
/**
|
||||||
boolean old = circuitBuilt;
|
* Sets the `circuitBuilt` flag and returns true if the flag has
|
||||||
|
* changed.
|
||||||
|
*/
|
||||||
|
private synchronized boolean setCircuitBuilt(boolean built) {
|
||||||
|
if (built == circuitBuilt) return false; // Unchanged
|
||||||
circuitBuilt = built;
|
circuitBuilt = built;
|
||||||
if (built != old) callback.pluginStateChanged(getState());
|
callback.pluginStateChanged(getState());
|
||||||
return old;
|
return true; // Changed
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void enableNetwork(boolean enable) {
|
/**
|
||||||
|
* Sets the `networkEnabled` flag and returns true if the flag has
|
||||||
|
* changed.
|
||||||
|
*/
|
||||||
|
private synchronized boolean enableNetwork(boolean enable) {
|
||||||
|
boolean wasInitialised = networkInitialised;
|
||||||
|
boolean wasEnabled = networkEnabled;
|
||||||
networkInitialised = true;
|
networkInitialised = true;
|
||||||
networkEnabled = enable;
|
networkEnabled = enable;
|
||||||
if (!enable) circuitBuilt = false;
|
if (!enable) circuitBuilt = false;
|
||||||
callback.pluginStateChanged(getState());
|
if (!wasInitialised || enable != wasEnabled) {
|
||||||
|
callback.pluginStateChanged(getState());
|
||||||
|
}
|
||||||
|
return enable != wasEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void setReasonsDisabled(int reasonsDisabled) {
|
/**
|
||||||
|
* Sets the `paddingEnabled` flag and returns true if the flag has
|
||||||
|
* changed. Doesn't affect getState().
|
||||||
|
*/
|
||||||
|
private synchronized boolean enableConnectionPadding(boolean enable) {
|
||||||
|
if (enable == paddingEnabled) return false; // Unchanged
|
||||||
|
paddingEnabled = enable;
|
||||||
|
return true; // Changed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the `ipv6Enabled` flag and returns true if the flag has
|
||||||
|
* changed. Doesn't affect getState().
|
||||||
|
*/
|
||||||
|
private synchronized boolean enableIpv6(boolean enable) {
|
||||||
|
if (enable == ipv6Enabled) return false; // Unchanged
|
||||||
|
ipv6Enabled = enable;
|
||||||
|
return true; // Changed
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void setReasonsDisabled(int reasons) {
|
||||||
|
boolean wasChecked = settingsChecked;
|
||||||
settingsChecked = true;
|
settingsChecked = true;
|
||||||
this.reasonsDisabled = reasonsDisabled;
|
int oldReasons = reasonsDisabled;
|
||||||
callback.pluginStateChanged(getState());
|
reasonsDisabled = reasons;
|
||||||
|
if (!wasChecked || reasons != oldReasons) {
|
||||||
|
callback.pluginStateChanged(getState());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Doesn't affect getState()
|
// Doesn't affect getState()
|
||||||
@@ -1068,6 +1120,17 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
if (serverSocket == ss) serverSocket = null;
|
if (serverSocket == ss) serverSocket = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the list of bridge types being used and returns true if the
|
||||||
|
* list has changed. The list is empty if bridges are disabled.
|
||||||
|
* Doesn't affect getState().
|
||||||
|
*/
|
||||||
|
private synchronized boolean setBridgeTypes(List<BridgeType> types) {
|
||||||
|
if (types.equals(bridgeTypes)) return false; // Unchanged
|
||||||
|
bridgeTypes = types;
|
||||||
|
return true; // Changed
|
||||||
|
}
|
||||||
|
|
||||||
private synchronized State getState() {
|
private synchronized State getState() {
|
||||||
if (!started || stopped || !settingsChecked) {
|
if (!started || stopped || !settingsChecked) {
|
||||||
return STARTING_STOPPING;
|
return STARTING_STOPPING;
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import static java.util.logging.Level.WARNING;
|
|||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
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.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_IDS;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS;
|
import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS;
|
||||||
@@ -235,8 +236,10 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
generateOffer();
|
generateOffer();
|
||||||
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
||||||
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
||||||
if (g.getAffectedContacts().contains(contactId))
|
if (g.getVisibility() == SHARED &&
|
||||||
|
g.getAffectedContacts().contains(contactId)) {
|
||||||
generateOffer();
|
generateOffer();
|
||||||
|
}
|
||||||
} else if (e instanceof MessageRequestedEvent) {
|
} else if (e instanceof MessageRequestedEvent) {
|
||||||
if (((MessageRequestedEvent) e).getContactId().equals(contactId))
|
if (((MessageRequestedEvent) e).getContactId().equals(contactId))
|
||||||
generateBatch();
|
generateBatch();
|
||||||
@@ -310,7 +313,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
Collection<Message> batch =
|
Collection<Message> batch =
|
||||||
db.generateRequestedBatch(txn, contactId,
|
db.generateRequestedBatch(txn, contactId,
|
||||||
BATCH_CAPACITY, maxLatency);
|
BATCH_CAPACITY, maxLatency);
|
||||||
setNextSendTime(db.getNextSendTime(txn, contactId));
|
setNextSendTime(db.getNextSendTime(txn, contactId,
|
||||||
|
maxLatency));
|
||||||
return batch;
|
return batch;
|
||||||
});
|
});
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
@@ -353,7 +357,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
Offer o = db.transactionWithNullableResult(false, txn -> {
|
Offer o = db.transactionWithNullableResult(false, txn -> {
|
||||||
Offer offer = db.generateOffer(txn, contactId,
|
Offer offer = db.generateOffer(txn, contactId,
|
||||||
MAX_MESSAGE_IDS, maxLatency);
|
MAX_MESSAGE_IDS, maxLatency);
|
||||||
setNextSendTime(db.getNextSendTime(txn, contactId));
|
setNextSendTime(db.getNextSendTime(txn, contactId,
|
||||||
|
maxLatency));
|
||||||
return offer;
|
return offer;
|
||||||
});
|
});
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
|
|||||||
@@ -7,14 +7,16 @@ import org.briarproject.bramble.api.event.EventBus;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
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.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
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.SyncRecordWriter;
|
||||||
import org.briarproject.bramble.api.transport.StreamWriter;
|
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
@@ -29,7 +31,7 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LEN
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SimplexOutgoingSession} for sending and acking messages via a
|
* 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
|
* 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
|
* recorded in the DB as sent or acked after the file has been successfully
|
||||||
* uploaded to the mailbox.
|
* uploaded to the mailbox.
|
||||||
@@ -41,7 +43,7 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
getLogger(MailboxOutgoingSession.class.getName());
|
getLogger(MailboxOutgoingSession.class.getName());
|
||||||
|
|
||||||
private final DeferredSendHandler deferredSendHandler;
|
private final OutgoingSessionRecord sessionRecord;
|
||||||
private final long initialCapacity;
|
private final long initialCapacity;
|
||||||
|
|
||||||
MailboxOutgoingSession(DatabaseComponent db,
|
MailboxOutgoingSession(DatabaseComponent db,
|
||||||
@@ -51,36 +53,42 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
|
|||||||
long maxLatency,
|
long maxLatency,
|
||||||
StreamWriter streamWriter,
|
StreamWriter streamWriter,
|
||||||
SyncRecordWriter recordWriter,
|
SyncRecordWriter recordWriter,
|
||||||
DeferredSendHandler deferredSendHandler,
|
OutgoingSessionRecord sessionRecord,
|
||||||
long capacity) {
|
long capacity) {
|
||||||
super(db, eventBus, contactId, transportId, maxLatency, streamWriter,
|
super(db, eventBus, contactId, transportId, maxLatency, streamWriter,
|
||||||
recordWriter);
|
recordWriter);
|
||||||
this.deferredSendHandler = deferredSendHandler;
|
this.sessionRecord = sessionRecord;
|
||||||
this.initialCapacity = capacity;
|
this.initialCapacity = capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void sendAcks() throws DbException, IOException {
|
void sendAcks() throws DbException, IOException {
|
||||||
while (!isInterrupted()) {
|
List<MessageId> idsToAck = loadMessageIdsToAck();
|
||||||
Collection<MessageId> idsToAck = loadMessageIdsToAck();
|
int idsSent = 0;
|
||||||
if (idsToAck.isEmpty()) break;
|
while (idsSent < idsToAck.size() && !isInterrupted()) {
|
||||||
recordWriter.writeAck(new Ack(idsToAck));
|
int idsRemaining = idsToAck.size() - idsSent;
|
||||||
deferredSendHandler.onAckSent(idsToAck);
|
long capacity = getRemainingCapacity();
|
||||||
|
long idCapacity =
|
||||||
|
(capacity - RECORD_HEADER_BYTES) / MessageId.LENGTH;
|
||||||
|
if (idCapacity == 0) break; // Out of capacity
|
||||||
|
int idsInRecord = (int) min(idCapacity, MAX_MESSAGE_IDS);
|
||||||
|
int idsToSend = min(idsRemaining, idsInRecord);
|
||||||
|
List<MessageId> acked =
|
||||||
|
idsToAck.subList(idsSent, idsSent + idsToSend);
|
||||||
|
recordWriter.writeAck(new Ack(acked));
|
||||||
|
sessionRecord.onAckSent(acked);
|
||||||
LOG.info("Sent ack");
|
LOG.info("Sent ack");
|
||||||
|
idsSent += idsToSend;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<MessageId> loadMessageIdsToAck() throws DbException {
|
private List<MessageId> loadMessageIdsToAck() throws DbException {
|
||||||
long idCapacity = (getRemainingCapacity() - RECORD_HEADER_BYTES)
|
|
||||||
/ MessageId.LENGTH;
|
|
||||||
if (idCapacity <= 0) return emptyList(); // Out of capacity
|
|
||||||
int maxMessageIds = (int) min(idCapacity, MAX_MESSAGE_IDS);
|
|
||||||
Collection<MessageId> ids = db.transactionWithResult(true, txn ->
|
Collection<MessageId> ids = db.transactionWithResult(true, txn ->
|
||||||
db.getMessagesToAck(txn, contactId, maxMessageIds));
|
db.getMessagesToAck(txn, contactId));
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info(ids.size() + " messages to ack");
|
LOG.info(ids.size() + " messages to ack");
|
||||||
}
|
}
|
||||||
return ids;
|
return new ArrayList<>(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getRemainingCapacity() {
|
private long getRemainingCapacity() {
|
||||||
@@ -96,7 +104,7 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
|
|||||||
db.getMessageToSend(txn, contactId, m, maxLatency, false));
|
db.getMessageToSend(txn, contactId, m, maxLatency, false));
|
||||||
if (message == null) continue; // No longer shared
|
if (message == null) continue; // No longer shared
|
||||||
recordWriter.writeMessage(message);
|
recordWriter.writeMessage(message);
|
||||||
deferredSendHandler.onMessageSent(m);
|
sessionRecord.onMessageSent(m);
|
||||||
LOG.info("Sent message");
|
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.event.EventBus;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
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.Priority;
|
||||||
import org.briarproject.bramble.api.sync.PriorityHandler;
|
import org.briarproject.bramble.api.sync.PriorityHandler;
|
||||||
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
||||||
@@ -25,6 +26,8 @@ import javax.annotation.Nullable;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_FILE_PAYLOAD_BYTES;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class SyncSessionFactoryImpl implements SyncSessionFactory {
|
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
|
@Override
|
||||||
public SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
public SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
||||||
long maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
long maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
||||||
|
|||||||
@@ -13,18 +13,24 @@ d Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=T
|
|||||||
d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
|
d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
|
||||||
n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cert=t8tA9q2AeGlmp/dO6oW9bkY5RqqmvqjArCEM9wjJoDnk6XtnaejkF0JTA7VamdyOzcvuBg iat-mode=0
|
n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cert=t8tA9q2AeGlmp/dO6oW9bkY5RqqmvqjArCEM9wjJoDnk6XtnaejkF0JTA7VamdyOzcvuBg iat-mode=0
|
||||||
n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
|
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 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 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 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 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 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
|
||||||
v Bridge 135.181.113.164:54444 74AF4CCA614C454B7D3E81FF8BACD78CEBC7D7DE
|
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
|
||||||
|
n Bridge obfs4 85.212.104.44:27623 9000BDF7063F808E554C320545BCF264222CDFE9 cert=eKSXeJLnFv6RDAap5vM2sqsi8GIWhlMi5prs8hu0HTKWTH9KjZhhoj3Cg/gIuoL8n8TXGQ iat-mode=0
|
||||||
|
n Bridge obfs4 104.168.68.90:443 ED55B3C321E44EA7E50EF568C8A63CF75E89A58C cert=fgonxDvltTp8nmcOE9sUG94eOAALxETVVXAwnTZJLPpf7rjPuTp+abKl4VyFkxfcLRr5KQ iat-mode=0
|
||||||
|
n Bridge obfs4 158.247.207.151:443 6170ADBBB6C1859A8E7E4416BB8AB3AF471AE47F cert=Od4izlwLnXcq7LMSOJtnZLtklaUn+X+gPcBwN7RUEkk9rqxRRYNHW7as8g6+jheDsazxAQ iat-mode=0
|
||||||
|
n Bridge obfs4 45.142.181.131:42069 6EBCF6B02DA2B982F4080A7119D737366AFB74FA cert=9HyWH/BCwWzNirZdTQtluCgJk+gFhpOqydIuyQ1iDvpnqsAynKF+zcPE/INZFefm86UlBg iat-mode=0
|
||||||
|
n Bridge obfs4 85.214.28.204:47111 78A36E46BB082A471848239D3F4390A8F8C6084D cert=96sr3eaUFBDu4wFVAQIfNFElh0UNuutJ/3/Fh2Vu2PHfacQ8bwfY02bwG351U8BZpLnfUQ iat-mode=0
|
||||||
|
n Bridge obfs4 152.67.77.101:4096 B82DB9CDDF887AB8A859420E07DF298E30AF8A6E cert=21OWn3yFo+hulmQNAOtF5uwwOqWtdT5PrLhk8BG9DpOd0/k5DEkQEYPyDdXbS9nZ0E5BJA iat-mode=0
|
||||||
v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
|
v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
|
||||||
v Bridge 77.96.91.103:443 ED000A75B79A58F1D83A4D1675C2A9395B71BE8E
|
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
|
v Bridge 185.189.195.124:8199 A1F3EE78F9C2343668E68FEB84358A4C742831A5
|
||||||
|
v Bridge 135.23.182.26:11393 6E9A100D1E0570F0012735D64EEB4CB62AC258D9
|
||||||
|
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.connection;
|
package org.briarproject.bramble.connection;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
||||||
import org.briarproject.bramble.api.connection.InterruptibleConnection;
|
import org.briarproject.bramble.api.connection.InterruptibleConnection;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
@@ -57,6 +58,10 @@ public class ConnectionRegistryImplTest extends BrambleMockTestCase {
|
|||||||
private final Priority high =
|
private final Priority high =
|
||||||
new Priority(fromHexString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"));
|
new Priority(fromHexString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"));
|
||||||
|
|
||||||
|
public ConnectionRegistryImplTest() throws FormatException {
|
||||||
|
// required for throws declaration
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRegisterMultipleConnections() {
|
public void testRegisterMultipleConnections() {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
|
|||||||
import org.briarproject.bramble.api.transport.KeyManager;
|
import org.briarproject.bramble.api.transport.KeyManager;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.DbExpectations;
|
import org.briarproject.bramble.test.DbExpectations;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -63,13 +62,9 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final long timestamp = System.currentTimeMillis();
|
private final long timestamp = System.currentTimeMillis();
|
||||||
private final boolean alice = new Random().nextBoolean();
|
private final boolean alice = new Random().nextBoolean();
|
||||||
|
|
||||||
private ContactManagerImpl contactManager;
|
private final ContactManagerImpl contactManager =
|
||||||
|
new ContactManagerImpl(db, keyManager, identityManager,
|
||||||
@Before
|
pendingContactFactory);
|
||||||
public void setUp() {
|
|
||||||
contactManager = new ContactManagerImpl(db, keyManager,
|
|
||||||
identityManager, pendingContactFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddContact() throws Exception {
|
public void testAddContact() throws Exception {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
package org.briarproject.bramble.crypto;
|
||||||
|
|
||||||
import org.bouncycastle.crypto.digests.Blake2bDigest;
|
import org.bouncycastle.crypto.digests.Blake2bDigest;
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -47,7 +48,7 @@ public class Blake2bDigestTest extends BrambleTestCase {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDigestWithKeyedTestVectors() {
|
public void testDigestWithKeyedTestVectors() throws FormatException {
|
||||||
for (String[] keyedTestVector : KEYED_TEST_VECTORS) {
|
for (String[] keyedTestVector : KEYED_TEST_VECTORS) {
|
||||||
byte[] input = StringUtils.fromHexString(keyedTestVector[0]);
|
byte[] input = StringUtils.fromHexString(keyedTestVector[0]);
|
||||||
byte[] key = StringUtils.fromHexString(keyedTestVector[1]);
|
byte[] key = StringUtils.fromHexString(keyedTestVector[1]);
|
||||||
@@ -63,7 +64,8 @@ public class Blake2bDigestTest extends BrambleTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDigestWithKeyedTestVectorsAndRandomUpdate() {
|
public void testDigestWithKeyedTestVectorsAndRandomUpdate()
|
||||||
|
throws FormatException {
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
for (int i = 0; i < 100; i++) {
|
for (int i = 0; i < 100; i++) {
|
||||||
for (String[] keyedTestVector : KEYED_TEST_VECTORS) {
|
for (String[] keyedTestVector : KEYED_TEST_VECTORS) {
|
||||||
@@ -138,7 +140,7 @@ public class Blake2bDigestTest extends BrambleTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void runSelfTest() {
|
public void runSelfTest() throws FormatException {
|
||||||
Blake2bDigest testDigest = new Blake2bDigest(256);
|
Blake2bDigest testDigest = new Blake2bDigest(256);
|
||||||
byte[] md = new byte[64];
|
byte[] md = new byte[64];
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
package org.briarproject.bramble.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
|
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
@@ -83,7 +84,7 @@ public class KeyAgreementTest extends BrambleTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRfc7748TestVector() {
|
public void testRfc7748TestVector() throws FormatException {
|
||||||
byte[] aPriv = parsePrivateKey(ALICE_PRIVATE);
|
byte[] aPriv = parsePrivateKey(ALICE_PRIVATE);
|
||||||
byte[] aPub = fromHexString(ALICE_PUBLIC);
|
byte[] aPub = fromHexString(ALICE_PUBLIC);
|
||||||
byte[] bPriv = parsePrivateKey(BOB_PRIVATE);
|
byte[] bPriv = parsePrivateKey(BOB_PRIVATE);
|
||||||
@@ -97,7 +98,8 @@ public class KeyAgreementTest extends BrambleTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDerivesSameSharedSecretFromEquivalentPublicKey() {
|
public void testDerivesSameSharedSecretFromEquivalentPublicKey()
|
||||||
|
throws FormatException {
|
||||||
byte[] aPub = fromHexString(ALICE_PUBLIC);
|
byte[] aPub = fromHexString(ALICE_PUBLIC);
|
||||||
byte[] bPriv = parsePrivateKey(BOB_PRIVATE);
|
byte[] bPriv = parsePrivateKey(BOB_PRIVATE);
|
||||||
byte[] sharedSecret = fromHexString(SHARED_SECRET);
|
byte[] sharedSecret = fromHexString(SHARED_SECRET);
|
||||||
@@ -168,7 +170,7 @@ public class KeyAgreementTest extends BrambleTestCase {
|
|||||||
return crypto.deriveSharedSecret(label, publicKey, keyPair, inputs);
|
return crypto.deriveSharedSecret(label, publicKey, keyPair, inputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] parsePrivateKey(String hex) {
|
private byte[] parsePrivateKey(String hex) throws FormatException {
|
||||||
// Private keys need to be clamped because curve25519-java does the
|
// Private keys need to be clamped because curve25519-java does the
|
||||||
// clamping at key generation time, not multiplication time
|
// clamping at key generation time, not multiplication time
|
||||||
return AgreementKeyParser.clamp(fromHexString(hex));
|
return AgreementKeyParser.clamp(fromHexString(hex));
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
package org.briarproject.bramble.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
@@ -15,11 +16,11 @@ public class XSalsa20Poly1305AuthenticatedCipherTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// Test vectors from the NaCl paper
|
// Test vectors from the NaCl paper
|
||||||
// http://cr.yp.to/highspeed/naclcrypto-20090310.pdf
|
// http://cr.yp.to/highspeed/naclcrypto-20090310.pdf
|
||||||
private static final byte[] TEST_KEY = StringUtils.fromHexString(
|
private final byte[] TEST_KEY = StringUtils.fromHexString(
|
||||||
"1b27556473e985d462cd51197a9a46c76009549eac6474f206c4ee0844f68389");
|
"1b27556473e985d462cd51197a9a46c76009549eac6474f206c4ee0844f68389");
|
||||||
private static final byte[] TEST_IV = StringUtils.fromHexString(
|
private final byte[] TEST_IV = StringUtils.fromHexString(
|
||||||
"69696ee955b62b73cd62bda875fc73d68219e0036b7a0b37");
|
"69696ee955b62b73cd62bda875fc73d68219e0036b7a0b37");
|
||||||
private static final byte[] TEST_PLAINTEXT = StringUtils.fromHexString(
|
private final byte[] TEST_PLAINTEXT = StringUtils.fromHexString(
|
||||||
"be075fc53c81f2d5cf141316" +
|
"be075fc53c81f2d5cf141316" +
|
||||||
"ebeb0c7b5228c52a4c62cbd4" +
|
"ebeb0c7b5228c52a4c62cbd4" +
|
||||||
"4b66849b64244ffce5ecbaaf" +
|
"4b66849b64244ffce5ecbaaf" +
|
||||||
@@ -31,7 +32,7 @@ public class XSalsa20Poly1305AuthenticatedCipherTest extends BrambleTestCase {
|
|||||||
"048977eb48f59ffd4924ca1c" +
|
"048977eb48f59ffd4924ca1c" +
|
||||||
"60902e52f0a089bc76897040" +
|
"60902e52f0a089bc76897040" +
|
||||||
"e082f937763848645e0705");
|
"e082f937763848645e0705");
|
||||||
private static final byte[] TEST_CIPHERTEXT = StringUtils.fromHexString(
|
private final byte[] TEST_CIPHERTEXT = StringUtils.fromHexString(
|
||||||
"f3ffc7703f9400e52a7dfb4b" +
|
"f3ffc7703f9400e52a7dfb4b" +
|
||||||
"3d3305d98e993b9f48681273" +
|
"3d3305d98e993b9f48681273" +
|
||||||
"c29650ba32fc76ce48332ea7" +
|
"c29650ba32fc76ce48332ea7" +
|
||||||
@@ -46,6 +47,10 @@ public class XSalsa20Poly1305AuthenticatedCipherTest extends BrambleTestCase {
|
|||||||
"a43d14a6599b1f654cb45a74" +
|
"a43d14a6599b1f654cb45a74" +
|
||||||
"e355a5");
|
"e355a5");
|
||||||
|
|
||||||
|
public XSalsa20Poly1305AuthenticatedCipherTest() throws FormatException {
|
||||||
|
// required for throws declaration
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEncrypt() throws Exception {
|
public void testEncrypt() throws Exception {
|
||||||
SecretKey k = new SecretKey(TEST_KEY);
|
SecretKey k = new SecretKey(TEST_KEY);
|
||||||
|
|||||||
@@ -618,11 +618,12 @@ public class BdfReaderImplTest extends BrambleTestCase {
|
|||||||
r.readDictionary();
|
r.readDictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setContents(String hex) {
|
private void setContents(String hex) throws FormatException {
|
||||||
setContents(hex, DEFAULT_MAX_BUFFER_SIZE);
|
setContents(hex, DEFAULT_MAX_BUFFER_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setContents(String hex, int maxBufferSize) {
|
private void setContents(String hex, int maxBufferSize)
|
||||||
|
throws FormatException {
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(fromHexString(hex));
|
ByteArrayInputStream in = new ByteArrayInputStream(fromHexString(hex));
|
||||||
r = new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT, maxBufferSize);
|
r = new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT, maxBufferSize);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package org.briarproject.bramble.data;
|
|||||||
|
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -17,14 +16,8 @@ import static org.junit.Assert.assertArrayEquals;
|
|||||||
|
|
||||||
public class BdfWriterImplTest extends BrambleTestCase {
|
public class BdfWriterImplTest extends BrambleTestCase {
|
||||||
|
|
||||||
private ByteArrayOutputStream out = null;
|
private final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
private BdfWriterImpl w = null;
|
private final BdfWriterImpl w = new BdfWriterImpl(out);
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
out = new ByteArrayOutputStream();
|
|
||||||
w = new BdfWriterImpl(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWriteNull() throws IOException {
|
public void testWriteNull() throws IOException {
|
||||||
|
|||||||
@@ -303,11 +303,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Check whether the contact is in the DB (which it's not)
|
// 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));
|
will(returnValue(txn));
|
||||||
exactly(25).of(database).containsContact(txn, contactId);
|
exactly(27).of(database).containsContact(txn, contactId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
exactly(25).of(database).abortTransaction(txn);
|
exactly(27).of(database).abortTransaction(txn);
|
||||||
}});
|
}});
|
||||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||||
eventExecutor, shutdownManager);
|
eventExecutor, shutdownManager);
|
||||||
@@ -321,6 +321,23 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
// Expected
|
// 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 {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(false, transaction ->
|
||||||
db.generateAck(transaction, contactId, 123));
|
db.generateAck(transaction, contactId, 123));
|
||||||
@@ -372,7 +389,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(true, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getMessagesToAck(transaction, contactId, 123));
|
db.getMessagesToAck(transaction, contactId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchContactException expected) {
|
} catch (NoSuchContactException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
@@ -1379,14 +1396,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
oneOf(database).containsContact(txn, contactId);
|
oneOf(database).containsContact(txn, contactId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
// First message is still visible to the contact - flag lowered
|
oneOf(database).lowerAckFlag(txn, contactId, acked);
|
||||||
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
|
|
||||||
will(returnValue(true));
|
|
||||||
// Second message is no longer visible - flag not lowered
|
|
||||||
oneOf(database).containsVisibleMessage(txn, contactId, messageId1);
|
|
||||||
will(returnValue(false));
|
|
||||||
oneOf(database)
|
|
||||||
.lowerAckFlag(txn, contactId, singletonList(messageId));
|
|
||||||
oneOf(database).commitTransaction(txn);
|
oneOf(database).commitTransaction(txn);
|
||||||
}});
|
}});
|
||||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||||
|
|||||||
@@ -378,9 +378,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// Initially there should be nothing to send
|
// Initially there should be nothing to send
|
||||||
assertFalse(
|
assertFalse(
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false));
|
||||||
assertFalse(
|
assertFalse(
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true));
|
||||||
|
|
||||||
// Add some messages to ack
|
// Add some messages to ack
|
||||||
Message message1 = getMessage(groupId);
|
Message message1 = getMessage(groupId);
|
||||||
@@ -389,10 +389,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addMessage(txn, message1, DELIVERED, true, false, contactId);
|
db.addMessage(txn, message1, DELIVERED, true, false, contactId);
|
||||||
|
|
||||||
// Both message IDs should be returned
|
// Both message IDs should be returned
|
||||||
assertTrue(
|
assertTrue(db.containsAcksToSend(txn, contactId));
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
|
||||||
assertTrue(
|
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
|
||||||
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
|
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||||
assertEquals(asList(messageId, messageId1), ids);
|
assertEquals(asList(messageId, messageId1), ids);
|
||||||
|
|
||||||
@@ -400,10 +397,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
|
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
|
||||||
|
|
||||||
// No message IDs should be returned
|
// No message IDs should be returned
|
||||||
assertFalse(
|
assertFalse(db.containsAcksToSend(txn, contactId));
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
|
||||||
assertFalse(
|
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
|
||||||
assertEquals(emptyList(), db.getMessagesToAck(txn, contactId, 1234));
|
assertEquals(emptyList(), db.getMessagesToAck(txn, contactId, 1234));
|
||||||
|
|
||||||
// Raise the ack flag again
|
// Raise the ack flag again
|
||||||
@@ -411,10 +405,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.raiseAckFlag(txn, contactId, messageId1);
|
db.raiseAckFlag(txn, contactId, messageId1);
|
||||||
|
|
||||||
// Both message IDs should be returned
|
// Both message IDs should be returned
|
||||||
assertTrue(
|
assertTrue(db.containsAcksToSend(txn, contactId));
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
|
||||||
assertTrue(
|
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
|
||||||
ids = db.getMessagesToAck(txn, contactId, 1234);
|
ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||||
assertEquals(asList(messageId, messageId1), ids);
|
assertEquals(asList(messageId, messageId1), ids);
|
||||||
|
|
||||||
@@ -2029,37 +2020,51 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addMessage(txn, message, UNKNOWN, false, false, null);
|
db.addMessage(txn, message, UNKNOWN, false, false, null);
|
||||||
|
|
||||||
// There should be no messages to send
|
// 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
|
// Share the group with the contact - still no messages to send
|
||||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
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
|
// Set the message's state to DELIVERED - still no messages to send
|
||||||
db.setMessageState(txn, messageId, DELIVERED);
|
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
|
// Share the message - now it should be sendable immediately
|
||||||
db.setMessageShared(txn, messageId, true);
|
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
|
// Mark the message as requested - it should still be sendable
|
||||||
db.raiseRequestedFlag(txn, contactId, messageId);
|
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
|
// Update the message's expiry time as though we sent it - now the
|
||||||
// message should be sendable after one round-trip
|
// message should be sendable after one round-trip
|
||||||
db.updateRetransmissionData(txn, contactId, messageId, 1000);
|
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||||
assertEquals(now + 2000, db.getNextSendTime(txn, contactId));
|
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
|
// Update the message's expiry time again - now it should be sendable
|
||||||
// after two round-trips
|
// after two round-trips
|
||||||
db.updateRetransmissionData(txn, contactId, messageId, 1000);
|
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||||
assertEquals(now + 4000, db.getNextSendTime(txn, contactId));
|
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
|
// Delete the message - there should be no messages to send
|
||||||
db.deleteMessage(txn, messageId);
|
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.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -2124,7 +2129,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||||
|
|
||||||
// The message should expire after 2 * 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
|
// Time: now + MAX_LATENCY * 2 - 1
|
||||||
// The message should not yet be sendable
|
// The message should not yet be sendable
|
||||||
@@ -2167,7 +2173,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||||
|
|
||||||
// The message should expire after 2 * 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
|
// The message should not be sendable via the same transport
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
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);
|
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||||
|
|
||||||
// The message should expire after 2 * 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
|
// Time: now + MAX_LATENCY * 2 - 1
|
||||||
// The message should not yet be sendable
|
// The message should not yet be sendable
|
||||||
@@ -2225,8 +2233,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
// Reset the retransmission times
|
// Reset the retransmission times
|
||||||
db.resetUnackedMessagesToSend(txn, contactId);
|
db.resetUnackedMessagesToSend(txn, contactId);
|
||||||
|
|
||||||
// The message should have infinitely short expiry
|
// The message should be sendable immediately
|
||||||
assertEquals(0, db.getNextSendTime(txn, contactId));
|
assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||||
|
|
||||||
// The message should be sendable
|
// The message should be sendable
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
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,
|
private void assertNothingToSendLazily(Database<Connection> db,
|
||||||
Connection txn) throws Exception {
|
Connection txn) throws Exception {
|
||||||
assertFalse(
|
assertFalse(
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false));
|
||||||
Collection<MessageId> ids =
|
Collection<MessageId> ids =
|
||||||
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
assertTrue(ids.isEmpty());
|
assertTrue(ids.isEmpty());
|
||||||
@@ -2590,7 +2598,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
private void assertOneMessageToSendLazily(Database<Connection> db,
|
private void assertOneMessageToSendLazily(Database<Connection> db,
|
||||||
Connection txn) throws Exception {
|
Connection txn) throws Exception {
|
||||||
assertTrue(
|
assertTrue(
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false));
|
||||||
Collection<MessageId> ids =
|
Collection<MessageId> ids =
|
||||||
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
assertEquals(singletonList(messageId), ids);
|
assertEquals(singletonList(messageId), ids);
|
||||||
@@ -2601,7 +2609,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
private void assertNothingToSendEagerly(Database<Connection> db,
|
private void assertNothingToSendEagerly(Database<Connection> db,
|
||||||
Connection txn) throws Exception {
|
Connection txn) throws Exception {
|
||||||
assertFalse(
|
assertFalse(
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true));
|
||||||
Collection<MessageId> unacked =
|
Collection<MessageId> unacked =
|
||||||
db.getUnackedMessagesToSend(txn, contactId);
|
db.getUnackedMessagesToSend(txn, contactId);
|
||||||
assertTrue(unacked.isEmpty());
|
assertTrue(unacked.isEmpty());
|
||||||
@@ -2611,7 +2619,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
private void assertOneMessageToSendEagerly(Database<Connection> db,
|
private void assertOneMessageToSendEagerly(Database<Connection> db,
|
||||||
Connection txn) throws Exception {
|
Connection txn) throws Exception {
|
||||||
assertTrue(
|
assertTrue(
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true));
|
||||||
Collection<MessageId> unacked =
|
Collection<MessageId> unacked =
|
||||||
db.getUnackedMessagesToSend(txn, contactId);
|
db.getUnackedMessagesToSend(txn, contactId);
|
||||||
assertEquals(singletonList(messageId), unacked);
|
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.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.DbExpectations;
|
import org.briarproject.bramble.test.DbExpectations;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
@@ -41,13 +40,8 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final KeyPair handshakeKeyPair =
|
private final KeyPair handshakeKeyPair =
|
||||||
new KeyPair(handshakePublicKey, handshakePrivateKey);
|
new KeyPair(handshakePublicKey, handshakePrivateKey);
|
||||||
|
|
||||||
private IdentityManagerImpl identityManager;
|
private final IdentityManagerImpl identityManager =
|
||||||
|
new IdentityManagerImpl(db, crypto, authorFactory, clock);
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
identityManager =
|
|
||||||
new IdentityManagerImpl(db, crypto, authorFactory, clock);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOpenDatabaseIdentityRegistered() throws Exception {
|
public void testOpenDatabaseIdentityRegistered() throws Exception {
|
||||||
|
|||||||
@@ -5,20 +5,18 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
|
|||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.Service;
|
||||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.DbExpectations;
|
import org.briarproject.bramble.test.DbExpectations;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
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.RUNNING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
|
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.LifecycleState.STOPPED;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
|
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.lifecycle.LifecycleManager.StartResult.SUCCESS;
|
||||||
import static org.briarproject.bramble.api.system.Clock.MAX_REASONABLE_TIME_MS;
|
import static org.briarproject.bramble.api.system.Clock.MAX_REASONABLE_TIME_MS;
|
||||||
@@ -31,22 +29,18 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
private final EventBus eventBus = context.mock(EventBus.class);
|
private final EventBus eventBus = context.mock(EventBus.class);
|
||||||
private final Clock clock = context.mock(Clock.class);
|
private final Clock clock = context.mock(Clock.class);
|
||||||
|
private final OpenDatabaseHook hook = context.mock(OpenDatabaseHook.class);
|
||||||
|
private final Service service = context.mock(Service.class);
|
||||||
|
|
||||||
private final SecretKey dbKey = getSecretKey();
|
private final SecretKey dbKey = getSecretKey();
|
||||||
|
|
||||||
private LifecycleManagerImpl lifecycleManager;
|
private final LifecycleManagerImpl lifecycleManager =
|
||||||
|
new LifecycleManagerImpl(db, eventBus, clock);
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
lifecycleManager = new LifecycleManagerImpl(db, eventBus, clock);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOpenDatabaseHooksAreCalledAtStartup() throws Exception {
|
public void testOpenDatabaseHooksAreCalledAtStartup() throws Exception {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
AtomicBoolean called = new AtomicBoolean(false);
|
|
||||||
OpenDatabaseHook hook = transaction -> called.set(true);
|
|
||||||
|
|
||||||
context.checking(new DbExpectations() {{
|
context.checking(new DbExpectations() {{
|
||||||
oneOf(clock).currentTimeMillis();
|
oneOf(clock).currentTimeMillis();
|
||||||
@@ -55,6 +49,7 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
oneOf(db).removeTemporaryMessages(txn);
|
oneOf(db).removeTemporaryMessages(txn);
|
||||||
|
oneOf(hook).onDatabaseOpened(txn);
|
||||||
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -62,7 +57,38 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
|
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
|
||||||
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
|
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
|
||||||
assertTrue(called.get());
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testServicesAreStartedAndStopped() 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);
|
||||||
|
oneOf(service).startService();
|
||||||
|
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
||||||
|
}});
|
||||||
|
|
||||||
|
lifecycleManager.registerService(service);
|
||||||
|
|
||||||
|
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
|
||||||
|
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(db).close();
|
||||||
|
oneOf(service).stopService();
|
||||||
|
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
||||||
|
}});
|
||||||
|
|
||||||
|
lifecycleManager.stopServices();
|
||||||
|
assertEquals(STOPPED, lifecycleManager.getLifecycleState());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -89,6 +115,31 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
|||||||
assertEquals(STARTING, lifecycleManager.getLifecycleState());
|
assertEquals(STARTING, lifecycleManager.getLifecycleState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSecondCallToStartServicesReturnsEarly() 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);
|
||||||
|
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
||||||
|
}});
|
||||||
|
|
||||||
|
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
|
||||||
|
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
|
// Calling startServices() again should not try to open the DB or
|
||||||
|
// start the services again
|
||||||
|
assertEquals(ALREADY_RUNNING, lifecycleManager.startServices(dbKey));
|
||||||
|
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSecondCallToStopServicesReturnsEarly() throws Exception {
|
public void testSecondCallToStopServicesReturnsEarly() throws Exception {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
@@ -101,7 +152,7 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
oneOf(db).removeTemporaryMessages(txn);
|
oneOf(db).removeTemporaryMessages(txn);
|
||||||
exactly(2).of(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
|
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
|
||||||
@@ -109,17 +160,17 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
|||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
|
||||||
oneOf(db).close();
|
oneOf(db).close();
|
||||||
|
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
lifecycleManager.stopServices();
|
lifecycleManager.stopServices();
|
||||||
assertEquals(STOPPING, lifecycleManager.getLifecycleState());
|
assertEquals(STOPPED, lifecycleManager.getLifecycleState());
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
// Calling stopServices() again should not broadcast another event or
|
// Calling stopServices() again should not broadcast another event or
|
||||||
// try to close the DB again
|
// try to close the DB again
|
||||||
lifecycleManager.stopServices();
|
lifecycleManager.stopServices();
|
||||||
assertEquals(STOPPING, lifecycleManager.getLifecycleState());
|
assertEquals(STOPPED, lifecycleManager.getLifecycleState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -187,6 +187,32 @@ public class ConnectivityCheckerImplTest extends BrambleMockTestCase {
|
|||||||
checker.onConnectivityCheckSucceeded(now);
|
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() {
|
private ConnectivityCheckerImpl createChecker() {
|
||||||
|
|
||||||
return new ConnectivityCheckerImpl(clock, mailboxApiCaller) {
|
return new ConnectivityCheckerImpl(clock, mailboxApiCaller) {
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
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();
|
||||||
|
|
||||||
|
expectDestroyConnectivityChecker();
|
||||||
|
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);
|
||||||
|
expectDestroyConnectivityChecker();
|
||||||
|
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();
|
||||||
|
|
||||||
|
expectDestroyConnectivityChecker();
|
||||||
|
client.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAssignContactForDownloadAndDestroyClient() {
|
||||||
|
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);
|
||||||
|
expectDestroyConnectivityChecker();
|
||||||
|
client.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAssignAndDeassignContactForDownload() {
|
||||||
|
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();
|
||||||
|
|
||||||
|
expectDestroyConnectivityChecker();
|
||||||
|
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();
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectDestroyConnectivityChecker() {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(connectivityChecker).destroy();
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
|
public class ContactMailboxDownloadWorkerTest
|
||||||
|
extends MailboxDownloadWorkerTest<ContactMailboxDownloadWorker> {
|
||||||
|
|
||||||
|
public ContactMailboxDownloadWorkerTest() {
|
||||||
|
mailboxProperties = getMailboxProperties(false, CLIENT_SUPPORTS);
|
||||||
|
worker = new ContactMailboxDownloadWorker(connectivityChecker,
|
||||||
|
torReachabilityMonitor, mailboxApiCaller, mailboxApi,
|
||||||
|
mailboxFileManager, 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
|
||||||
|
// and reachability observers
|
||||||
|
expectRemoveObservers();
|
||||||
|
worker.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChecksForFilesWhenConnectivityCheckSucceeds()
|
||||||
|
throws Exception {
|
||||||
|
// When the worker is started it should start a connectivity check
|
||||||
|
expectStartConnectivityCheck();
|
||||||
|
worker.start();
|
||||||
|
|
||||||
|
// When the connectivity check succeeds, a list-inbox task should be
|
||||||
|
// started for the first download cycle
|
||||||
|
AtomicReference<ApiCall> listTask = new AtomicReference<>();
|
||||||
|
expectStartTask(listTask);
|
||||||
|
worker.onConnectivityCheckSucceeded();
|
||||||
|
|
||||||
|
// When the list-inbox tasks runs and finds no files to download,
|
||||||
|
// it should add a Tor reachability observer
|
||||||
|
expectCheckForFiles(mailboxProperties.getInboxId(), emptyList());
|
||||||
|
expectAddReachabilityObserver();
|
||||||
|
assertFalse(listTask.get().callApi());
|
||||||
|
|
||||||
|
// When the reachability observer is called, a list-inbox task should
|
||||||
|
// be started for the second download cycle
|
||||||
|
expectStartTask(listTask);
|
||||||
|
worker.onTorReachable();
|
||||||
|
|
||||||
|
// When the list-inbox tasks runs and finds no files to download,
|
||||||
|
// it should finish the second download cycle
|
||||||
|
expectCheckForFiles(mailboxProperties.getInboxId(), emptyList());
|
||||||
|
assertFalse(listTask.get().callApi());
|
||||||
|
|
||||||
|
// When the worker is destroyed it should remove the connectivity
|
||||||
|
// and reachability observers
|
||||||
|
expectRemoveObservers();
|
||||||
|
worker.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDownloadsFilesWhenConnectivityCheckSucceeds()
|
||||||
|
throws Exception {
|
||||||
|
// When the worker is started it should start a connectivity check
|
||||||
|
expectStartConnectivityCheck();
|
||||||
|
worker.start();
|
||||||
|
|
||||||
|
// When the connectivity check succeeds, a list-inbox task should be
|
||||||
|
// started for the first download cycle
|
||||||
|
AtomicReference<ApiCall> listTask = new AtomicReference<>();
|
||||||
|
expectStartTask(listTask);
|
||||||
|
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<>();
|
||||||
|
expectCheckForFiles(mailboxProperties.getInboxId(), files);
|
||||||
|
expectStartTask(downloadTask);
|
||||||
|
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<>();
|
||||||
|
expectDownloadFile(mailboxProperties.getInboxId(), file1);
|
||||||
|
expectStartTask(deleteTask);
|
||||||
|
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
|
||||||
|
expectDeleteFile(mailboxProperties.getInboxId(), file1, true);
|
||||||
|
expectStartTask(downloadTask);
|
||||||
|
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
|
||||||
|
expectDownloadFile(mailboxProperties.getInboxId(), file2);
|
||||||
|
expectStartTask(deleteTask);
|
||||||
|
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
|
||||||
|
expectDeleteFile(mailboxProperties.getInboxId(), file2, false);
|
||||||
|
expectStartTask(listTask);
|
||||||
|
assertFalse(deleteTask.get().callApi());
|
||||||
|
|
||||||
|
// When the list-inbox tasks runs and finds no more files to download,
|
||||||
|
// it should add a Tor reachability observer
|
||||||
|
expectCheckForFiles(mailboxProperties.getInboxId(), emptyList());
|
||||||
|
expectAddReachabilityObserver();
|
||||||
|
assertFalse(listTask.get().callApi());
|
||||||
|
|
||||||
|
// When the reachability observer is called, a list-inbox task should
|
||||||
|
// be started for the second download cycle
|
||||||
|
expectStartTask(listTask);
|
||||||
|
worker.onTorReachable();
|
||||||
|
|
||||||
|
// When the list-inbox tasks runs and finds no more files to download,
|
||||||
|
// it should finish the second download cycle
|
||||||
|
expectCheckForFiles(mailboxProperties.getInboxId(), emptyList());
|
||||||
|
assertFalse(listTask.get().callApi());
|
||||||
|
|
||||||
|
// When the worker is destroyed it should remove the connectivity
|
||||||
|
// and reachability observers
|
||||||
|
expectRemoveObservers();
|
||||||
|
worker.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,7 +68,10 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private final MailboxApiImpl api = new MailboxApiImpl(httpClientProvider);
|
// We aren't using a real onion address, so use the given address verbatim
|
||||||
|
private final UrlConverter urlConverter = onion -> onion;
|
||||||
|
private final MailboxApiImpl api = new MailboxApiImpl(httpClientProvider,
|
||||||
|
urlConverter);
|
||||||
|
|
||||||
private final MailboxAuthToken token = new MailboxAuthToken(getRandomId());
|
private final MailboxAuthToken token = new MailboxAuthToken(getRandomId());
|
||||||
private final MailboxAuthToken token2 = new MailboxAuthToken(getRandomId());
|
private final MailboxAuthToken token2 = new MailboxAuthToken(getRandomId());
|
||||||
@@ -627,6 +630,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
server.enqueue(new MockResponse().setBody(invalidResponse3));
|
server.enqueue(new MockResponse().setBody(invalidResponse3));
|
||||||
server.enqueue(new MockResponse().setBody(invalidResponse4));
|
server.enqueue(new MockResponse().setBody(invalidResponse4));
|
||||||
server.enqueue(new MockResponse().setResponseCode(401));
|
server.enqueue(new MockResponse().setResponseCode(401));
|
||||||
|
server.enqueue(new MockResponse().setResponseCode(404));
|
||||||
server.enqueue(new MockResponse().setResponseCode(500));
|
server.enqueue(new MockResponse().setResponseCode(500));
|
||||||
server.start();
|
server.start();
|
||||||
String baseUrl = getBaseUrl(server);
|
String baseUrl = getBaseUrl(server);
|
||||||
@@ -703,13 +707,21 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
assertEquals("GET", request8.getMethod());
|
assertEquals("GET", request8.getMethod());
|
||||||
assertToken(request8, token);
|
assertToken(request8, token);
|
||||||
|
|
||||||
// 500 internal server error
|
// 404 not found
|
||||||
assertThrows(ApiException.class,
|
assertThrows(TolerableFailureException.class, () ->
|
||||||
() -> api.getFiles(properties, contactInboxId));
|
api.getFiles(properties, contactInboxId));
|
||||||
RecordedRequest request9 = server.takeRequest();
|
RecordedRequest request9 = server.takeRequest();
|
||||||
assertEquals("/files/" + contactInboxId, request9.getPath());
|
assertEquals("/files/" + contactInboxId, request9.getPath());
|
||||||
assertEquals("GET", request9.getMethod());
|
assertEquals("GET", request9.getMethod());
|
||||||
assertToken(request9, token);
|
assertToken(request9, token);
|
||||||
|
|
||||||
|
// 500 internal server error
|
||||||
|
assertThrows(ApiException.class,
|
||||||
|
() -> api.getFiles(properties, contactInboxId));
|
||||||
|
RecordedRequest request10 = server.takeRequest();
|
||||||
|
assertEquals("/files/" + contactInboxId, request10.getPath());
|
||||||
|
assertEquals("GET", request10.getMethod());
|
||||||
|
assertToken(request10, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
|
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.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 java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||||
|
|
||||||
|
abstract class MailboxDownloadWorkerTest<W extends MailboxDownloadWorker>
|
||||||
|
extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
final ConnectivityChecker connectivityChecker =
|
||||||
|
context.mock(ConnectivityChecker.class);
|
||||||
|
final TorReachabilityMonitor torReachabilityMonitor =
|
||||||
|
context.mock(TorReachabilityMonitor.class);
|
||||||
|
final MailboxApiCaller mailboxApiCaller =
|
||||||
|
context.mock(MailboxApiCaller.class);
|
||||||
|
final MailboxApi mailboxApi = context.mock(MailboxApi.class);
|
||||||
|
final MailboxFileManager mailboxFileManager =
|
||||||
|
context.mock(MailboxFileManager.class);
|
||||||
|
private final Cancellable apiCall = context.mock(Cancellable.class);
|
||||||
|
|
||||||
|
private final long now = System.currentTimeMillis();
|
||||||
|
final MailboxFile file1 =
|
||||||
|
new MailboxFile(new MailboxFileId(getRandomId()), now - 1);
|
||||||
|
final MailboxFile file2 =
|
||||||
|
new MailboxFile(new MailboxFileId(getRandomId()), now);
|
||||||
|
final List<MailboxFile> files = asList(file1, file2);
|
||||||
|
|
||||||
|
private File testDir, tempFile;
|
||||||
|
MailboxProperties mailboxProperties;
|
||||||
|
W worker;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
testDir = getTestDirectory();
|
||||||
|
tempFile = new File(testDir, "temp");
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
deleteTestDirectory(testDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void expectStartConnectivityCheck() {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(connectivityChecker).checkConnectivity(
|
||||||
|
with(mailboxProperties), with(worker));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void expectStartTask(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)
|
||||||
|
));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void expectCheckForFoldersWithAvailableFiles(
|
||||||
|
List<MailboxFolderId> folderIds) throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(mailboxApi).getFolders(mailboxProperties);
|
||||||
|
will(returnValue(folderIds));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void expectCheckForFiles(MailboxFolderId folderId,
|
||||||
|
List<MailboxFile> files) throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(mailboxApi).getFiles(mailboxProperties, folderId);
|
||||||
|
will(returnValue(files));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void expectDownloadFile(MailboxFolderId folderId,
|
||||||
|
MailboxFile file)
|
||||||
|
throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(mailboxFileManager).createTempFileForDownload();
|
||||||
|
will(returnValue(tempFile));
|
||||||
|
oneOf(mailboxApi).getFile(mailboxProperties, folderId, file.name,
|
||||||
|
tempFile);
|
||||||
|
oneOf(mailboxFileManager).handleDownloadedFile(tempFile);
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void expectDeleteFile(MailboxFolderId folderId, MailboxFile file,
|
||||||
|
boolean tolerableFailure) throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(mailboxApi).deleteFile(mailboxProperties, folderId,
|
||||||
|
file.name);
|
||||||
|
if (tolerableFailure) {
|
||||||
|
will(throwException(new TolerableFailureException()));
|
||||||
|
}
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void expectAddReachabilityObserver() {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(torReachabilityMonitor).addOneShotObserver(worker);
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void expectRemoveObservers() {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(connectivityChecker).removeObserver(worker);
|
||||||
|
oneOf(torReachabilityMonitor).removeObserver(worker);
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,16 +2,20 @@ package org.briarproject.bramble.mailbox;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||||
import org.briarproject.bramble.api.connection.ConnectionManager.TagController;
|
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.event.EventBus;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
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.event.TransportActiveEvent;
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
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.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||||
|
import org.briarproject.bramble.test.ConsumeArgumentAction;
|
||||||
import org.briarproject.bramble.test.RunAction;
|
import org.briarproject.bramble.test.RunAction;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.lib.action.DoAllAction;
|
import org.jmock.lib.action.DoAllAction;
|
||||||
@@ -20,6 +24,7 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
@@ -28,11 +33,14 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleS
|
|||||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
|
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.api.plugin.file.FileConstants.PROP_PATH;
|
||||||
import static org.briarproject.bramble.mailbox.MailboxFileManagerImpl.DOWNLOAD_DIR_NAME;
|
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.deleteTestDirectory;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
public class MailboxFileManagerImplTest extends BrambleMockTestCase {
|
public class MailboxFileManagerImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
@@ -47,6 +55,10 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final SimplexPlugin plugin = context.mock(SimplexPlugin.class);
|
private final SimplexPlugin plugin = context.mock(SimplexPlugin.class);
|
||||||
private final TransportConnectionReader transportConnectionReader =
|
private final TransportConnectionReader transportConnectionReader =
|
||||||
context.mock(TransportConnectionReader.class);
|
context.mock(TransportConnectionReader.class);
|
||||||
|
private final TransportConnectionWriter transportConnectionWriter =
|
||||||
|
context.mock(TransportConnectionWriter.class);
|
||||||
|
|
||||||
|
private final ContactId contactId = getContactId();
|
||||||
|
|
||||||
private File mailboxDir;
|
private File mailboxDir;
|
||||||
private MailboxFileManagerImpl manager;
|
private MailboxFileManagerImpl manager;
|
||||||
@@ -65,17 +77,25 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testHandlesOrphanedFilesAtStartup() throws Exception {
|
public void testHandlesOrphanedFilesAtStartup() throws Exception {
|
||||||
// Create an orphaned file, left behind at the previous shutdown
|
// 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);
|
File downloadDir = new File(mailboxDir, DOWNLOAD_DIR_NAME);
|
||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
downloadDir.mkdirs();
|
downloadDir.mkdirs();
|
||||||
File orphan = new File(downloadDir, "orphan");
|
File orphanedDownload = new File(downloadDir, "orphan");
|
||||||
assertTrue(orphan.createNewFile());
|
assertTrue(orphanedDownload.createNewFile());
|
||||||
|
|
||||||
TransportProperties props = new TransportProperties();
|
TransportProperties props = new TransportProperties();
|
||||||
props.put(PROP_PATH, orphan.getAbsolutePath());
|
props.put(PROP_PATH, orphanedDownload.getAbsolutePath());
|
||||||
|
|
||||||
// When the plugin becomes active the orphaned file should be handled
|
// When the plugin becomes active the orphaned upload should be deleted
|
||||||
|
// and the orphaned download should be handled
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(ioExecutor).execute(with(any(Runnable.class)));
|
oneOf(ioExecutor).execute(with(any(Runnable.class)));
|
||||||
will(new RunAction());
|
will(new RunAction());
|
||||||
@@ -90,10 +110,12 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
manager.eventOccurred(new TransportActiveEvent(ID));
|
manager.eventOccurred(new TransportActiveEvent(ID));
|
||||||
|
|
||||||
|
assertFalse(orphanedUpload.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeletesFileWhenReadSucceeds() throws Exception {
|
public void testDeletesDownloadedFileWhenReadSucceeds() throws Exception {
|
||||||
expectCheckForOrphans();
|
expectCheckForOrphans();
|
||||||
manager.eventOccurred(new TransportActiveEvent(ID));
|
manager.eventOccurred(new TransportActiveEvent(ID));
|
||||||
|
|
||||||
@@ -102,7 +124,7 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
|
|||||||
new AtomicReference<>(null);
|
new AtomicReference<>(null);
|
||||||
AtomicReference<TagController> controller = new AtomicReference<>(null);
|
AtomicReference<TagController> controller = new AtomicReference<>(null);
|
||||||
|
|
||||||
expectPassFileToConnectionManager(f, reader, controller);
|
expectPassDownloadedFileToConnectionManager(f, reader, controller);
|
||||||
manager.handleDownloadedFile(f);
|
manager.handleDownloadedFile(f);
|
||||||
|
|
||||||
// The read is successful, so the tag controller should allow the tag
|
// The read is successful, so the tag controller should allow the tag
|
||||||
@@ -117,29 +139,117 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeletesFileWhenTagIsNotRecognised() throws Exception {
|
public void testDeletesDownloadedFileWhenTagIsNotRecognised()
|
||||||
testDeletesFile(false, RUNNING, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDeletesFileWhenReadFails() throws Exception {
|
|
||||||
testDeletesFile(true, RUNNING, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDoesNotDeleteFileWhenTagIsNotRecognisedAtShutdown()
|
|
||||||
throws Exception {
|
throws Exception {
|
||||||
testDeletesFile(false, STOPPING, true);
|
testDeletesDownloadedFile(false, RUNNING, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDoesNotDeleteFileWhenReadFailsAtShutdown()
|
public void testDeletesDownloadedFileWhenReadFails() throws Exception {
|
||||||
throws Exception {
|
testDeletesDownloadedFile(true, RUNNING, false);
|
||||||
testDeletesFile(true, STOPPING, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testDeletesFile(boolean recognised, LifecycleState state,
|
@Test
|
||||||
boolean fileExists) throws Exception {
|
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();
|
expectCheckForOrphans();
|
||||||
manager.eventOccurred(new TransportActiveEvent(ID));
|
manager.eventOccurred(new TransportActiveEvent(ID));
|
||||||
|
|
||||||
@@ -148,7 +258,7 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
|
|||||||
new AtomicReference<>(null);
|
new AtomicReference<>(null);
|
||||||
AtomicReference<TagController> controller = new AtomicReference<>(null);
|
AtomicReference<TagController> controller = new AtomicReference<>(null);
|
||||||
|
|
||||||
expectPassFileToConnectionManager(f, reader, controller);
|
expectPassDownloadedFileToConnectionManager(f, reader, controller);
|
||||||
manager.handleDownloadedFile(f);
|
manager.handleDownloadedFile(f);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
@@ -169,7 +279,7 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
|
|||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expectPassFileToConnectionManager(File f,
|
private void expectPassDownloadedFileToConnectionManager(File f,
|
||||||
AtomicReference<TransportConnectionReader> reader,
|
AtomicReference<TransportConnectionReader> reader,
|
||||||
AtomicReference<TagController> controller) {
|
AtomicReference<TagController> controller) {
|
||||||
TransportProperties props = new TransportProperties();
|
TransportProperties props = new TransportProperties();
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ public class MailboxIntegrationTest extends BrambleTestCase {
|
|||||||
@Rule
|
@Rule
|
||||||
public TemporaryFolder folder = new TemporaryFolder();
|
public TemporaryFolder folder = new TemporaryFolder();
|
||||||
|
|
||||||
private final static String URL_BASE = "http://127.0.0.1:8000";
|
private static final String URL_BASE = "http://127.0.0.1:8000";
|
||||||
private final static MailboxAuthToken SETUP_TOKEN;
|
private static final MailboxAuthToken SETUP_TOKEN;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
@@ -74,8 +74,10 @@ public class MailboxIntegrationTest extends BrambleTestCase {
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private final static MailboxApiImpl api =
|
// We aren't using a real onion address, so use the given address verbatim
|
||||||
new MailboxApiImpl(httpClientProvider);
|
private static final UrlConverter urlConverter = onion -> onion;
|
||||||
|
private static final MailboxApiImpl api =
|
||||||
|
new MailboxApiImpl(httpClientProvider, urlConverter);
|
||||||
// needs to be static to keep values across different tests
|
// needs to be static to keep values across different tests
|
||||||
private static MailboxProperties ownerProperties;
|
private static MailboxProperties ownerProperties;
|
||||||
|
|
||||||
@@ -121,7 +123,7 @@ public class MailboxIntegrationTest extends BrambleTestCase {
|
|||||||
ContactId contactId = new ContactId(1);
|
ContactId contactId = new ContactId(1);
|
||||||
MailboxContact contact = getMailboxContact(contactId);
|
MailboxContact contact = getMailboxContact(contactId);
|
||||||
MailboxProperties contactProperties = new MailboxProperties(
|
MailboxProperties contactProperties = new MailboxProperties(
|
||||||
ownerProperties.getBaseUrl(), contact.token,
|
ownerProperties.getOnion(), contact.token,
|
||||||
new ArrayList<>(), contact.inboxId, contact.outboxId);
|
new ArrayList<>(), contact.inboxId, contact.outboxId);
|
||||||
api.addContact(ownerProperties, contact);
|
api.addContact(ownerProperties, contact);
|
||||||
|
|
||||||
@@ -167,7 +169,7 @@ public class MailboxIntegrationTest extends BrambleTestCase {
|
|||||||
ContactId contactId = new ContactId(1);
|
ContactId contactId = new ContactId(1);
|
||||||
MailboxContact contact = getMailboxContact(contactId);
|
MailboxContact contact = getMailboxContact(contactId);
|
||||||
MailboxProperties contactProperties = new MailboxProperties(
|
MailboxProperties contactProperties = new MailboxProperties(
|
||||||
ownerProperties.getBaseUrl(), contact.token,
|
ownerProperties.getOnion(), contact.token,
|
||||||
new ArrayList<>(), contact.inboxId, contact.outboxId);
|
new ArrayList<>(), contact.inboxId, contact.outboxId);
|
||||||
api.addContact(ownerProperties, contact);
|
api.addContact(ownerProperties, contact);
|
||||||
|
|
||||||
@@ -184,7 +186,7 @@ public class MailboxIntegrationTest extends BrambleTestCase {
|
|||||||
MailboxFileId fileName1 = files1.get(0).name;
|
MailboxFileId fileName1 = files1.get(0).name;
|
||||||
|
|
||||||
// owner can't check files
|
// owner can't check files
|
||||||
assertThrows(ApiException.class, () ->
|
assertThrows(TolerableFailureException.class, () ->
|
||||||
api.getFiles(ownerProperties, contact.inboxId));
|
api.getFiles(ownerProperties, contact.inboxId));
|
||||||
|
|
||||||
// contact downloads file
|
// contact downloads file
|
||||||
@@ -195,12 +197,13 @@ public class MailboxIntegrationTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// owner can't download file, even if knowing name
|
// owner can't download file, even if knowing name
|
||||||
File file1forbidden = folder.newFile();
|
File file1forbidden = folder.newFile();
|
||||||
assertThrows(ApiException.class, () -> api.getFile(ownerProperties,
|
assertThrows(TolerableFailureException.class, () ->
|
||||||
contact.inboxId, fileName1, file1forbidden));
|
api.getFile(ownerProperties, contact.inboxId, fileName1,
|
||||||
|
file1forbidden));
|
||||||
assertEquals(0, file1forbidden.length());
|
assertEquals(0, file1forbidden.length());
|
||||||
|
|
||||||
// owner can't delete file
|
// owner can't delete file
|
||||||
assertThrows(ApiException.class, () ->
|
assertThrows(TolerableFailureException.class, () ->
|
||||||
api.deleteFile(ownerProperties, contact.inboxId, fileName1));
|
api.deleteFile(ownerProperties, contact.inboxId, fileName1));
|
||||||
|
|
||||||
// contact deletes file
|
// contact deletes file
|
||||||
@@ -230,7 +233,7 @@ public class MailboxIntegrationTest extends BrambleTestCase {
|
|||||||
MailboxFileId file3name = files2.get(1).name;
|
MailboxFileId file3name = files2.get(1).name;
|
||||||
|
|
||||||
// contact can't list files in contact's outbox
|
// contact can't list files in contact's outbox
|
||||||
assertThrows(ApiException.class, () ->
|
assertThrows(TolerableFailureException.class, () ->
|
||||||
api.getFiles(contactProperties, contact.outboxId));
|
api.getFiles(contactProperties, contact.outboxId));
|
||||||
|
|
||||||
// owner downloads both files from contact's outbox
|
// owner downloads both files from contact's outbox
|
||||||
@@ -250,17 +253,19 @@ public class MailboxIntegrationTest extends BrambleTestCase {
|
|||||||
// contact can't download files again, even if knowing name
|
// contact can't download files again, even if knowing name
|
||||||
File file2forbidden = folder.newFile();
|
File file2forbidden = folder.newFile();
|
||||||
File file3forbidden = folder.newFile();
|
File file3forbidden = folder.newFile();
|
||||||
assertThrows(ApiException.class, () -> api.getFile(contactProperties,
|
assertThrows(TolerableFailureException.class, () ->
|
||||||
contact.outboxId, file2name, file2forbidden));
|
api.getFile(contactProperties, contact.outboxId, file2name,
|
||||||
assertThrows(ApiException.class, () -> api.getFile(contactProperties,
|
file2forbidden));
|
||||||
contact.outboxId, file3name, file3forbidden));
|
assertThrows(TolerableFailureException.class, () ->
|
||||||
|
api.getFile(contactProperties, contact.outboxId, file3name,
|
||||||
|
file3forbidden));
|
||||||
assertEquals(0, file1forbidden.length());
|
assertEquals(0, file1forbidden.length());
|
||||||
assertEquals(0, file2forbidden.length());
|
assertEquals(0, file2forbidden.length());
|
||||||
|
|
||||||
// contact can't delete files in outbox
|
// contact can't delete files in outbox
|
||||||
assertThrows(ApiException.class, () ->
|
assertThrows(TolerableFailureException.class, () ->
|
||||||
api.deleteFile(contactProperties, contact.outboxId, file2name));
|
api.deleteFile(contactProperties, contact.outboxId, file2name));
|
||||||
assertThrows(ApiException.class, () ->
|
assertThrows(TolerableFailureException.class, () ->
|
||||||
api.deleteFile(contactProperties, contact.outboxId, file3name));
|
api.deleteFile(contactProperties, contact.outboxId, file3name));
|
||||||
|
|
||||||
// owner deletes files
|
// owner deletes files
|
||||||
|
|||||||
@@ -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.MailboxUpdate;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
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.api.system.Clock;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.DbExpectations;
|
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.getContact;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
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.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
@@ -57,7 +55,6 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
private final String onion = getRandomString(56);
|
private final String onion = getRandomString(56);
|
||||||
private final byte[] onionBytes = getRandomBytes(32);
|
private final byte[] onionBytes = getRandomBytes(32);
|
||||||
private final String baseUrl = "http://" + onion + ".onion"; // TODO
|
|
||||||
private final MailboxAuthToken setupToken =
|
private final MailboxAuthToken setupToken =
|
||||||
new MailboxAuthToken(getRandomId());
|
new MailboxAuthToken(getRandomId());
|
||||||
private final MailboxAuthToken ownerToken =
|
private final MailboxAuthToken ownerToken =
|
||||||
@@ -65,9 +62,9 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
|||||||
private final String validPayload = getValidPayload();
|
private final String validPayload = getValidPayload();
|
||||||
private final long time = System.currentTimeMillis();
|
private final long time = System.currentTimeMillis();
|
||||||
private final MailboxProperties setupProperties = new MailboxProperties(
|
private final MailboxProperties setupProperties = new MailboxProperties(
|
||||||
baseUrl, setupToken, new ArrayList<>());
|
onion, setupToken, new ArrayList<>());
|
||||||
private final MailboxProperties ownerProperties = new MailboxProperties(
|
private final MailboxProperties ownerProperties = new MailboxProperties(
|
||||||
baseUrl, ownerToken, new ArrayList<>());
|
onion, ownerToken, new ArrayList<>());
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInitialQrCodeReceivedState() {
|
public void testInitialQrCodeReceivedState() {
|
||||||
@@ -113,7 +110,8 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
oneOf(mailboxSettingsManager).setOwnMailboxProperties(
|
oneOf(mailboxSettingsManager).setOwnMailboxProperties(
|
||||||
with(txn), with(matches(ownerProperties)));
|
with(txn), with(matches(ownerProperties)));
|
||||||
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, time);
|
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, time,
|
||||||
|
ownerProperties.getServerSupports());
|
||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn);
|
||||||
will(returnValue(singletonList(contact1)));
|
will(returnValue(singletonList(contact1)));
|
||||||
oneOf(mailboxUpdateManager).getRemoteUpdate(txn,
|
oneOf(mailboxUpdateManager).getRemoteUpdate(txn,
|
||||||
@@ -138,7 +136,6 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
|||||||
i.getAndIncrement();
|
i.getAndIncrement();
|
||||||
});
|
});
|
||||||
task.run();
|
task.run();
|
||||||
hasEvent(txn, OwnMailboxConnectionStatusEvent.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -210,7 +207,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
|||||||
private PredicateMatcher<MailboxProperties> matches(MailboxProperties p2) {
|
private PredicateMatcher<MailboxProperties> matches(MailboxProperties p2) {
|
||||||
return new PredicateMatcher<>(MailboxProperties.class, p1 ->
|
return new PredicateMatcher<>(MailboxProperties.class, p1 ->
|
||||||
p1.getAuthToken().equals(p2.getAuthToken()) &&
|
p1.getAuthToken().equals(p2.getAuthToken()) &&
|
||||||
p1.getBaseUrl().equals(p2.getBaseUrl()) &&
|
p1.getOnion().equals(p2.getOnion()) &&
|
||||||
p1.isOwner() == p2.isOwner() &&
|
p1.isOwner() == p2.isOwner() &&
|
||||||
p1.getServerSupports().equals(p2.getServerSupports()));
|
p1.getServerSupports().equals(p2.getServerSupports()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.db.Transaction;
|
|||||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager.MailboxHook;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
|
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
|
||||||
@@ -26,10 +27,11 @@ import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTIN
|
|||||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_TOKEN;
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_TOKEN;
|
||||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_NAMESPACE;
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_NAMESPACE;
|
||||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_UPLOADS_NAMESPACE;
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_UPLOADS_NAMESPACE;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getEvent;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
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.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
@@ -38,6 +40,7 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
private final SettingsManager settingsManager =
|
private final SettingsManager settingsManager =
|
||||||
context.mock(SettingsManager.class);
|
context.mock(SettingsManager.class);
|
||||||
|
private final MailboxHook hook = context.mock(MailboxHook.class);
|
||||||
|
|
||||||
private final MailboxSettingsManager manager =
|
private final MailboxSettingsManager manager =
|
||||||
new MailboxSettingsManagerImpl(settingsManager);
|
new MailboxSettingsManagerImpl(settingsManager);
|
||||||
@@ -46,6 +49,8 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final MailboxAuthToken token = new MailboxAuthToken(getRandomId());
|
private final MailboxAuthToken token = new MailboxAuthToken(getRandomId());
|
||||||
private final List<MailboxVersion> serverSupports =
|
private final List<MailboxVersion> serverSupports =
|
||||||
asList(new MailboxVersion(1, 0), new MailboxVersion(1, 1));
|
asList(new MailboxVersion(1, 0), new MailboxVersion(1, 1));
|
||||||
|
private final MailboxProperties properties = new MailboxProperties(onion,
|
||||||
|
token, serverSupports);
|
||||||
private final int[] serverSupportsInts = {1, 0, 1, 1};
|
private final int[] serverSupportsInts = {1, 0, 1, 1};
|
||||||
private final ContactId contactId1 = new ContactId(random.nextInt());
|
private final ContactId contactId1 = new ContactId(random.nextInt());
|
||||||
private final ContactId contactId2 = new ContactId(random.nextInt());
|
private final ContactId contactId2 = new ContactId(random.nextInt());
|
||||||
@@ -83,7 +88,7 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
MailboxProperties properties = manager.getOwnMailboxProperties(txn);
|
MailboxProperties properties = manager.getOwnMailboxProperties(txn);
|
||||||
assertNotNull(properties);
|
assertNotNull(properties);
|
||||||
assertEquals(onion, properties.getBaseUrl());
|
assertEquals(onion, properties.getOnion());
|
||||||
assertEquals(token, properties.getAuthToken());
|
assertEquals(token, properties.getAuthToken());
|
||||||
assertEquals(serverSupports, properties.getServerSupports());
|
assertEquals(serverSupports, properties.getServerSupports());
|
||||||
assertTrue(properties.isOwner());
|
assertTrue(properties.isOwner());
|
||||||
@@ -97,17 +102,40 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
|||||||
expectedSettings.put(SETTINGS_KEY_TOKEN, token.toString());
|
expectedSettings.put(SETTINGS_KEY_TOKEN, token.toString());
|
||||||
expectedSettings.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS,
|
expectedSettings.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS,
|
||||||
serverSupportsInts);
|
serverSupportsInts);
|
||||||
MailboxProperties properties = new MailboxProperties(onion, token,
|
|
||||||
serverSupports);
|
manager.registerMailboxHook(hook);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
|
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
|
||||||
SETTINGS_NAMESPACE);
|
SETTINGS_NAMESPACE);
|
||||||
|
oneOf(hook).mailboxPaired(txn, properties);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
manager.setOwnMailboxProperties(txn, properties);
|
manager.setOwnMailboxProperties(txn, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemovesProperties() throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
Settings expectedSettings = new Settings();
|
||||||
|
expectedSettings.put(SETTINGS_KEY_ONION, "");
|
||||||
|
expectedSettings.put(SETTINGS_KEY_TOKEN, "");
|
||||||
|
expectedSettings.put(SETTINGS_KEY_ATTEMPTS, "");
|
||||||
|
expectedSettings.put(SETTINGS_KEY_LAST_ATTEMPT, "");
|
||||||
|
expectedSettings.put(SETTINGS_KEY_LAST_SUCCESS, "");
|
||||||
|
expectedSettings.put(SETTINGS_KEY_SERVER_SUPPORTS, "");
|
||||||
|
|
||||||
|
manager.registerMailboxHook(hook);
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
|
||||||
|
SETTINGS_NAMESPACE);
|
||||||
|
oneOf(hook).mailboxUnpaired(txn);
|
||||||
|
}});
|
||||||
|
|
||||||
|
manager.removeOwnMailboxProperties(txn);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReturnsDefaultStatusIfSettingsAreEmpty() throws Exception {
|
public void testReturnsDefaultStatusIfSettingsAreEmpty() throws Exception {
|
||||||
Transaction txn = new Transaction(null, true);
|
Transaction txn = new Transaction(null, true);
|
||||||
@@ -146,23 +174,26 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testRecordsSuccess() throws Exception {
|
public void testRecordsSuccess() throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Settings oldSettings = new Settings();
|
|
||||||
oldSettings
|
|
||||||
.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, serverSupportsInts);
|
|
||||||
Settings expectedSettings = new Settings();
|
Settings expectedSettings = new Settings();
|
||||||
expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
||||||
expectedSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
|
expectedSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
|
||||||
expectedSettings.putInt(SETTINGS_KEY_ATTEMPTS, 0);
|
expectedSettings.putInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||||
|
expectedSettings.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS,
|
||||||
|
serverSupportsInts);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
|
|
||||||
will(returnValue(oldSettings));
|
|
||||||
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
|
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
|
||||||
SETTINGS_NAMESPACE);
|
SETTINGS_NAMESPACE);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
manager.recordSuccessfulConnection(txn, now);
|
manager.recordSuccessfulConnection(txn, now, serverSupports);
|
||||||
hasEvent(txn, OwnMailboxConnectionStatusEvent.class);
|
OwnMailboxConnectionStatusEvent e =
|
||||||
|
getEvent(txn, OwnMailboxConnectionStatusEvent.class);
|
||||||
|
MailboxStatus status = e.getStatus();
|
||||||
|
assertEquals(now, status.getTimeOfLastAttempt());
|
||||||
|
assertEquals(now, status.getTimeOfLastSuccess());
|
||||||
|
assertEquals(0, status.getAttemptsSinceSuccess());
|
||||||
|
assertFalse(status.hasProblem(now));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.mailbox;
|
|||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
import org.briarproject.bramble.api.data.BdfEntry;
|
import org.briarproject.bramble.api.data.BdfEntry;
|
||||||
@@ -16,6 +17,9 @@ import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
|||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
|
import org.briarproject.bramble.api.mailbox.event.MailboxPairedEvent;
|
||||||
|
import org.briarproject.bramble.api.mailbox.event.MailboxUnpairedEvent;
|
||||||
|
import org.briarproject.bramble.api.mailbox.event.MailboxUpdateSentEvent;
|
||||||
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
|
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
|
||||||
import org.briarproject.bramble.api.sync.Group;
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
@@ -32,6 +36,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static java.util.Collections.singleton;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.CLIENT_ID;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.CLIENT_ID;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.GROUP_KEY_SENT_CLIENT_SUPPORTS;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.GROUP_KEY_SENT_CLIENT_SUPPORTS;
|
||||||
@@ -45,6 +50,7 @@ import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY
|
|||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getEvent;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
@@ -71,6 +77,11 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final MailboxSettingsManager mailboxSettingsManager =
|
private final MailboxSettingsManager mailboxSettingsManager =
|
||||||
context.mock(MailboxSettingsManager.class);
|
context.mock(MailboxSettingsManager.class);
|
||||||
|
|
||||||
|
private final Contact contact = getContact();
|
||||||
|
private final List<Contact> contacts = singletonList(contact);
|
||||||
|
private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
|
private final GroupId contactGroupId = contactGroup.getId();
|
||||||
|
private final Message message = getMessage(contactGroupId);
|
||||||
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
private final BdfDictionary propsDict;
|
private final BdfDictionary propsDict;
|
||||||
private final BdfDictionary emptyPropsDict = new BdfDictionary();
|
private final BdfDictionary emptyPropsDict = new BdfDictionary();
|
||||||
@@ -78,7 +89,6 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final BdfList someClientSupports;
|
private final BdfList someClientSupports;
|
||||||
private final List<MailboxVersion> newerClientSupportsList;
|
private final List<MailboxVersion> newerClientSupportsList;
|
||||||
private final BdfList newerClientSupports;
|
private final BdfList newerClientSupports;
|
||||||
private final List<MailboxVersion> someServerSupportsList;
|
|
||||||
private final BdfList someServerSupports;
|
private final BdfList someServerSupports;
|
||||||
private final BdfList emptyServerSupports = new BdfList();
|
private final BdfList emptyServerSupports = new BdfList();
|
||||||
private final MailboxProperties updateProps;
|
private final MailboxProperties updateProps;
|
||||||
@@ -100,8 +110,8 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
newerClientSupportsList.get(0).getMajor(),
|
newerClientSupportsList.get(0).getMajor(),
|
||||||
newerClientSupportsList.get(0).getMinor()));
|
newerClientSupportsList.get(0).getMinor()));
|
||||||
|
|
||||||
someServerSupportsList = singletonList(new MailboxVersion(
|
List<MailboxVersion> someServerSupportsList =
|
||||||
rnd.nextInt(), rnd.nextInt()));
|
singletonList(new MailboxVersion(rnd.nextInt(), rnd.nextInt()));
|
||||||
someServerSupports = BdfList.of(BdfList.of(
|
someServerSupports = BdfList.of(BdfList.of(
|
||||||
someServerSupportsList.get(0).getMajor(),
|
someServerSupportsList.get(0).getMajor(),
|
||||||
someServerSupportsList.get(0).getMinor()));
|
someServerSupportsList.get(0).getMinor()));
|
||||||
@@ -109,7 +119,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
updateNoMailbox = new MailboxUpdate(someClientSupportsList);
|
updateNoMailbox = new MailboxUpdate(someClientSupportsList);
|
||||||
|
|
||||||
updateProps = getMailboxProperties(false, someServerSupportsList);
|
updateProps = getMailboxProperties(false, someServerSupportsList);
|
||||||
ownProps = new MailboxProperties(updateProps.getBaseUrl(),
|
ownProps = new MailboxProperties(updateProps.getOnion(),
|
||||||
updateProps.getAuthToken(), someServerSupportsList);
|
updateProps.getAuthToken(), someServerSupportsList);
|
||||||
updateWithMailbox = new MailboxUpdateWithMailbox(someClientSupportsList,
|
updateWithMailbox = new MailboxUpdateWithMailbox(someClientSupportsList,
|
||||||
updateProps);
|
updateProps);
|
||||||
@@ -135,8 +145,6 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testCreatesGroupsAtUnpairedStartup() throws Exception {
|
public void testCreatesGroupsAtUnpairedStartup() throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||||
BdfDictionary sentDict = BdfDictionary.of(new BdfEntry(
|
BdfDictionary sentDict = BdfDictionary.of(new BdfEntry(
|
||||||
GROUP_KEY_SENT_CLIENT_SUPPORTS,
|
GROUP_KEY_SENT_CLIENT_SUPPORTS,
|
||||||
@@ -158,8 +166,8 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||||
will(returnValue(SHARED));
|
will(returnValue(SHARED));
|
||||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||||
contactGroup.getId(), SHARED);
|
contactGroupId, SHARED);
|
||||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
oneOf(clientHelper).setContactId(txn, contactGroupId,
|
||||||
contact.getId());
|
contact.getId());
|
||||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
@@ -167,10 +175,10 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
expectStoreMessage(txn, contactGroupId, 1, someClientSupports,
|
||||||
emptyServerSupports, emptyPropsDict, true);
|
emptyServerSupports, emptyPropsDict);
|
||||||
|
|
||||||
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
||||||
sentDict);
|
sentDict);
|
||||||
@@ -178,14 +186,15 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
t.onDatabaseOpened(txn);
|
t.onDatabaseOpened(txn);
|
||||||
|
|
||||||
|
MailboxUpdateSentEvent e = getEvent(txn, MailboxUpdateSentEvent.class);
|
||||||
|
assertNoMailboxPropertiesSent(e, someClientSupportsList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreatesGroupsAndCreatesAndSendsAtPairedStartup()
|
public void testCreatesGroupsAndCreatesAndSendsAtPairedStartup()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||||
BdfDictionary sentDict = BdfDictionary.of(new BdfEntry(
|
BdfDictionary sentDict = BdfDictionary.of(new BdfEntry(
|
||||||
GROUP_KEY_SENT_CLIENT_SUPPORTS,
|
GROUP_KEY_SENT_CLIENT_SUPPORTS,
|
||||||
@@ -207,8 +216,8 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||||
will(returnValue(SHARED));
|
will(returnValue(SHARED));
|
||||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||||
contactGroup.getId(), SHARED);
|
contactGroupId, SHARED);
|
||||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
oneOf(clientHelper).setContactId(txn, contactGroupId,
|
||||||
contact.getId());
|
contact.getId());
|
||||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||||
will(returnValue(ownProps));
|
will(returnValue(ownProps));
|
||||||
@@ -222,10 +231,10 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
expectStoreMessage(txn, contactGroupId, 1, someClientSupports,
|
||||||
someServerSupports, propsDict, true);
|
someServerSupports, propsDict);
|
||||||
|
|
||||||
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
||||||
sentDict);
|
sentDict);
|
||||||
@@ -233,14 +242,15 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
t.onDatabaseOpened(txn);
|
t.onDatabaseOpened(txn);
|
||||||
|
|
||||||
|
MailboxUpdateSentEvent e = getEvent(txn, MailboxUpdateSentEvent.class);
|
||||||
|
assertMailboxPropertiesSent(e, someClientSupportsList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUnchangedClientSupportsOnSecondStartup() throws Exception {
|
public void testUnchangedClientSupportsOnSecondStartup() throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn1 = new Transaction(null, false);
|
||||||
|
Transaction txn2 = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
|
|
||||||
Map<MessageId, BdfDictionary> emptyMessageMetadata =
|
Map<MessageId, BdfDictionary> emptyMessageMetadata =
|
||||||
new LinkedHashMap<>();
|
new LinkedHashMap<>();
|
||||||
@@ -249,46 +259,49 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
someClientSupports));
|
someClientSupports));
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
oneOf(db).containsGroup(txn1, localGroup.getId());
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(db).addGroup(txn, localGroup);
|
oneOf(db).addGroup(txn1, localGroup);
|
||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn1);
|
||||||
will(returnValue(singletonList(contact)));
|
will(returnValue(singletonList(contact)));
|
||||||
|
|
||||||
// addingContact()
|
// addingContact()
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(db).addGroup(txn, contactGroup);
|
oneOf(db).addGroup(txn1, contactGroup);
|
||||||
oneOf(clientVersioningManager).getClientVisibility(txn,
|
oneOf(clientVersioningManager).getClientVisibility(txn1,
|
||||||
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||||
will(returnValue(SHARED));
|
will(returnValue(SHARED));
|
||||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
oneOf(db).setGroupVisibility(txn1, contact.getId(),
|
||||||
contactGroup.getId(), SHARED);
|
contactGroupId, SHARED);
|
||||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
oneOf(clientHelper).setContactId(txn1, contactGroupId,
|
||||||
contact.getId());
|
contact.getId());
|
||||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn1);
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn1,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(emptyMessageMetadata));
|
will(returnValue(emptyMessageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
expectStoreMessage(txn1, contactGroupId, 1, someClientSupports,
|
||||||
emptyServerSupports, emptyPropsDict, true);
|
emptyServerSupports, emptyPropsDict);
|
||||||
|
|
||||||
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
oneOf(clientHelper).mergeGroupMetadata(txn1, localGroup.getId(),
|
||||||
sentDict);
|
sentDict);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
t.onDatabaseOpened(txn);
|
t.onDatabaseOpened(txn1);
|
||||||
|
|
||||||
|
MailboxUpdateSentEvent e = getEvent(txn1, MailboxUpdateSentEvent.class);
|
||||||
|
assertNoMailboxPropertiesSent(e, someClientSupportsList);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
oneOf(db).containsGroup(txn2, localGroup.getId());
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
oneOf(clientHelper).getGroupMetadataAsDictionary(txn2,
|
||||||
localGroup.getId());
|
localGroup.getId());
|
||||||
will(returnValue(sentDict));
|
will(returnValue(sentDict));
|
||||||
oneOf(clientHelper).parseMailboxVersionList(someClientSupports);
|
oneOf(clientHelper).parseMailboxVersionList(someClientSupports);
|
||||||
@@ -296,16 +309,16 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
t = createInstance(someClientSupportsList);
|
t = createInstance(someClientSupportsList);
|
||||||
t.onDatabaseOpened(txn);
|
t.onDatabaseOpened(txn2);
|
||||||
|
|
||||||
|
assertFalse(hasEvent(txn2, MailboxUpdateSentEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSendsUpdateWhenClientSupportsChangedOnSecondStartup()
|
public void testSendsUpdateWhenClientSupportsChangedOnSecondStartup()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn1 = new Transaction(null, false);
|
||||||
|
Transaction txn2 = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
|
|
||||||
Map<MessageId, BdfDictionary> emptyMessageMetadata =
|
Map<MessageId, BdfDictionary> emptyMessageMetadata =
|
||||||
new LinkedHashMap<>();
|
new LinkedHashMap<>();
|
||||||
@@ -314,41 +327,45 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
someClientSupports));
|
someClientSupports));
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
oneOf(db).containsGroup(txn1, localGroup.getId());
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(db).addGroup(txn, localGroup);
|
oneOf(db).addGroup(txn1, localGroup);
|
||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn1);
|
||||||
will(returnValue(singletonList(contact)));
|
will(returnValue(singletonList(contact)));
|
||||||
|
|
||||||
// addingContact()
|
// addingContact()
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(db).addGroup(txn, contactGroup);
|
oneOf(db).addGroup(txn1, contactGroup);
|
||||||
oneOf(clientVersioningManager).getClientVisibility(txn,
|
oneOf(clientVersioningManager).getClientVisibility(txn1,
|
||||||
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||||
will(returnValue(SHARED));
|
will(returnValue(SHARED));
|
||||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
oneOf(db).setGroupVisibility(txn1, contact.getId(),
|
||||||
contactGroup.getId(), SHARED);
|
contactGroupId, SHARED);
|
||||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
oneOf(clientHelper).setContactId(txn1, contactGroupId,
|
||||||
contact.getId());
|
contact.getId());
|
||||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn1);
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn1,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(emptyMessageMetadata));
|
will(returnValue(emptyMessageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
expectStoreMessage(txn1, contactGroupId, 1, someClientSupports,
|
||||||
emptyServerSupports, emptyPropsDict, true);
|
emptyServerSupports, emptyPropsDict);
|
||||||
|
|
||||||
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
oneOf(clientHelper).mergeGroupMetadata(txn1, localGroup.getId(),
|
||||||
sentDict);
|
sentDict);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
t.onDatabaseOpened(txn);
|
t.onDatabaseOpened(txn1);
|
||||||
|
|
||||||
|
MailboxUpdateSentEvent e1 =
|
||||||
|
getEvent(txn1, MailboxUpdateSentEvent.class);
|
||||||
|
assertNoMailboxPropertiesSent(e1, someClientSupportsList);
|
||||||
|
|
||||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||||
@@ -357,66 +374,68 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||||
MessageId messageId = new MessageId(getRandomId());
|
MessageId messageId = new MessageId(getRandomId());
|
||||||
messageMetadata.put(messageId, metaDictionary);
|
messageMetadata.put(messageId, metaDictionary);
|
||||||
BdfList body = BdfList.of(1, someClientSupports, someServerSupports,
|
BdfList oldBody = BdfList.of(1, someClientSupports, emptyServerSupports,
|
||||||
propsDict);
|
emptyPropsDict);
|
||||||
BdfDictionary newerSentDict = BdfDictionary.of(new BdfEntry(
|
BdfDictionary newerSentDict = BdfDictionary.of(new BdfEntry(
|
||||||
GROUP_KEY_SENT_CLIENT_SUPPORTS,
|
GROUP_KEY_SENT_CLIENT_SUPPORTS,
|
||||||
newerClientSupports));
|
newerClientSupports));
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
oneOf(db).containsGroup(txn2, localGroup.getId());
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
|
|
||||||
// Find out that we are now on newerClientSupportsList
|
// Find out that we are now on newerClientSupportsList
|
||||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
oneOf(clientHelper).getGroupMetadataAsDictionary(txn2,
|
||||||
localGroup.getId());
|
localGroup.getId());
|
||||||
will(returnValue(sentDict));
|
will(returnValue(sentDict));
|
||||||
oneOf(clientHelper).parseMailboxVersionList(someClientSupports);
|
oneOf(clientHelper).parseMailboxVersionList(someClientSupports);
|
||||||
will(returnValue(someClientSupportsList));
|
will(returnValue(someClientSupportsList));
|
||||||
|
|
||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn2);
|
||||||
will(returnValue(singletonList(contact)));
|
will(returnValue(singletonList(contact)));
|
||||||
oneOf(db).getContact(txn, contact.getId());
|
oneOf(db).getContact(txn2, contact.getId());
|
||||||
will(returnValue(contact));
|
will(returnValue(contact));
|
||||||
|
|
||||||
// getLocalUpdate()
|
// getLocalUpdate()
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn2,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
oneOf(clientHelper).getMessageAsList(txn2, messageId);
|
||||||
will(returnValue(body));
|
will(returnValue(oldBody));
|
||||||
oneOf(clientHelper).parseAndValidateMailboxUpdate(
|
oneOf(clientHelper).parseAndValidateMailboxUpdate(
|
||||||
someClientSupports, someServerSupports, propsDict);
|
someClientSupports, emptyServerSupports, emptyPropsDict);
|
||||||
will(returnValue(updateWithMailbox));
|
will(returnValue(updateNoMailbox));
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
|
|
||||||
// storeMessageReplaceLatest()
|
// storeMessageReplaceLatest()
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn2,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 2,
|
expectStoreMessage(txn2, contactGroupId, 2,
|
||||||
newerClientSupports, someServerSupports, propsDict, true);
|
newerClientSupports, emptyServerSupports, emptyPropsDict);
|
||||||
oneOf(db).removeMessage(txn, messageId);
|
oneOf(db).removeMessage(txn2, messageId);
|
||||||
|
|
||||||
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
oneOf(clientHelper).mergeGroupMetadata(txn2, localGroup.getId(),
|
||||||
newerSentDict);
|
newerSentDict);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
t = createInstance(newerClientSupportsList);
|
t = createInstance(newerClientSupportsList);
|
||||||
t.onDatabaseOpened(txn);
|
t.onDatabaseOpened(txn2);
|
||||||
|
|
||||||
|
MailboxUpdateSentEvent e2 =
|
||||||
|
getEvent(txn2, MailboxUpdateSentEvent.class);
|
||||||
|
assertNoMailboxPropertiesSent(e2, newerClientSupportsList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreatesContactGroupWhenAddingContactUnpaired()
|
public void testCreatesContactGroupWhenAddingContactUnpaired()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
@@ -429,8 +448,8 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||||
will(returnValue(SHARED));
|
will(returnValue(SHARED));
|
||||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||||
contactGroup.getId(), SHARED);
|
contactGroupId, SHARED);
|
||||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
oneOf(clientHelper).setContactId(txn, contactGroupId,
|
||||||
contact.getId());
|
contact.getId());
|
||||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
@@ -438,22 +457,23 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
expectStoreMessage(txn, contactGroupId, 1, someClientSupports,
|
||||||
emptyServerSupports, emptyPropsDict, true);
|
emptyServerSupports, emptyPropsDict);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
t.addingContact(txn, contact);
|
t.addingContact(txn, contact);
|
||||||
|
|
||||||
|
MailboxUpdateSentEvent e = getEvent(txn, MailboxUpdateSentEvent.class);
|
||||||
|
assertNoMailboxPropertiesSent(e, someClientSupportsList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreatesContactGroupAndCreatesAndSendsWhenAddingContactPaired()
|
public void testCreatesContactGroupAndCreatesAndSendsWhenAddingContactPaired()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
@@ -466,8 +486,8 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||||
will(returnValue(SHARED));
|
will(returnValue(SHARED));
|
||||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||||
contactGroup.getId(), SHARED);
|
contactGroupId, SHARED);
|
||||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
oneOf(clientHelper).setContactId(txn, contactGroupId,
|
||||||
contact.getId());
|
contact.getId());
|
||||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||||
will(returnValue(ownProps));
|
will(returnValue(ownProps));
|
||||||
@@ -481,21 +501,22 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
expectStoreMessage(txn, contactGroupId, 1, someClientSupports,
|
||||||
someServerSupports, propsDict, true);
|
someServerSupports, propsDict);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
t.addingContact(txn, contact);
|
t.addingContact(txn, contact);
|
||||||
|
|
||||||
|
MailboxUpdateSentEvent e = getEvent(txn, MailboxUpdateSentEvent.class);
|
||||||
|
assertMailboxPropertiesSent(e, someClientSupportsList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRemovesGroupWhenRemovingContact() throws Exception {
|
public void testRemovesGroupWhenRemovingContact() throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
@@ -512,9 +533,6 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
public void testDoesNotDeleteAnythingWhenFirstUpdateIsDelivered()
|
public void testDoesNotDeleteAnythingWhenFirstUpdateIsDelivered()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
|
||||||
GroupId contactGroupId = new GroupId(getRandomId());
|
|
||||||
Message message = getMessage(contactGroupId);
|
|
||||||
BdfList body = BdfList.of(1, someClientSupports, someServerSupports,
|
BdfList body = BdfList.of(1, someClientSupports, someServerSupports,
|
||||||
propsDict);
|
propsDict);
|
||||||
Metadata meta = new Metadata();
|
Metadata meta = new Metadata();
|
||||||
@@ -549,16 +567,23 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||||
t.incomingMessage(txn, message, meta));
|
t.incomingMessage(txn, message, meta));
|
||||||
assertTrue(hasEvent(txn, RemoteMailboxUpdateEvent.class));
|
|
||||||
|
RemoteMailboxUpdateEvent e =
|
||||||
|
getEvent(txn, RemoteMailboxUpdateEvent.class);
|
||||||
|
assertEquals(contact.getId(), e.getContact());
|
||||||
|
MailboxUpdate u = e.getMailboxUpdate();
|
||||||
|
assertTrue(u.hasMailbox());
|
||||||
|
MailboxUpdateWithMailbox uMailbox = (MailboxUpdateWithMailbox) u;
|
||||||
|
assertEquals(updateWithMailbox.getClientSupports(),
|
||||||
|
uMailbox.getClientSupports());
|
||||||
|
assertEquals(updateWithMailbox.getMailboxProperties(),
|
||||||
|
uMailbox.getMailboxProperties());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeletesOlderUpdateWhenUpdateIsDelivered()
|
public void testDeletesOlderUpdateWhenUpdateIsDelivered()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
|
||||||
GroupId contactGroupId = new GroupId(getRandomId());
|
|
||||||
Message message = getMessage(contactGroupId);
|
|
||||||
BdfList body = BdfList.of(1, someClientSupports, someServerSupports,
|
BdfList body = BdfList.of(1, someClientSupports, someServerSupports,
|
||||||
propsDict);
|
propsDict);
|
||||||
Metadata meta = new Metadata();
|
Metadata meta = new Metadata();
|
||||||
@@ -601,14 +626,22 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||||
t.incomingMessage(txn, message, meta));
|
t.incomingMessage(txn, message, meta));
|
||||||
assertTrue(hasEvent(txn, RemoteMailboxUpdateEvent.class));
|
|
||||||
|
RemoteMailboxUpdateEvent e =
|
||||||
|
getEvent(txn, RemoteMailboxUpdateEvent.class);
|
||||||
|
assertEquals(contact.getId(), e.getContact());
|
||||||
|
MailboxUpdate u = e.getMailboxUpdate();
|
||||||
|
assertTrue(u.hasMailbox());
|
||||||
|
MailboxUpdateWithMailbox uMailbox = (MailboxUpdateWithMailbox) u;
|
||||||
|
assertEquals(updateWithMailbox.getClientSupports(),
|
||||||
|
uMailbox.getClientSupports());
|
||||||
|
assertEquals(updateWithMailbox.getMailboxProperties(),
|
||||||
|
uMailbox.getMailboxProperties());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeletesObsoleteUpdateWhenDelivered() throws Exception {
|
public void testDeletesObsoleteUpdateWhenDelivered() throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
GroupId contactGroupId = new GroupId(getRandomId());
|
|
||||||
Message message = getMessage(contactGroupId);
|
|
||||||
Metadata meta = new Metadata();
|
Metadata meta = new Metadata();
|
||||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||||
new BdfEntry(MSG_KEY_VERSION, 3),
|
new BdfEntry(MSG_KEY_VERSION, 3),
|
||||||
@@ -635,16 +668,13 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||||
t.incomingMessage(txn, message, meta));
|
t.incomingMessage(txn, message, meta));
|
||||||
|
|
||||||
assertFalse(hasEvent(txn, RemoteMailboxUpdateEvent.class));
|
assertFalse(hasEvent(txn, RemoteMailboxUpdateEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreatesAndStoresLocalUpdateWithNewVersionOnPairing()
|
public void testCreatesAndStoresLocalUpdateWithNewVersionOnPairing()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Contact contact = getContact();
|
|
||||||
List<Contact> contacts = singletonList(contact);
|
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
|
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||||
MessageId latestId = new MessageId(getRandomId());
|
MessageId latestId = new MessageId(getRandomId());
|
||||||
@@ -671,23 +701,33 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 2, someClientSupports,
|
expectStoreMessage(txn, contactGroupId, 2, someClientSupports,
|
||||||
someServerSupports, propsDict, true);
|
someServerSupports, propsDict);
|
||||||
oneOf(db).removeMessage(txn, latestId);
|
oneOf(db).removeMessage(txn, latestId);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
t.mailboxPaired(txn, ownProps.getOnion(), someServerSupportsList);
|
t.mailboxPaired(txn, ownProps);
|
||||||
|
|
||||||
|
MailboxPairedEvent e = getEvent(txn, MailboxPairedEvent.class);
|
||||||
|
assertEquals(ownProps, e.getProperties());
|
||||||
|
Map<ContactId, MailboxUpdateWithMailbox> localUpdates =
|
||||||
|
e.getLocalUpdates();
|
||||||
|
assertEquals(singleton(contact.getId()), localUpdates.keySet());
|
||||||
|
MailboxUpdateWithMailbox u = localUpdates.get(contact.getId());
|
||||||
|
assertEquals(updateWithMailbox.getClientSupports(),
|
||||||
|
u.getClientSupports());
|
||||||
|
assertEquals(updateWithMailbox.getMailboxProperties(),
|
||||||
|
u.getMailboxProperties());
|
||||||
|
|
||||||
|
assertFalse(hasEvent(txn, MailboxUpdateSentEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStoresLocalUpdateNoMailboxWithNewVersionOnUnpairing()
|
public void testStoresLocalUpdateNoMailboxWithNewVersionOnUnpairing()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Contact contact = getContact();
|
|
||||||
List<Contact> contacts = singletonList(contact);
|
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
|
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||||
@@ -709,22 +749,28 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 2, someClientSupports,
|
expectStoreMessage(txn, contactGroupId, 2, someClientSupports,
|
||||||
emptyServerSupports, emptyPropsDict, true);
|
emptyServerSupports, emptyPropsDict);
|
||||||
oneOf(db).removeMessage(txn, latestId);
|
oneOf(db).removeMessage(txn, latestId);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
t.mailboxUnpaired(txn);
|
t.mailboxUnpaired(txn);
|
||||||
|
|
||||||
|
MailboxUnpairedEvent e = getEvent(txn, MailboxUnpairedEvent.class);
|
||||||
|
Map<ContactId, MailboxUpdate> localUpdates = e.getLocalUpdates();
|
||||||
|
assertEquals(singleton(contact.getId()), localUpdates.keySet());
|
||||||
|
MailboxUpdate u = localUpdates.get(contact.getId());
|
||||||
|
assertFalse(u.hasMailbox());
|
||||||
|
|
||||||
|
assertFalse(hasEvent(txn, MailboxUpdateSentEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetRemoteUpdate() throws Exception {
|
public void testGetRemoteUpdate() throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||||
@@ -742,7 +788,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
.createContactGroup(CLIENT_ID, MAJOR_VERSION, contact);
|
.createContactGroup(CLIENT_ID, MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
||||||
will(returnValue(body));
|
will(returnValue(body));
|
||||||
@@ -760,8 +806,6 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
public void testGetRemoteUpdateReturnsNullBecauseNoUpdate()
|
public void testGetRemoteUpdateReturnsNullBecauseNoUpdate()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
Map<MessageId, BdfDictionary> emptyMessageMetadata =
|
Map<MessageId, BdfDictionary> emptyMessageMetadata =
|
||||||
new LinkedHashMap<>();
|
new LinkedHashMap<>();
|
||||||
|
|
||||||
@@ -772,7 +816,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(emptyMessageMetadata));
|
will(returnValue(emptyMessageMetadata));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -783,8 +827,6 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetRemoteUpdateNoMailbox() throws Exception {
|
public void testGetRemoteUpdateNoMailbox() throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||||
@@ -802,7 +844,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
||||||
will(returnValue(body));
|
will(returnValue(body));
|
||||||
@@ -819,8 +861,6 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetLocalUpdate() throws Exception {
|
public void testGetLocalUpdate() throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||||
new BdfEntry(MSG_KEY_LOCAL, true)
|
new BdfEntry(MSG_KEY_LOCAL, true)
|
||||||
@@ -838,7 +878,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
||||||
will(returnValue(body));
|
will(returnValue(body));
|
||||||
@@ -855,8 +895,6 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetLocalUpdateNoMailbox() throws Exception {
|
public void testGetLocalUpdateNoMailbox() throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||||
new BdfEntry(MSG_KEY_LOCAL, true)
|
new BdfEntry(MSG_KEY_LOCAL, true)
|
||||||
@@ -874,7 +912,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
||||||
will(returnValue(body));
|
will(returnValue(body));
|
||||||
@@ -890,15 +928,14 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
private void expectStoreMessage(Transaction txn, GroupId g,
|
private void expectStoreMessage(Transaction txn, GroupId g,
|
||||||
long version, BdfList clientSupports, BdfList serverSupports,
|
long version, BdfList clientSupports, BdfList serverSupports,
|
||||||
BdfDictionary properties, boolean local)
|
BdfDictionary properties) throws Exception {
|
||||||
throws Exception {
|
|
||||||
BdfList body = BdfList.of(version, clientSupports, serverSupports,
|
BdfList body = BdfList.of(version, clientSupports, serverSupports,
|
||||||
properties);
|
properties);
|
||||||
Message message = getMessage(g);
|
Message message = getMessage(g);
|
||||||
long timestamp = message.getTimestamp();
|
long timestamp = message.getTimestamp();
|
||||||
BdfDictionary meta = BdfDictionary.of(
|
BdfDictionary meta = BdfDictionary.of(
|
||||||
new BdfEntry(MSG_KEY_VERSION, version),
|
new BdfEntry(MSG_KEY_VERSION, version),
|
||||||
new BdfEntry(MSG_KEY_LOCAL, local)
|
new BdfEntry(MSG_KEY_LOCAL, true)
|
||||||
);
|
);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
@@ -910,4 +947,22 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
false);
|
false);
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertNoMailboxPropertiesSent(MailboxUpdateSentEvent e,
|
||||||
|
List<MailboxVersion> clientSupports) {
|
||||||
|
assertEquals(contact.getId(), e.getContactId());
|
||||||
|
MailboxUpdate u = e.getMailboxUpdate();
|
||||||
|
assertEquals(clientSupports, u.getClientSupports());
|
||||||
|
assertFalse(u.hasMailbox());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertMailboxPropertiesSent(MailboxUpdateSentEvent e,
|
||||||
|
List<MailboxVersion> clientSupports) {
|
||||||
|
assertEquals(contact.getId(), e.getContactId());
|
||||||
|
MailboxUpdate u = e.getMailboxUpdate();
|
||||||
|
assertEquals(clientSupports, u.getClientSupports());
|
||||||
|
assertTrue(u.hasMailbox());
|
||||||
|
MailboxUpdateWithMailbox uMailbox = (MailboxUpdateWithMailbox) u;
|
||||||
|
assertEquals(updateProps, uMailbox.getMailboxProperties());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
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.test.TestUtils.getContactId;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
|
|
||||||
|
public class OwnMailboxClientTest 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 contactListWorker =
|
||||||
|
context.mock(MailboxWorker.class, "contactListWorker");
|
||||||
|
private final MailboxWorker uploadWorker1 =
|
||||||
|
context.mock(MailboxWorker.class, "uploadWorker1");
|
||||||
|
private final MailboxWorker uploadWorker2 =
|
||||||
|
context.mock(MailboxWorker.class, "uploadWorker2");
|
||||||
|
private final MailboxWorker downloadWorker =
|
||||||
|
context.mock(MailboxWorker.class, "downloadWorker");
|
||||||
|
|
||||||
|
private final MailboxProperties properties =
|
||||||
|
getMailboxProperties(true, CLIENT_SUPPORTS);
|
||||||
|
private final MailboxFolderId folderId = new MailboxFolderId(getRandomId());
|
||||||
|
private final ContactId contactId1 = getContactId();
|
||||||
|
private final ContactId contactId2 = getContactId();
|
||||||
|
|
||||||
|
private final OwnMailboxClient client;
|
||||||
|
|
||||||
|
public OwnMailboxClientTest() {
|
||||||
|
expectCreateContactListWorker();
|
||||||
|
client = new OwnMailboxClient(workerFactory, connectivityChecker,
|
||||||
|
reachabilityMonitor, properties);
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStartAndDestroyWithNoContactsAssigned() {
|
||||||
|
expectStartWorker(contactListWorker);
|
||||||
|
client.start();
|
||||||
|
|
||||||
|
expectDestroyWorker(contactListWorker);
|
||||||
|
expectDestroyConnectivityChecker();
|
||||||
|
client.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAssignContactForUploadAndDestroyClient() {
|
||||||
|
expectStartWorker(contactListWorker);
|
||||||
|
client.start();
|
||||||
|
|
||||||
|
// When the contact is assigned, the worker should be created and
|
||||||
|
// started
|
||||||
|
expectCreateUploadWorker(contactId1, uploadWorker1);
|
||||||
|
expectStartWorker(uploadWorker1);
|
||||||
|
client.assignContactForUpload(contactId1, properties, folderId);
|
||||||
|
|
||||||
|
// When the client is destroyed, the worker should be destroyed
|
||||||
|
expectDestroyWorker(uploadWorker1);
|
||||||
|
expectDestroyWorker(contactListWorker);
|
||||||
|
expectDestroyConnectivityChecker();
|
||||||
|
client.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAssignAndDeassignContactForUpload() {
|
||||||
|
expectStartWorker(contactListWorker);
|
||||||
|
client.start();
|
||||||
|
|
||||||
|
// When the contact is assigned, the worker should be created and
|
||||||
|
// started
|
||||||
|
expectCreateUploadWorker(contactId1, uploadWorker1);
|
||||||
|
expectStartWorker(uploadWorker1);
|
||||||
|
client.assignContactForUpload(contactId1, properties, folderId);
|
||||||
|
|
||||||
|
// When the contact is deassigned, the worker should be destroyed
|
||||||
|
expectDestroyWorker(uploadWorker1);
|
||||||
|
client.deassignContactForUpload(contactId1);
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
|
expectDestroyWorker(contactListWorker);
|
||||||
|
expectDestroyConnectivityChecker();
|
||||||
|
client.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAssignAndDeassignTwoContactsForUpload() {
|
||||||
|
expectStartWorker(contactListWorker);
|
||||||
|
client.start();
|
||||||
|
|
||||||
|
// When the first contact is assigned, the first worker should be
|
||||||
|
// created and started
|
||||||
|
expectCreateUploadWorker(contactId1, uploadWorker1);
|
||||||
|
expectStartWorker(uploadWorker1);
|
||||||
|
client.assignContactForUpload(contactId1, properties, folderId);
|
||||||
|
|
||||||
|
// When the second contact is assigned, the second worker should be
|
||||||
|
// created and started
|
||||||
|
expectCreateUploadWorker(contactId2, uploadWorker2);
|
||||||
|
expectStartWorker(uploadWorker2);
|
||||||
|
client.assignContactForUpload(contactId2, properties, folderId);
|
||||||
|
|
||||||
|
// When the second contact is deassigned, the second worker should be
|
||||||
|
// destroyed
|
||||||
|
expectDestroyWorker(uploadWorker2);
|
||||||
|
client.deassignContactForUpload(contactId2);
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
|
// When the first contact is deassigned, the first worker should be
|
||||||
|
// destroyed
|
||||||
|
expectDestroyWorker(uploadWorker1);
|
||||||
|
client.deassignContactForUpload(contactId1);
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
|
expectDestroyWorker(contactListWorker);
|
||||||
|
expectDestroyConnectivityChecker();
|
||||||
|
client.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAssignContactForDownloadAndDestroyClient() {
|
||||||
|
expectStartWorker(contactListWorker);
|
||||||
|
client.start();
|
||||||
|
|
||||||
|
// When the contact is assigned, the worker should be created and
|
||||||
|
// started
|
||||||
|
expectCreateDownloadWorker();
|
||||||
|
expectStartWorker(downloadWorker);
|
||||||
|
client.assignContactForDownload(contactId1, properties, folderId);
|
||||||
|
|
||||||
|
// When the client is destroyed, the worker should be destroyed
|
||||||
|
expectDestroyWorker(downloadWorker);
|
||||||
|
expectDestroyWorker(contactListWorker);
|
||||||
|
expectDestroyConnectivityChecker();
|
||||||
|
client.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAssignAndDeassignTwoContactsForDownload() {
|
||||||
|
expectStartWorker(contactListWorker);
|
||||||
|
client.start();
|
||||||
|
|
||||||
|
// When the first contact is assigned, the worker should be created and
|
||||||
|
// started
|
||||||
|
expectCreateDownloadWorker();
|
||||||
|
expectStartWorker(downloadWorker);
|
||||||
|
client.assignContactForDownload(contactId1, properties, folderId);
|
||||||
|
|
||||||
|
// When the second contact is assigned, nothing should happen to the
|
||||||
|
// worker
|
||||||
|
client.assignContactForDownload(contactId2, properties, folderId);
|
||||||
|
|
||||||
|
// When the first contact is deassigned, nothing should happen to the
|
||||||
|
// worker
|
||||||
|
client.deassignContactForDownload(contactId1);
|
||||||
|
|
||||||
|
// When the second contact is deassigned, the worker should be
|
||||||
|
// destroyed
|
||||||
|
expectDestroyWorker(downloadWorker);
|
||||||
|
client.deassignContactForDownload(contactId2);
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
|
expectDestroyWorker(contactListWorker);
|
||||||
|
expectDestroyConnectivityChecker();
|
||||||
|
client.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectCreateContactListWorker() {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(workerFactory).createContactListWorkerForOwnMailbox(
|
||||||
|
connectivityChecker, properties);
|
||||||
|
will(returnValue(contactListWorker));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectCreateUploadWorker(ContactId contactId,
|
||||||
|
MailboxWorker worker) {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(workerFactory).createUploadWorker(connectivityChecker,
|
||||||
|
properties, folderId, contactId);
|
||||||
|
will(returnValue(worker));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectCreateDownloadWorker() {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(workerFactory).createDownloadWorkerForOwnMailbox(
|
||||||
|
connectivityChecker, reachabilityMonitor, properties);
|
||||||
|
will(returnValue(downloadWorker));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectStartWorker(MailboxWorker worker) {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(worker).start();
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectDestroyWorker(MailboxWorker worker) {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(worker).destroy();
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectDestroyConnectivityChecker() {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(connectivityChecker).destroy();
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.db.Transaction;
|
|||||||
import org.briarproject.bramble.api.db.TransactionManager;
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
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.api.system.Clock;
|
||||||
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
@@ -15,8 +16,10 @@ import org.jmock.lib.action.DoAllAction;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
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.getMailboxProperties;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@@ -39,6 +42,8 @@ public class OwnMailboxConnectivityCheckerTest extends BrambleMockTestCase {
|
|||||||
private final MailboxProperties properties =
|
private final MailboxProperties properties =
|
||||||
getMailboxProperties(true, CLIENT_SUPPORTS);
|
getMailboxProperties(true, CLIENT_SUPPORTS);
|
||||||
private final long now = System.currentTimeMillis();
|
private final long now = System.currentTimeMillis();
|
||||||
|
private final List<MailboxVersion> serverSupports =
|
||||||
|
singletonList(new MailboxVersion(123, 456));
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testObserverIsCalledWhenCheckSucceeds() throws Exception {
|
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
|
// When the check succeeds, the success should be recorded in the DB
|
||||||
// and the observer should be called
|
// and the observer should be called
|
||||||
context.checking(new DbExpectations() {{
|
context.checking(new DbExpectations() {{
|
||||||
oneOf(mailboxApi).checkStatus(properties);
|
oneOf(mailboxApi).getServerSupports(properties);
|
||||||
will(returnValue(true));
|
will(returnValue(serverSupports));
|
||||||
oneOf(clock).currentTimeMillis();
|
oneOf(clock).currentTimeMillis();
|
||||||
will(returnValue(now));
|
will(returnValue(now));
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, now);
|
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, now,
|
||||||
|
serverSupports);
|
||||||
oneOf(observer).onConnectivityCheckSucceeded();
|
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
|
// When the check fails, the failure should be recorded in the DB and
|
||||||
// the observer should not be called
|
// the observer should not be called
|
||||||
context.checking(new DbExpectations() {{
|
context.checking(new DbExpectations() {{
|
||||||
oneOf(mailboxApi).checkStatus(properties);
|
oneOf(mailboxApi).getServerSupports(properties);
|
||||||
will(throwException(new IOException()));
|
will(throwException(new IOException()));
|
||||||
oneOf(clock).currentTimeMillis();
|
oneOf(clock).currentTimeMillis();
|
||||||
will(returnValue(now));
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user