mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
106 Commits
release-1.
...
print-db-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6fa7520e9 | ||
|
|
d3bffaadf3 | ||
|
|
12b887881d | ||
|
|
74a3f54d28 | ||
|
|
edcb234b93 | ||
|
|
dae00c7e4e | ||
|
|
29b16c4d74 | ||
|
|
40d58a9359 | ||
|
|
60a1a4d2d1 | ||
|
|
238aeb3abd | ||
|
|
62c16fad09 | ||
|
|
68e57bda0d | ||
|
|
0df73dbf0a | ||
|
|
5b648cbd35 | ||
|
|
5e7891d78a | ||
|
|
d5e17c8201 | ||
|
|
d572ae71e7 | ||
|
|
2e9d9dac84 | ||
|
|
573817c4c9 | ||
|
|
4f00f39d3f | ||
|
|
c7d3628ecb | ||
|
|
b198bef5f8 | ||
|
|
cff94009a1 | ||
|
|
44f9f0bbc5 | ||
|
|
5fdb43ce9b | ||
|
|
725d11d960 | ||
|
|
7cf2c2faa7 | ||
|
|
4b3c26feb6 | ||
|
|
2fbeb29195 | ||
|
|
5892fba237 | ||
|
|
cc9f04980a | ||
|
|
44fb2a5c59 | ||
|
|
68e534348f | ||
|
|
795a8f1e70 | ||
|
|
bf968b227e | ||
|
|
8b94dad01f | ||
|
|
fa0610fff1 | ||
|
|
1d94db8d60 | ||
|
|
1d4f450960 | ||
|
|
7f6b31d36c | ||
|
|
05737d858d | ||
|
|
2c8e2ab6b8 | ||
|
|
97f64fb31c | ||
|
|
e66152e812 | ||
|
|
101ffa2f08 | ||
|
|
13eebe393a | ||
|
|
5bc5791ddb | ||
|
|
a35e9af1de | ||
|
|
ade89c14c4 | ||
|
|
16cfb89310 | ||
|
|
78f00863dd | ||
|
|
bd50a109cd | ||
|
|
38c91aea32 | ||
|
|
92517ae7c0 | ||
|
|
dd1c8c8301 | ||
|
|
edc1029e92 | ||
|
|
27e9338a12 | ||
|
|
243df3096a | ||
|
|
50f9718037 | ||
|
|
88c8bd32a5 | ||
|
|
3e597ceff8 | ||
|
|
3d6972fd73 | ||
|
|
288f3331ec | ||
|
|
a14ee55f12 | ||
|
|
2a85907565 | ||
|
|
1fe7b2f451 | ||
|
|
585ceb626b | ||
|
|
5da782cf18 | ||
|
|
ec6b999d30 | ||
|
|
d8a925a94f | ||
|
|
3de4386e63 | ||
|
|
8c60787866 | ||
|
|
fa8ca8e6cf | ||
|
|
07814d43de | ||
|
|
d80ba0f556 | ||
|
|
d70e1ed32e | ||
|
|
eec2c87797 | ||
|
|
a256027916 | ||
|
|
bf0f99277a | ||
|
|
2d62deb2db | ||
|
|
e3682bb331 | ||
|
|
6805040ac4 | ||
|
|
4198e1f22a | ||
|
|
ee11d2a28d | ||
|
|
f3718e496c | ||
|
|
414c296abd | ||
|
|
79051439c5 | ||
|
|
32b62d3e30 | ||
|
|
e3f2a30120 | ||
|
|
58a122ee28 | ||
|
|
f5f7b3eb51 | ||
|
|
098128c8a8 | ||
|
|
27d566df7a | ||
|
|
9469825f4f | ||
|
|
5ce90422c6 | ||
|
|
256662e094 | ||
|
|
dc7f1e0c86 | ||
|
|
a54e1d424c | ||
|
|
9fa3ee18a4 | ||
|
|
84be347695 | ||
|
|
952ac2c922 | ||
|
|
4390c810d1 | ||
|
|
a567301e49 | ||
|
|
5e8d5c96fc | ||
|
|
80d804d280 | ||
|
|
7fad299cf0 |
@@ -98,6 +98,7 @@ bridge test:
|
||||
allow_failure: true
|
||||
script:
|
||||
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
|
||||
timeout: 3h
|
||||
|
||||
mailbox integration test:
|
||||
extends: .optional_tests
|
||||
@@ -122,5 +123,6 @@ pre_release_tests:
|
||||
extends: .optional_tests
|
||||
script:
|
||||
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
|
||||
timeout: 3h
|
||||
only:
|
||||
- tags
|
||||
|
||||
@@ -15,8 +15,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
versionCode 10405
|
||||
versionName "1.4.5"
|
||||
versionCode 10406
|
||||
versionName "1.4.6"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
@@ -43,7 +43,7 @@ configurations {
|
||||
dependencies {
|
||||
implementation project(path: ':bramble-core', configuration: 'default')
|
||||
tor "org.briarproject:tor-android:$tor_version"
|
||||
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version@zip"
|
||||
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version"
|
||||
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
|
||||
@@ -70,11 +70,6 @@ clean.dependsOn cleanTorBinaries
|
||||
|
||||
task unpackTorBinaries {
|
||||
doLast {
|
||||
copy {
|
||||
from configurations.tor.collect { zipTree(it) }
|
||||
into torBinariesDir
|
||||
include 'geoip.zip'
|
||||
}
|
||||
configurations.tor.each { outer ->
|
||||
zipTree(outer).each { inner ->
|
||||
if (inner.name.endsWith('_arm_pie.zip')) {
|
||||
|
||||
@@ -39,6 +39,6 @@ class AndroidRemovableDrivePlugin extends RemovableDrivePlugin {
|
||||
OutputStream openOutputStream(TransportProperties p) throws IOException {
|
||||
String uri = p.get(PROP_URI);
|
||||
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
||||
return app.getContentResolver().openOutputStream(Uri.parse(uri));
|
||||
return app.getContentResolver().openOutputStream(Uri.parse(uri), "wt");
|
||||
}
|
||||
}
|
||||
|
||||
0
bramble-android/src/main/res/raw/.gitkeep
Normal file
0
bramble-android/src/main/res/raw/.gitkeep
Normal file
@@ -87,8 +87,8 @@ dependencyVerification {
|
||||
'org.apache.httpcomponents:httpmime:4.5.6:httpmime-4.5.6.jar:0b2b1102c18d3c7e05a77214b9b7501a6f6056174ae5604e0e256776eda7553e',
|
||||
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
|
||||
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
|
||||
'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a:obfs4proxy-android-0.0.12-dev-40245c4a.zip:8ab05a8f8391be2cb5ab2b665c281a06d9e3a756bd0f95a40a36ca927866ea82',
|
||||
'org.briarproject:tor-android:0.3.5.17:tor-android-0.3.5.17.jar:1888afc10a26b93d00a010ea27bf0b1b162a6d524688b08b98d70d14dc363b54',
|
||||
'org.briarproject:obfs4proxy-android:0.0.12:obfs4proxy-android-0.0.12.jar:84159d2a4668abc40e3fccaa1f6fa0c04892863f9eb80a866ac8928d9f9a7e89',
|
||||
'org.briarproject:tor-android:0.4.5.12-2:tor-android-0.4.5.12-2.jar:8545dbcef2bb6aa89c32bb6f8ac51f7a64bce3ae85845b3578ffdeb9b206feb9',
|
||||
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
||||
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
||||
'org.checkerframework:checker-qual:3.5.0:checker-qual-3.5.0.jar:729990b3f18a95606fc2573836b6958bcdb44cb52bfbd1b7aa9c339cff35a5a4',
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package org.briarproject.bramble.api;
|
||||
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public abstract class StringMap extends Hashtable<String, String> {
|
||||
|
||||
protected StringMap(Map<String, String> m) {
|
||||
@@ -52,4 +58,31 @@ public abstract class StringMap extends Hashtable<String, String> {
|
||||
public void putLong(String key, long value) {
|
||||
put(key, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public int[] getIntArray(String key) {
|
||||
String s = get(key);
|
||||
if (s == null) return null;
|
||||
// Handle empty string because "".split(",") returns {""}
|
||||
if (s.length() == 0) return new int[0];
|
||||
String[] intStrings = s.split(",");
|
||||
int[] ints = new int[intStrings.length];
|
||||
try {
|
||||
for (int i = 0; i < ints.length; i++) {
|
||||
ints[i] = Integer.parseInt(intStrings[i]);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
return ints;
|
||||
}
|
||||
|
||||
public void putIntArray(String key, int[] value) {
|
||||
List<String> intStrings = new ArrayList<>();
|
||||
for (int integer : value) {
|
||||
intStrings.add(String.valueOf(integer));
|
||||
}
|
||||
// Puts empty string if input array value is empty
|
||||
put(key, StringUtils.join(intStrings, ","));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,14 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
public abstract class UniqueId extends Bytes {
|
||||
public class UniqueId extends Bytes {
|
||||
|
||||
/**
|
||||
* The length of a unique identifier in bytes.
|
||||
*/
|
||||
public static final int LENGTH = 32;
|
||||
|
||||
protected UniqueId(byte[] id) {
|
||||
public UniqueId(byte[] id) {
|
||||
super(id);
|
||||
if (id.length != LENGTH) throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
@@ -20,6 +21,8 @@ import java.security.GeneralSecurityException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface ClientHelper {
|
||||
|
||||
@@ -123,6 +126,18 @@ public interface ClientHelper {
|
||||
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
|
||||
BdfDictionary properties) throws FormatException;
|
||||
|
||||
/**
|
||||
* Parse and validate the property dictionary of a Mailbox property update
|
||||
* message.
|
||||
*
|
||||
* @return the properties for using the Mailbox, or null if there is no
|
||||
* Mailbox available
|
||||
* @throws FormatException if the properties are not valid
|
||||
*/
|
||||
@Nullable
|
||||
MailboxPropertiesUpdate parseAndValidateMailboxPropertiesUpdate(
|
||||
BdfDictionary properties) throws FormatException;
|
||||
|
||||
/**
|
||||
* Retrieves the contact ID from the group metadata of the given contact
|
||||
* group.
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.bramble.api.contact;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/**
|
||||
@@ -17,9 +16,4 @@ public class PendingContactId extends UniqueId {
|
||||
public PendingContactId(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
return o instanceof PendingContactId && super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.api.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
@@ -10,6 +11,8 @@ import javax.annotation.Nullable;
|
||||
@NotNullByDefault
|
||||
public interface CryptoComponent {
|
||||
|
||||
UniqueId generateUniqueId();
|
||||
|
||||
SecretKey generateSecretKey();
|
||||
|
||||
SecureRandom getSecureRandom();
|
||||
@@ -172,9 +175,11 @@ public interface CryptoComponent {
|
||||
String asciiArmour(byte[] b, int lineLength);
|
||||
|
||||
/**
|
||||
* Encode the onion/hidden service address given its public key. As
|
||||
* specified here: https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt?id=29245fd5#n2135
|
||||
* Encode the Onion given its public key. Specified here:
|
||||
* https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt?id=29245fd5#n2135
|
||||
*
|
||||
* @return the encoded onion, base32 chars
|
||||
*/
|
||||
String encodeOnionAddress(byte[] publicKey);
|
||||
String encodeOnion(byte[] publicKey);
|
||||
|
||||
}
|
||||
|
||||
@@ -472,9 +472,9 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Reset the transmission count, expiry time and ETA of all messages that
|
||||
* are eligible to be sent to the given contact. This includes messages that
|
||||
* have already been sent and are not yet due for retransmission.
|
||||
* Resets the transmission count, expiry time and max latency of all messages
|
||||
* that are eligible to be sent to the given contact. This includes messages
|
||||
* that have already been sent and are not yet due for retransmission.
|
||||
*/
|
||||
void resetUnackedMessagesToSend(Transaction txn, ContactId c)
|
||||
throws DbException;
|
||||
@@ -746,4 +746,6 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
*/
|
||||
void updateTransportKeys(Transaction txn, Collection<TransportKeySet> keys)
|
||||
throws DbException;
|
||||
|
||||
void printStats(Transaction txn) throws DbException;
|
||||
}
|
||||
|
||||
@@ -21,9 +21,4 @@ public class AuthorId extends UniqueId {
|
||||
public AuthorId(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof AuthorId && super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,4 +32,13 @@ public interface MailboxManager {
|
||||
*/
|
||||
MailboxPairingTask startPairingTask(String qrCodePayload);
|
||||
|
||||
/**
|
||||
* Can be used by the UI to test the mailbox connection.
|
||||
*
|
||||
* @return true (success) or false (error).
|
||||
* A {@link OwnMailboxConnectionStatusEvent} might be broadcast with a new
|
||||
* {@link MailboxStatus}.
|
||||
*/
|
||||
boolean checkConnection();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,59 +1,25 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public abstract class MailboxPairingState {
|
||||
|
||||
/**
|
||||
* The QR code payload that was scanned by the user.
|
||||
* This is null if the code should not be re-used anymore in this state.
|
||||
*/
|
||||
@Nullable
|
||||
public final String qrCodePayload;
|
||||
|
||||
MailboxPairingState(@Nullable String qrCodePayload) {
|
||||
this.qrCodePayload = qrCodePayload;
|
||||
}
|
||||
|
||||
public static class QrCodeReceived extends MailboxPairingState {
|
||||
public QrCodeReceived(String qrCodePayload) {
|
||||
super(qrCodePayload);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Pairing extends MailboxPairingState {
|
||||
public Pairing(String qrCodePayload) {
|
||||
super(qrCodePayload);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Paired extends MailboxPairingState {
|
||||
public Paired() {
|
||||
super(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidQrCode extends MailboxPairingState {
|
||||
public InvalidQrCode() {
|
||||
super(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MailboxAlreadyPaired extends MailboxPairingState {
|
||||
public MailboxAlreadyPaired() {
|
||||
super(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ConnectionError extends MailboxPairingState {
|
||||
public ConnectionError(String qrCodePayload) {
|
||||
super(qrCodePayload);
|
||||
}
|
||||
}
|
||||
|
||||
public static class UnexpectedError extends MailboxPairingState {
|
||||
public UnexpectedError(String qrCodePayload) {
|
||||
super(qrCodePayload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@@ -11,18 +13,25 @@ public class MailboxProperties {
|
||||
private final String baseUrl;
|
||||
private final MailboxAuthToken authToken;
|
||||
private final boolean owner;
|
||||
private final List<MailboxVersion> serverSupports;
|
||||
|
||||
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
|
||||
boolean owner) {
|
||||
boolean owner, List<MailboxVersion> serverSupports) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.authToken = authToken;
|
||||
this.owner = owner;
|
||||
this.serverSupports = serverSupports;
|
||||
}
|
||||
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
public String getOnion() {
|
||||
return baseUrl.replaceFirst("^http://", "")
|
||||
.replaceFirst("\\.onion$", "");
|
||||
}
|
||||
|
||||
public MailboxAuthToken getAuthToken() {
|
||||
return authToken;
|
||||
}
|
||||
@@ -30,4 +39,8 @@ public class MailboxProperties {
|
||||
public boolean isOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public List<MailboxVersion> getServerSupports() {
|
||||
return serverSupports;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class MailboxPropertiesUpdate {
|
||||
|
||||
private final String onion;
|
||||
private final MailboxAuthToken authToken;
|
||||
private final MailboxFolderId inboxId;
|
||||
private final MailboxFolderId outboxId;
|
||||
|
||||
public MailboxPropertiesUpdate(String onion,
|
||||
MailboxAuthToken authToken, MailboxFolderId inboxId,
|
||||
MailboxFolderId outboxId) {
|
||||
this.onion = onion;
|
||||
this.authToken = authToken;
|
||||
this.inboxId = inboxId;
|
||||
this.outboxId = outboxId;
|
||||
}
|
||||
|
||||
public String getOnion() {
|
||||
return onion;
|
||||
}
|
||||
|
||||
public MailboxAuthToken getAuthToken() {
|
||||
return authToken;
|
||||
}
|
||||
|
||||
public MailboxFolderId getInboxId() {
|
||||
return inboxId;
|
||||
}
|
||||
|
||||
public MailboxFolderId getOutboxId() {
|
||||
return outboxId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface MailboxPropertyManager {
|
||||
|
||||
/**
|
||||
* The unique ID of the mailbox property client.
|
||||
*/
|
||||
ClientId CLIENT_ID =
|
||||
new ClientId("org.briarproject.bramble.mailbox.properties");
|
||||
|
||||
/**
|
||||
* The current major version of the mailbox property client.
|
||||
*/
|
||||
int MAJOR_VERSION = 0;
|
||||
|
||||
/**
|
||||
* The current minor version of the mailbox property client.
|
||||
*/
|
||||
int MINOR_VERSION = 0;
|
||||
|
||||
/**
|
||||
* The number of properties required for a (non-empty) update message.
|
||||
*/
|
||||
int PROP_COUNT = 4;
|
||||
|
||||
/**
|
||||
* The required properties of a non-empty update message.
|
||||
*/
|
||||
String PROP_KEY_ONION = "onion";
|
||||
String PROP_KEY_AUTHTOKEN = "authToken";
|
||||
String PROP_KEY_INBOXID = "inboxId";
|
||||
String PROP_KEY_OUTBOXID = "outboxId";
|
||||
|
||||
/**
|
||||
* Length of the Onion property.
|
||||
*/
|
||||
int PROP_ONION_LENGTH = 56;
|
||||
|
||||
/**
|
||||
* Message metadata key for the version number of a local or remote update,
|
||||
* as a BDF long.
|
||||
*/
|
||||
String MSG_KEY_VERSION = "version";
|
||||
|
||||
/**
|
||||
* Message metadata key for whether an update is local or remote, as a BDF
|
||||
* boolean.
|
||||
*/
|
||||
String MSG_KEY_LOCAL = "local";
|
||||
|
||||
@Nullable
|
||||
MailboxPropertiesUpdate getLocalProperties(Transaction txn, ContactId c)
|
||||
throws DbException;
|
||||
|
||||
@Nullable
|
||||
MailboxPropertiesUpdate getRemoteProperties(Transaction txn, ContactId c)
|
||||
throws DbException;
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -10,6 +12,13 @@ import javax.annotation.Nullable;
|
||||
@NotNullByDefault
|
||||
public interface MailboxSettingsManager {
|
||||
|
||||
/**
|
||||
* Registers a hook to be called when a mailbox has been paired or unpaired.
|
||||
* This method should be called before
|
||||
* {@link LifecycleManager#startServices(SecretKey)}.
|
||||
*/
|
||||
void registerMailboxHook(MailboxHook hook);
|
||||
|
||||
@Nullable
|
||||
MailboxProperties getOwnMailboxProperties(Transaction txn)
|
||||
throws DbException;
|
||||
@@ -30,4 +39,22 @@ public interface MailboxSettingsManager {
|
||||
|
||||
@Nullable
|
||||
String getPendingUpload(Transaction txn, ContactId id) throws DbException;
|
||||
|
||||
interface MailboxHook {
|
||||
/**
|
||||
* Called when Briar is paired with a mailbox
|
||||
*
|
||||
* @param txn A read-write transaction
|
||||
* @param ownOnion Our new mailbox's onion (56 base32 chars)
|
||||
*/
|
||||
void mailboxPaired(Transaction txn, String ownOnion)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Called when the mailbox is unpaired
|
||||
*
|
||||
* @param txn A read-write transaction
|
||||
*/
|
||||
void mailboxUnpaired(Transaction txn) throws DbException;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class MailboxVersion implements Comparable<MailboxVersion> {
|
||||
|
||||
private final int major;
|
||||
private final int minor;
|
||||
|
||||
public MailboxVersion(int major, int minor) {
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
}
|
||||
|
||||
public int getMajor() {
|
||||
return major;
|
||||
}
|
||||
|
||||
public int getMinor() {
|
||||
return minor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof MailboxVersion) {
|
||||
MailboxVersion v = (MailboxVersion) o;
|
||||
return major == v.major && minor == v.minor;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MailboxVersion v) {
|
||||
int c = major - v.major;
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
return minor - v.minor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that is broadcast by {@link MailboxSettingsManager} when
|
||||
* recording the connection status of own Mailbox.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class OwnMailboxConnectionStatusEvent extends Event {
|
||||
|
||||
private final MailboxStatus status;
|
||||
|
||||
public OwnMailboxConnectionStatusEvent(MailboxStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public MailboxStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when {@link MailboxPropertiesUpdate} are received
|
||||
* from a contact.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class RemoteMailboxPropertiesUpdateEvent extends Event {
|
||||
|
||||
private final ContactId contactId;
|
||||
@Nullable
|
||||
private final MailboxPropertiesUpdate mailboxPropertiesUpdate;
|
||||
|
||||
public RemoteMailboxPropertiesUpdateEvent(ContactId contactId,
|
||||
@Nullable MailboxPropertiesUpdate mailboxPropertiesUpdate) {
|
||||
this.contactId = contactId;
|
||||
this.mailboxPropertiesUpdate = mailboxPropertiesUpdate;
|
||||
}
|
||||
|
||||
public ContactId getContact() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MailboxPropertiesUpdate getMailboxPropertiesUpdate() {
|
||||
return mailboxPropertiesUpdate;
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,4 @@ public class GroupId extends UniqueId {
|
||||
public GroupId(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof GroupId && super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,4 @@ public class MessageId extends UniqueId {
|
||||
public MessageId(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof MessageId && super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class NetworkUtils {
|
||||
// Despite what the docs say, the return value can be null
|
||||
//noinspection ConstantConditions
|
||||
return ifaces == null ? emptyList() : list(ifaces);
|
||||
} catch (SocketException e) {
|
||||
} catch (SocketException | NullPointerException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
@@ -1,17 +1,46 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
|
||||
public abstract class BrambleTestCase {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(BrambleTestCase.class.getName());
|
||||
|
||||
@Nullable
|
||||
protected volatile Throwable exceptionInBackgroundThread = null;
|
||||
|
||||
public BrambleTestCase() {
|
||||
// Ensure exceptions thrown on worker threads cause tests to fail
|
||||
UncaughtExceptionHandler fail = (thread, throwable) -> {
|
||||
throwable.printStackTrace();
|
||||
fail();
|
||||
LOG.log(WARNING, "Caught unhandled exception", throwable);
|
||||
exceptionInBackgroundThread = throwable;
|
||||
};
|
||||
Thread.setDefaultUncaughtExceptionHandler(fail);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeBrambleTestCase() {
|
||||
exceptionInBackgroundThread = null;
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterBrambleTestCase() {
|
||||
Throwable thrown = exceptionInBackgroundThread;
|
||||
if (thrown != null) {
|
||||
LOG.log(WARNING,
|
||||
"Background thread has thrown an exception unexpectedly",
|
||||
thrown);
|
||||
throw new AssertionError(thrown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,16 @@ import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.crypto.SignaturePrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.SignaturePublicKey;
|
||||
import org.briarproject.bramble.api.db.CommitAction;
|
||||
import org.briarproject.bramble.api.db.EventAction;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.Identity;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
@@ -39,6 +45,8 @@ import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_PUBLIC_KEY_BYTES;
|
||||
@@ -271,4 +279,39 @@ public class TestUtils {
|
||||
return optionalTests != null &&
|
||||
asList(optionalTests.split(",")).contains(testClass.getName());
|
||||
}
|
||||
|
||||
public static boolean mailboxPropertiesUpdateEqual(
|
||||
@Nullable MailboxPropertiesUpdate a,
|
||||
@Nullable MailboxPropertiesUpdate b) {
|
||||
if (a == null || b == null) {
|
||||
return a == b;
|
||||
}
|
||||
return a.getOnion().equals(b.getOnion()) &&
|
||||
a.getAuthToken().equals(b.getAuthToken()) &&
|
||||
a.getInboxId().equals(b.getInboxId()) &&
|
||||
a.getOutboxId().equals(b.getOutboxId());
|
||||
}
|
||||
|
||||
public static boolean mailboxPropertiesEqual(
|
||||
@Nullable MailboxProperties a,
|
||||
@Nullable MailboxProperties b) {
|
||||
if (a == null || b == null) {
|
||||
return a == b;
|
||||
}
|
||||
return a.getOnion().equals(b.getOnion()) &&
|
||||
a.getAuthToken().equals(b.getAuthToken()) &&
|
||||
a.isOwner() == b.isOwner() &&
|
||||
a.getServerSupports().equals(b.getServerSupports());
|
||||
}
|
||||
|
||||
public static boolean hasEvent(Transaction txn,
|
||||
Class<? extends Event> eventClass) {
|
||||
for (CommitAction action : txn.getActions()) {
|
||||
if (action instanceof EventAction) {
|
||||
Event event = ((EventAction) action).getEvent();
|
||||
if (eventClass.isInstance(event)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class ThreadExceptionTest extends BrambleTestCase {
|
||||
|
||||
@Test(expected = AssertionError.class)
|
||||
public void testAssertionErrorMakesTestCaseFail() {
|
||||
// This is what BrambleTestCase does, too:
|
||||
fail();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceptionInThreadMakesTestCaseFail() {
|
||||
Thread t = new Thread(() -> {
|
||||
System.out.println("thread before exception");
|
||||
throw new RuntimeException("boom");
|
||||
});
|
||||
|
||||
t.start();
|
||||
try {
|
||||
t.join();
|
||||
System.out.println("joined thread");
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("interrupted while joining thread");
|
||||
fail();
|
||||
}
|
||||
|
||||
assertNotNull(exceptionInBackgroundThread);
|
||||
exceptionInBackgroundThread = null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
|
||||
'com.fasterxml.jackson.core:jackson-annotations:2.13.0:jackson-annotations-2.13.0.jar:81f9724d8843e8b08f8f6c0609e7a2b030d00c34861c4ac7e2099a7235047d6f',
|
||||
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
|
||||
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
|
||||
'com.google.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb',
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.briarproject.bramble.crypto.CryptoExecutorModule;
|
||||
import org.briarproject.bramble.db.DatabaseExecutorModule;
|
||||
import org.briarproject.bramble.identity.IdentityModule;
|
||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||
import org.briarproject.bramble.mailbox.MailboxModule;
|
||||
import org.briarproject.bramble.plugin.PluginModule;
|
||||
import org.briarproject.bramble.properties.PropertiesModule;
|
||||
import org.briarproject.bramble.rendezvous.RendezvousModule;
|
||||
@@ -28,6 +29,8 @@ public interface BrambleCoreEagerSingletons {
|
||||
|
||||
void inject(LifecycleModule.EagerSingletons init);
|
||||
|
||||
void inject(MailboxModule.EagerSingletons init);
|
||||
|
||||
void inject(PluginModule.EagerSingletons init);
|
||||
|
||||
void inject(PropertiesModule.EagerSingletons init);
|
||||
@@ -51,6 +54,7 @@ public interface BrambleCoreEagerSingletons {
|
||||
c.inject(new DatabaseExecutorModule.EagerSingletons());
|
||||
c.inject(new IdentityModule.EagerSingletons());
|
||||
c.inject(new LifecycleModule.EagerSingletons());
|
||||
c.inject(new MailboxModule.EagerSingletons());
|
||||
c.inject(new RendezvousModule.EagerSingletons());
|
||||
c.inject(new PluginModule.EagerSingletons());
|
||||
c.inject(new PropertiesModule.EagerSingletons());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.bramble.client;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
@@ -22,6 +23,9 @@ import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
@@ -29,6 +33,7 @@ import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.util.Base32;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -39,6 +44,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -46,6 +52,12 @@ import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KE
|
||||
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_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_COUNT;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_AUTHTOKEN;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_INBOXID;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_ONION;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_OUTBOXID;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_ONION_LENGTH;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
@@ -399,6 +411,35 @@ class ClientHelperImpl implements ClientHelper {
|
||||
return tpMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MailboxPropertiesUpdate parseAndValidateMailboxPropertiesUpdate(
|
||||
BdfDictionary properties) throws FormatException {
|
||||
if (properties.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// Accepting more props than we need, for forward compatibility
|
||||
if (properties.size() < PROP_COUNT) {
|
||||
throw new FormatException();
|
||||
}
|
||||
String onion = properties.getString(PROP_KEY_ONION);
|
||||
checkLength(onion, PROP_ONION_LENGTH);
|
||||
try {
|
||||
Base32.decode(onion, true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new FormatException();
|
||||
}
|
||||
byte[] authToken = properties.getRaw(PROP_KEY_AUTHTOKEN);
|
||||
checkLength(authToken, UniqueId.LENGTH);
|
||||
byte[] inboxId = properties.getRaw(PROP_KEY_INBOXID);
|
||||
checkLength(inboxId, UniqueId.LENGTH);
|
||||
byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID);
|
||||
checkLength(outboxId, UniqueId.LENGTH);
|
||||
return new MailboxPropertiesUpdate(onion,
|
||||
new MailboxAuthToken(authToken), new MailboxFolderId(inboxId),
|
||||
new MailboxFolderId(outboxId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
||||
throws DbException {
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.bouncycastle.crypto.CryptoException;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.digests.Blake2bDigest;
|
||||
import org.bouncycastle.crypto.digests.SHA3Digest;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
@@ -41,6 +42,7 @@ import javax.inject.Inject;
|
||||
|
||||
import static java.lang.System.arraycopy;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
|
||||
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
|
||||
@@ -54,7 +56,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
|
||||
class CryptoComponentImpl implements CryptoComponent {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(CryptoComponentImpl.class.getName());
|
||||
getLogger(CryptoComponentImpl.class.getName());
|
||||
|
||||
private static final int SIGNATURE_KEY_PAIR_BITS = 256;
|
||||
private static final int STORAGE_IV_BYTES = 24; // 196 bits
|
||||
@@ -128,6 +130,13 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UniqueId generateUniqueId() {
|
||||
byte[] b = new byte[UniqueId.LENGTH];
|
||||
secureRandom.nextBytes(b);
|
||||
return new UniqueId(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey generateSecretKey() {
|
||||
byte[] b = new byte[SecretKey.LENGTH];
|
||||
@@ -449,7 +458,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encodeOnionAddress(byte[] publicKey) {
|
||||
public String encodeOnion(byte[] publicKey) {
|
||||
Digest digest = new SHA3Digest(256);
|
||||
byte[] label = ".onion checksum".getBytes(Charset.forName("US-ASCII"));
|
||||
digest.update(label, 0, label.length);
|
||||
|
||||
@@ -758,9 +758,10 @@ interface Database<T> {
|
||||
void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Resets the transmission count, expiry time and ETA of all messages that
|
||||
* are eligible to be sent to the given contact. This includes messages that
|
||||
* have already been sent and are not yet due for retransmission.
|
||||
* Resets the transmission count, expiry time and max latency of all
|
||||
* messages that are eligible to be sent to the given contact. This includes
|
||||
* messages that have already been sent and are not yet due for
|
||||
* retransmission.
|
||||
*/
|
||||
void resetUnackedMessagesToSend(T txn, ContactId c) throws DbException;
|
||||
|
||||
@@ -848,15 +849,19 @@ interface Database<T> {
|
||||
void stopCleanupTimer(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Updates the transmission count, expiry time and estimated time of arrival
|
||||
* of the given message with respect to the given contact, using the latency
|
||||
* of the transport over which it was sent.
|
||||
* Updates the transmission count, expiry time and max latency of the given
|
||||
* message with respect to the given contact.
|
||||
*
|
||||
* @param maxLatency latency of the transport over which the message was
|
||||
* sent.
|
||||
*/
|
||||
void updateExpiryTimeAndEta(T txn, ContactId c, MessageId m,
|
||||
void updateRetransmissionData(T txn, ContactId c, MessageId m,
|
||||
long maxLatency) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given transport keys, deleting any keys they have replaced.
|
||||
*/
|
||||
void updateTransportKeys(T txn, TransportKeySet ks) throws DbException;
|
||||
|
||||
void printStats(T txn) throws DbException;
|
||||
}
|
||||
|
||||
@@ -437,7 +437,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
Message message = db.getMessage(txn, m);
|
||||
totalLength += message.getRawLength();
|
||||
messages.add(message);
|
||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||
}
|
||||
if (ids.isEmpty()) return null;
|
||||
db.lowerRequestedFlag(txn, c, ids);
|
||||
@@ -462,7 +462,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
totalLength += message.getRawLength();
|
||||
messages.add(message);
|
||||
sentIds.add(m);
|
||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||
}
|
||||
}
|
||||
if (messages.isEmpty()) return messages;
|
||||
@@ -483,7 +483,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
db.getMessagesToOffer(txn, c, maxMessages, maxLatency);
|
||||
if (ids.isEmpty()) return null;
|
||||
for (MessageId m : ids)
|
||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||
return new Offer(ids);
|
||||
}
|
||||
|
||||
@@ -518,7 +518,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
Message message = db.getMessage(txn, m);
|
||||
totalLength += message.getRawLength();
|
||||
messages.add(message);
|
||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||
}
|
||||
if (ids.isEmpty()) return null;
|
||||
db.lowerRequestedFlag(txn, c, ids);
|
||||
@@ -1256,6 +1256,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void printStats(Transaction transaction) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
db.printStats(txn);
|
||||
}
|
||||
|
||||
private class CommitActionVisitor implements Visitor {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,8 +2,6 @@ package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.DAYS;
|
||||
|
||||
interface DatabaseConstants {
|
||||
|
||||
/**
|
||||
@@ -25,19 +23,6 @@ interface DatabaseConstants {
|
||||
*/
|
||||
String SCHEMA_VERSION_KEY = "schemaVersion";
|
||||
|
||||
/**
|
||||
* The {@link Settings} key under which the time of the last database
|
||||
* compaction is stored.
|
||||
*/
|
||||
String LAST_COMPACTED_KEY = "lastCompacted";
|
||||
|
||||
/**
|
||||
* The maximum time between database compactions in milliseconds. When the
|
||||
* database is opened it will be compacted if more than this amount of time
|
||||
* has passed since the last compaction.
|
||||
*/
|
||||
long MAX_COMPACTION_INTERVAL_MS = DAYS.toMillis(30);
|
||||
|
||||
/**
|
||||
* The {@link Settings} key under which the flag is stored indicating
|
||||
* whether the database is marked as dirty.
|
||||
|
||||
@@ -13,8 +13,12 @@ import org.briarproject.bramble.util.StringUtils;
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -26,6 +30,7 @@ import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||
import static org.briarproject.bramble.util.IoUtils.isNonEmptyDirectory;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.LogUtils.logFileOrDir;
|
||||
|
||||
/**
|
||||
@@ -85,17 +90,89 @@ class H2Database extends JdbcDatabase {
|
||||
public void close() throws DbException {
|
||||
// H2 will close the database when the last connection closes
|
||||
Connection c = null;
|
||||
Statement s = null;
|
||||
try {
|
||||
c = createConnection();
|
||||
super.closeAllConnections();
|
||||
closeAllConnections();
|
||||
setDirty(c, false);
|
||||
s = c.createStatement();
|
||||
s.execute("SHUTDOWN COMPACT");
|
||||
s.close();
|
||||
c.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s, LOG, WARNING);
|
||||
tryToClose(c, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void printStats(Connection txn) throws DbException {
|
||||
List<String> names = printNames(txn);
|
||||
for (String table : names) {
|
||||
tryPrintStats(txn, table);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> printNames(Connection txn) throws DbException {
|
||||
List<String> names = new ArrayList<>();
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql =
|
||||
"SELECT table_catalog, table_schema, table_name FROM INFORMATION_SCHEMA.TABLES";
|
||||
ps = txn.prepareStatement(sql);
|
||||
rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
String catalog = rs.getString(1);
|
||||
String schema = rs.getString(2);
|
||||
String name = rs.getString(3);
|
||||
LOG.info(
|
||||
String.format("Table %s %s %s", catalog, schema, name));
|
||||
names.add(schema + "." + name);
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
private void tryPrintStats(Connection txn, String table) {
|
||||
try {
|
||||
printStats(txn, table);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void printStats(Connection txn, String table) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "CALL DISK_SPACE_USED(?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, table);
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) {
|
||||
rs.close();
|
||||
ps.close();
|
||||
}
|
||||
long size = rs.getLong(1);
|
||||
if (rs.next()) throw new DbStateException();
|
||||
rs.close();
|
||||
ps.close();
|
||||
LOG.info(String.format("Size of table %s: %d", table, size));
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Connection createConnection() throws DbException, SQLException {
|
||||
SecretKey key = this.key;
|
||||
|
||||
@@ -79,11 +79,11 @@ class HyperSqlDatabase extends JdbcDatabase {
|
||||
Connection c = null;
|
||||
Statement s = null;
|
||||
try {
|
||||
super.closeAllConnections();
|
||||
closeAllConnections();
|
||||
c = createConnection();
|
||||
setDirty(c, false);
|
||||
s = c.createStatement();
|
||||
s.executeQuery("SHUTDOWN");
|
||||
s.executeQuery("SHUTDOWN COMPACT");
|
||||
s.close();
|
||||
c.close();
|
||||
} catch (SQLException e) {
|
||||
@@ -93,6 +93,11 @@ class HyperSqlDatabase extends JdbcDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void printStats(Connection txn) throws DbException {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Connection createConnection() throws DbException, SQLException {
|
||||
SecretKey key = this.key;
|
||||
@@ -106,7 +111,7 @@ class HyperSqlDatabase extends JdbcDatabase {
|
||||
Connection c = null;
|
||||
Statement s = null;
|
||||
try {
|
||||
super.closeAllConnections();
|
||||
closeAllConnections();
|
||||
c = createConnection();
|
||||
s = c.createStatement();
|
||||
s.executeQuery("SHUTDOWN COMPACT");
|
||||
|
||||
@@ -85,8 +85,6 @@ import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.DIRTY_KEY;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
|
||||
import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
||||
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||
@@ -102,7 +100,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
|
||||
abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
// Package access for testing
|
||||
static final int CODE_SCHEMA_VERSION = 49;
|
||||
static final int CODE_SCHEMA_VERSION = 50;
|
||||
|
||||
// Time period offsets for incoming transport keys
|
||||
private static final int OFFSET_PREV = -1;
|
||||
@@ -252,7 +250,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " requested BOOLEAN NOT NULL,"
|
||||
+ " expiry BIGINT NOT NULL,"
|
||||
+ " txCount INT NOT NULL,"
|
||||
+ " eta BIGINT NOT NULL,"
|
||||
+ " maxLatency BIGINT," // Null if latency was reset
|
||||
+ " PRIMARY KEY (messageId, contactId),"
|
||||
+ " FOREIGN KEY (messageId)"
|
||||
+ " REFERENCES messages (messageId)"
|
||||
@@ -378,8 +376,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
throws DbException, SQLException;
|
||||
|
||||
// Used exclusively during open to compact the database after schema
|
||||
// migrations or after DatabaseConstants#MAX_COMPACTION_INTERVAL_MS has
|
||||
// elapsed
|
||||
// migrations or if the database was not shut down cleanly
|
||||
protected abstract void compactAndClose() throws DbException;
|
||||
|
||||
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
|
||||
@@ -405,7 +402,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
if (reopen) {
|
||||
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||
wasDirtyOnInitialisation = isDirty(s);
|
||||
compact = migrateSchema(txn, s, listener) || isCompactionDue(s);
|
||||
boolean migrated = migrateSchema(txn, s, listener);
|
||||
compact = wasDirtyOnInitialisation || migrated;
|
||||
} else {
|
||||
wasDirtyOnInitialisation = false;
|
||||
createTables(txn);
|
||||
@@ -435,14 +433,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
} finally {
|
||||
connectionsLock.unlock();
|
||||
}
|
||||
txn = startTransaction();
|
||||
try {
|
||||
storeLastCompacted(txn);
|
||||
commitTransaction(txn);
|
||||
} catch (DbException e) {
|
||||
abortTransaction(txn);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,18 +492,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
new Migration45_46(),
|
||||
new Migration46_47(dbTypes),
|
||||
new Migration47_48(),
|
||||
new Migration48_49()
|
||||
new Migration48_49(),
|
||||
new Migration49_50()
|
||||
);
|
||||
}
|
||||
|
||||
private boolean isCompactionDue(Settings s) {
|
||||
long lastCompacted = s.getLong(LAST_COMPACTED_KEY, 0);
|
||||
long elapsed = clock.currentTimeMillis() - lastCompacted;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(elapsed + " ms since last compaction");
|
||||
return elapsed > MAX_COMPACTION_INTERVAL_MS;
|
||||
}
|
||||
|
||||
private void storeSchemaVersion(Connection txn, int version)
|
||||
throws DbException {
|
||||
Settings s = new Settings();
|
||||
@@ -521,12 +504,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||
}
|
||||
|
||||
private void storeLastCompacted(Connection txn) throws DbException {
|
||||
Settings s = new Settings();
|
||||
s.putLong(LAST_COMPACTED_KEY, clock.currentTimeMillis());
|
||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||
}
|
||||
|
||||
private boolean isDirty(Settings s) {
|
||||
return s.getBoolean(DIRTY_KEY, false);
|
||||
}
|
||||
@@ -540,7 +517,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
private void initialiseSettings(Connection txn) throws DbException {
|
||||
Settings s = new Settings();
|
||||
s.putInt(SCHEMA_VERSION_KEY, CODE_SCHEMA_VERSION);
|
||||
s.putLong(LAST_COMPACTED_KEY, clock.currentTimeMillis());
|
||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||
}
|
||||
|
||||
@@ -920,9 +896,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
try {
|
||||
String sql = "INSERT INTO statuses (messageId, contactId, groupId,"
|
||||
+ " timestamp, length, state, groupShared, messageShared,"
|
||||
+ " deleted, ack, seen, requested, expiry, txCount, eta)"
|
||||
+ " deleted, ack, seen, requested, expiry, txCount,"
|
||||
+ " maxLatency)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, 0, 0,"
|
||||
+ " 0)";
|
||||
+ " NULL)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
@@ -1156,17 +1133,17 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
} else {
|
||||
long now = clock.currentTimeMillis();
|
||||
long eta = now + maxLatency;
|
||||
sql = "SELECT NULL FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
+ " AND (expiry <= ? OR eta > ?)";
|
||||
+ " 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, eta);
|
||||
ps.setLong(4, maxLatency);
|
||||
}
|
||||
rs = ps.executeQuery();
|
||||
boolean messagesToSend = rs.next();
|
||||
@@ -2194,7 +2171,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
public Collection<MessageId> getMessagesToOffer(Connection txn,
|
||||
ContactId c, int maxMessages, long maxLatency) throws DbException {
|
||||
long now = clock.currentTimeMillis();
|
||||
long eta = now + maxLatency;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -2203,13 +2179,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
+ " AND seen = FALSE AND requested = FALSE"
|
||||
+ " AND (expiry <= ? OR eta > ?)"
|
||||
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||
+ " OR ? < maxLatency)"
|
||||
+ " ORDER BY timestamp LIMIT ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, eta);
|
||||
ps.setLong(4, maxLatency);
|
||||
ps.setInt(5, maxMessages);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
@@ -2253,7 +2230,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
public Collection<MessageId> getMessagesToSend(Connection txn, ContactId c,
|
||||
int maxLength, long maxLatency) throws DbException {
|
||||
long now = clock.currentTimeMillis();
|
||||
long eta = now + maxLatency;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -2262,13 +2238,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
+ " AND seen = FALSE"
|
||||
+ " AND (expiry <= ? OR eta > ?)"
|
||||
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||
+ " OR ? < maxLatency)"
|
||||
+ " ORDER BY timestamp";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, eta);
|
||||
ps.setLong(4, maxLatency);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
int total = 0;
|
||||
@@ -2552,7 +2529,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
public Collection<MessageId> getRequestedMessagesToSend(Connection txn,
|
||||
ContactId c, int maxLength, long maxLatency) throws DbException {
|
||||
long now = clock.currentTimeMillis();
|
||||
long eta = now + maxLatency;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -2561,13 +2537,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
+ " AND seen = FALSE AND requested = TRUE"
|
||||
+ " AND (expiry <= ? OR eta > ?)"
|
||||
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||
+ " OR ? < maxLatency)"
|
||||
+ " ORDER BY timestamp";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, eta);
|
||||
ps.setLong(4, maxLatency);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
int total = 0;
|
||||
@@ -3298,7 +3275,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "UPDATE statuses SET expiry = 0, txCount = 0, eta = 0"
|
||||
String sql = "UPDATE statuses SET expiry = 0, txCount = 0,"
|
||||
+ " maxLatency = NULL"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE";
|
||||
@@ -3643,8 +3621,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateExpiryTimeAndEta(Connection txn, ContactId c, MessageId m,
|
||||
long maxLatency) throws DbException {
|
||||
public void updateRetransmissionData(Connection txn, ContactId c,
|
||||
MessageId m, long maxLatency) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -3660,13 +3638,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
rs.close();
|
||||
ps.close();
|
||||
sql = "UPDATE statuses"
|
||||
+ " SET expiry = ?, txCount = txCount + 1, eta = ?"
|
||||
+ " SET expiry = ?, txCount = txCount + 1, maxLatency = ?"
|
||||
+ " WHERE messageId = ? AND contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
long now = clock.currentTimeMillis();
|
||||
long eta = now + maxLatency;
|
||||
ps.setLong(1, calculateExpiry(now, maxLatency, txCount));
|
||||
ps.setLong(2, eta);
|
||||
ps.setLong(2, maxLatency);
|
||||
ps.setBytes(3, m.getBytes());
|
||||
ps.setInt(4, c.getInt());
|
||||
int affected = ps.executeUpdate();
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||
|
||||
class Migration49_50 implements Migration<Connection> {
|
||||
|
||||
private static final Logger LOG = getLogger(Migration49_50.class.getName());
|
||||
|
||||
@Override
|
||||
public int getStartVersion() {
|
||||
return 49;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndVersion() {
|
||||
return 50;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrate(Connection txn) throws DbException {
|
||||
Statement s = null;
|
||||
try {
|
||||
s = txn.createStatement();
|
||||
s.execute("ALTER TABLE statuses"
|
||||
+ " ALTER COLUMN eta"
|
||||
+ " RENAME TO maxLatency");
|
||||
s.execute("ALTER TABLE statuses"
|
||||
+ " ALTER COLUMN maxLatency"
|
||||
+ " SET NULL");
|
||||
s.execute("UPDATE statuses SET maxLatency = NULL");
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ interface MailboxApi {
|
||||
* @return the owner token
|
||||
* @throws ApiException for 401 response.
|
||||
*/
|
||||
MailboxAuthToken setup(MailboxProperties properties)
|
||||
MailboxProperties setup(MailboxProperties properties)
|
||||
throws IOException, ApiException;
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.mailbox.MailboxFileId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.File;
|
||||
@@ -56,7 +57,7 @@ class MailboxApiImpl implements MailboxApi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailboxAuthToken setup(MailboxProperties properties)
|
||||
public MailboxProperties setup(MailboxProperties properties)
|
||||
throws IOException, ApiException {
|
||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||
Request request = getRequestBuilder(properties.getAuthToken())
|
||||
@@ -75,8 +76,27 @@ class MailboxApiImpl implements MailboxApi {
|
||||
if (tokenNode == null) {
|
||||
throw new ApiException();
|
||||
}
|
||||
String ownerToken = tokenNode.textValue();
|
||||
return MailboxAuthToken.fromString(ownerToken);
|
||||
List<MailboxVersion> serverSupports = new ArrayList<>();
|
||||
ArrayNode serverSupportsNode = getArray(node, "serverSupports");
|
||||
for (JsonNode versionNode : serverSupportsNode) {
|
||||
if (!versionNode.isObject()) throw new ApiException();
|
||||
ObjectNode objectNode = (ObjectNode) versionNode;
|
||||
JsonNode majorNode = objectNode.get("major");
|
||||
JsonNode minorNode = objectNode.get("minor");
|
||||
if (majorNode == null || !majorNode.isNumber()) {
|
||||
throw new ApiException();
|
||||
}
|
||||
if (minorNode == null || !minorNode.isNumber()) {
|
||||
throw new ApiException();
|
||||
}
|
||||
int major = majorNode.asInt();
|
||||
int minor = minorNode.asInt();
|
||||
if (major < 0 || minor < 0) throw new ApiException();
|
||||
serverSupports.add(new MailboxVersion(major, minor));
|
||||
}
|
||||
return new MailboxProperties(properties.getBaseUrl(),
|
||||
MailboxAuthToken.fromString(tokenNode.textValue()),
|
||||
true, serverSupports);
|
||||
} catch (JacksonException | InvalidMailboxIdException e) {
|
||||
throw new ApiException();
|
||||
}
|
||||
|
||||
@@ -2,27 +2,42 @@ 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.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class MailboxManagerImpl implements MailboxManager {
|
||||
|
||||
private static final String TAG = MailboxManagerImpl.class.getName();
|
||||
private final static Logger LOG = getLogger(TAG);
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final MailboxApi api;
|
||||
private final TransactionManager db;
|
||||
private final MailboxSettingsManager mailboxSettingsManager;
|
||||
private final MailboxPairingTaskFactory pairingTaskFactory;
|
||||
private final Clock clock;
|
||||
private final Object lock = new Object();
|
||||
|
||||
@Nullable
|
||||
@@ -32,11 +47,17 @@ class MailboxManagerImpl implements MailboxManager {
|
||||
@Inject
|
||||
MailboxManagerImpl(
|
||||
@IoExecutor Executor ioExecutor,
|
||||
MailboxApi api,
|
||||
TransactionManager db,
|
||||
MailboxSettingsManager mailboxSettingsManager,
|
||||
MailboxPairingTaskFactory pairingTaskFactory) {
|
||||
MailboxPairingTaskFactory pairingTaskFactory,
|
||||
Clock clock) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.api = api;
|
||||
this.db = db;
|
||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||
this.pairingTaskFactory = pairingTaskFactory;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,4 +96,29 @@ class MailboxManagerImpl implements MailboxManager {
|
||||
return created;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkConnection() {
|
||||
boolean success;
|
||||
try {
|
||||
MailboxProperties props = db.transactionWithNullableResult(true,
|
||||
mailboxSettingsManager::getOwnMailboxProperties);
|
||||
success = api.checkStatus(props);
|
||||
} catch (DbException | IOException | MailboxApi.ApiException e) {
|
||||
success = false;
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
if (success) {
|
||||
try {
|
||||
// we are only recording successful connections here
|
||||
// as those update the UI and failures might be false negatives
|
||||
db.transaction(false, txn ->
|
||||
mailboxSettingsManager.recordSuccessfulConnection(txn,
|
||||
clock.currentTimeMillis()));
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +1,36 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.CLIENT_ID;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MAJOR_VERSION;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MINOR_VERSION;
|
||||
|
||||
@Module
|
||||
public class MailboxModule {
|
||||
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
MailboxPropertyValidator mailboxPropertyValidator;
|
||||
@Inject
|
||||
MailboxPropertyManager mailboxPropertyManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
MailboxManager providesMailboxManager(MailboxManagerImpl mailboxManager) {
|
||||
@@ -33,4 +53,34 @@ public class MailboxModule {
|
||||
MailboxApi providesMailboxApi(MailboxApiImpl mailboxApi) {
|
||||
return mailboxApi;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
MailboxPropertyValidator provideMailboxPropertyValidator(
|
||||
ValidationManager validationManager, ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
MailboxPropertyValidator validator = new MailboxPropertyValidator(
|
||||
clientHelper, metadataEncoder, clock);
|
||||
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
|
||||
validator);
|
||||
return validator;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
MailboxPropertyManager provideMailboxPropertyManager(
|
||||
LifecycleManager lifecycleManager,
|
||||
ValidationManager validationManager, ContactManager contactManager,
|
||||
ClientVersioningManager clientVersioningManager,
|
||||
MailboxSettingsManager mailboxSettingsManager,
|
||||
MailboxPropertyManagerImpl mailboxPropertyManager) {
|
||||
lifecycleManager.registerOpenDatabaseHook(mailboxPropertyManager);
|
||||
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
|
||||
mailboxPropertyManager);
|
||||
contactManager.registerContactHook(mailboxPropertyManager);
|
||||
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
||||
MINOR_VERSION, mailboxPropertyManager);
|
||||
mailboxSettingsManager.registerMailboxHook(mailboxPropertyManager);
|
||||
return mailboxPropertyManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
@@ -18,31 +19,35 @@ import javax.inject.Inject;
|
||||
class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
|
||||
|
||||
private final Executor eventExecutor;
|
||||
private final TransactionManager db;
|
||||
private final DatabaseComponent db;
|
||||
private final CryptoComponent crypto;
|
||||
private final Clock clock;
|
||||
private final MailboxApi api;
|
||||
private final MailboxSettingsManager mailboxSettingsManager;
|
||||
private final MailboxPropertyManager mailboxPropertyManager;
|
||||
|
||||
@Inject
|
||||
MailboxPairingTaskFactoryImpl(
|
||||
@EventExecutor Executor eventExecutor,
|
||||
TransactionManager db,
|
||||
DatabaseComponent db,
|
||||
CryptoComponent crypto,
|
||||
Clock clock,
|
||||
MailboxApi api,
|
||||
MailboxSettingsManager mailboxSettingsManager) {
|
||||
MailboxSettingsManager mailboxSettingsManager,
|
||||
MailboxPropertyManager mailboxPropertyManager) {
|
||||
this.eventExecutor = eventExecutor;
|
||||
this.db = db;
|
||||
this.crypto = crypto;
|
||||
this.clock = clock;
|
||||
this.api = api;
|
||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||
this.mailboxPropertyManager = mailboxPropertyManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailboxPairingTask createPairingTask(String qrCodePayload) {
|
||||
return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db,
|
||||
crypto, clock, api, mailboxSettingsManager);
|
||||
crypto, clock, api, mailboxSettingsManager,
|
||||
mailboxPropertyManager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,17 @@ package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.Consumer;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
@@ -43,11 +46,12 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
||||
|
||||
private final String payload;
|
||||
private final Executor eventExecutor;
|
||||
private final TransactionManager db;
|
||||
private final DatabaseComponent db;
|
||||
private final CryptoComponent crypto;
|
||||
private final Clock clock;
|
||||
private final MailboxApi api;
|
||||
private final MailboxSettingsManager mailboxSettingsManager;
|
||||
private final MailboxPropertyManager mailboxPropertyManager;
|
||||
|
||||
private final Object lock = new Object();
|
||||
@GuardedBy("lock")
|
||||
@@ -59,11 +63,12 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
||||
MailboxPairingTaskImpl(
|
||||
String payload,
|
||||
@EventExecutor Executor eventExecutor,
|
||||
TransactionManager db,
|
||||
DatabaseComponent db,
|
||||
CryptoComponent crypto,
|
||||
Clock clock,
|
||||
MailboxApi api,
|
||||
MailboxSettingsManager mailboxSettingsManager) {
|
||||
MailboxSettingsManager mailboxSettingsManager,
|
||||
MailboxPropertyManager mailboxPropertyManager) {
|
||||
this.payload = payload;
|
||||
this.eventExecutor = eventExecutor;
|
||||
this.db = db;
|
||||
@@ -71,7 +76,8 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
||||
this.clock = clock;
|
||||
this.api = api;
|
||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||
state = new MailboxPairingState.QrCodeReceived(payload);
|
||||
this.mailboxPropertyManager = mailboxPropertyManager;
|
||||
state = new MailboxPairingState.QrCodeReceived();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -100,23 +106,31 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
||||
} catch (MailboxAlreadyPairedException e) {
|
||||
onMailboxError(e, new MailboxPairingState.MailboxAlreadyPaired());
|
||||
} catch (IOException e) {
|
||||
onMailboxError(e, new MailboxPairingState.ConnectionError(payload));
|
||||
onMailboxError(e, new MailboxPairingState.ConnectionError());
|
||||
} catch (ApiException | DbException e) {
|
||||
onMailboxError(e, new MailboxPairingState.UnexpectedError(payload));
|
||||
onMailboxError(e, new MailboxPairingState.UnexpectedError());
|
||||
}
|
||||
}
|
||||
|
||||
private void pairMailbox() throws IOException, ApiException, DbException {
|
||||
MailboxProperties mailboxProperties = decodeQrCodePayload(payload);
|
||||
setState(new MailboxPairingState.Pairing(payload));
|
||||
MailboxAuthToken ownerToken = api.setup(mailboxProperties);
|
||||
MailboxProperties ownerProperties = new MailboxProperties(
|
||||
mailboxProperties.getBaseUrl(), ownerToken, true);
|
||||
setState(new MailboxPairingState.Pairing());
|
||||
MailboxProperties ownerProperties = api.setup(mailboxProperties);
|
||||
long time = clock.currentTimeMillis();
|
||||
db.transaction(false, txn -> {
|
||||
mailboxSettingsManager
|
||||
.setOwnMailboxProperties(txn, ownerProperties);
|
||||
mailboxSettingsManager.recordSuccessfulConnection(txn, time);
|
||||
// A (possibly new) mailbox is paired. Reset message retransmission
|
||||
// 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.
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
MailboxPropertiesUpdate remoteProps = mailboxPropertyManager
|
||||
.getRemoteProperties(txn, c.getId());
|
||||
if (remoteProps == null) {
|
||||
db.resetUnackedMessagesToSend(txn, c.getId());
|
||||
}
|
||||
}
|
||||
});
|
||||
setState(new MailboxPairingState.Paired());
|
||||
}
|
||||
@@ -162,11 +176,12 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
||||
}
|
||||
LOG.info("QR code is valid");
|
||||
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
|
||||
String onionAddress = crypto.encodeOnionAddress(onionPubKey);
|
||||
String baseUrl = "http://" + onionAddress + ".onion";
|
||||
String onion = crypto.encodeOnion(onionPubKey);
|
||||
String baseUrl = "http://" + onion + ".onion";
|
||||
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
|
||||
MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes);
|
||||
return new MailboxProperties(baseUrl, setupToken, true);
|
||||
return new MailboxProperties(baseUrl, setupToken, true,
|
||||
new ArrayList<>());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataParser;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager.MailboxHook;
|
||||
import org.briarproject.bramble.api.mailbox.RemoteMailboxPropertiesUpdateEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.InvalidMessageException;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||
|
||||
@NotNullByDefault
|
||||
class MailboxPropertyManagerImpl implements MailboxPropertyManager,
|
||||
OpenDatabaseHook, ContactHook, ClientVersioningHook,
|
||||
IncomingMessageHook, MailboxHook {
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
private final ClientVersioningManager clientVersioningManager;
|
||||
private final MetadataParser metadataParser;
|
||||
private final ContactGroupFactory contactGroupFactory;
|
||||
private final Clock clock;
|
||||
private final MailboxSettingsManager mailboxSettingsManager;
|
||||
private final CryptoComponent crypto;
|
||||
private final Group localGroup;
|
||||
|
||||
@Inject
|
||||
MailboxPropertyManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||
ClientVersioningManager clientVersioningManager,
|
||||
MetadataParser metadataParser,
|
||||
ContactGroupFactory contactGroupFactory, Clock clock,
|
||||
MailboxSettingsManager mailboxSettingsManager,
|
||||
CryptoComponent crypto) {
|
||||
this.db = db;
|
||||
this.clientHelper = clientHelper;
|
||||
this.clientVersioningManager = clientVersioningManager;
|
||||
this.metadataParser = metadataParser;
|
||||
this.contactGroupFactory = contactGroupFactory;
|
||||
this.clock = clock;
|
||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||
this.crypto = crypto;
|
||||
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
||||
MAJOR_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDatabaseOpened(Transaction txn) throws DbException {
|
||||
if (db.containsGroup(txn, localGroup.getId())) {
|
||||
return;
|
||||
}
|
||||
db.addGroup(txn, localGroup);
|
||||
// Set things up for any pre-existing contacts
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
addingContact(txn, c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addingContact(Transaction txn, Contact c) throws DbException {
|
||||
// Create a group to share with the contact
|
||||
Group g = getContactGroup(c);
|
||||
db.addGroup(txn, g);
|
||||
// Apply the client's visibility to the contact group
|
||||
Visibility client = clientVersioningManager
|
||||
.getClientVisibility(txn, c.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
||||
// Attach the contact ID to the group
|
||||
clientHelper.setContactId(txn, g.getId(), c.getId());
|
||||
// If we are paired, create and send props to the newly added contact
|
||||
MailboxProperties ownProps =
|
||||
mailboxSettingsManager.getOwnMailboxProperties(txn);
|
||||
if (ownProps != null) {
|
||||
createAndSendProperties(txn, c, ownProps.getOnion());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removingContact(Transaction txn, Contact c) throws DbException {
|
||||
db.removeGroup(txn, getContactGroup(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mailboxPaired(Transaction txn, String ownOnion)
|
||||
throws DbException {
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
createAndSendProperties(txn, c, ownOnion);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mailboxUnpaired(Transaction txn) throws DbException {
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
sendEmptyProperties(txn, c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClientVisibilityChanging(Transaction txn, Contact c,
|
||||
Visibility v) throws DbException {
|
||||
// Apply the client's visibility to the contact group
|
||||
Group g = getContactGroup(c);
|
||||
db.setGroupVisibility(txn, c.getId(), g.getId(), v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
Metadata meta) throws DbException, InvalidMessageException {
|
||||
try {
|
||||
BdfDictionary d = metadataParser.parse(meta);
|
||||
// Get latest non-local update in the same group (from same contact)
|
||||
LatestUpdate latest = findLatest(txn, m.getGroupId(), false);
|
||||
if (latest != null) {
|
||||
if (d.getLong(MSG_KEY_VERSION) > latest.version) {
|
||||
db.deleteMessage(txn, latest.messageId);
|
||||
db.deleteMessageMetadata(txn, latest.messageId);
|
||||
} else {
|
||||
// Delete this update, we already have a newer one
|
||||
db.deleteMessage(txn, m.getId());
|
||||
db.deleteMessageMetadata(txn, m.getId());
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
}
|
||||
ContactId c = clientHelper.getContactId(txn, m.getGroupId());
|
||||
BdfList body = clientHelper.getMessageAsList(txn, m.getId());
|
||||
MailboxPropertiesUpdate p = parseProperties(body);
|
||||
txn.attach(new RemoteMailboxPropertiesUpdateEvent(c, p));
|
||||
// Reset message retransmission timers for the contact. Avoiding
|
||||
// messages getting stranded:
|
||||
// - on our mailbox, if they now have a mailbox but didn't before
|
||||
// - on the contact's old mailbox, if they removed their mailbox
|
||||
// - on the contact's old mailbox, if they replaced their mailbox
|
||||
db.resetUnackedMessagesToSend(txn, c);
|
||||
} catch (FormatException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MailboxPropertiesUpdate getLocalProperties(Transaction txn,
|
||||
ContactId c) throws DbException {
|
||||
return getProperties(txn, db.getContact(txn, c), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MailboxPropertiesUpdate getRemoteProperties(Transaction txn,
|
||||
ContactId c) throws DbException {
|
||||
return getProperties(txn, db.getContact(txn, c), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and sends an update message to the given contact. The message
|
||||
* holds our own mailbox's onion, and generated unique properties. All of
|
||||
* which the contact needs to communicate with our Mailbox.
|
||||
*/
|
||||
private void createAndSendProperties(Transaction txn,
|
||||
Contact c, String ownOnion) throws DbException {
|
||||
MailboxPropertiesUpdate p = new MailboxPropertiesUpdate(ownOnion,
|
||||
new MailboxAuthToken(crypto.generateUniqueId().getBytes()),
|
||||
new MailboxFolderId(crypto.generateUniqueId().getBytes()),
|
||||
new MailboxFolderId(crypto.generateUniqueId().getBytes()));
|
||||
Group g = getContactGroup(c);
|
||||
storeMessageReplaceLatest(txn, g.getId(), p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an empty update message to the given contact. The empty update
|
||||
* indicates for the receiving contact that we no longer have a Mailbox that
|
||||
* they can use.
|
||||
*/
|
||||
private void sendEmptyProperties(Transaction txn, Contact c)
|
||||
throws DbException {
|
||||
Group g = getContactGroup(c);
|
||||
storeMessageReplaceLatest(txn, g.getId(), null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private MailboxPropertiesUpdate getProperties(Transaction txn,
|
||||
Contact c, boolean local) throws DbException {
|
||||
MailboxPropertiesUpdate p = null;
|
||||
Group g = getContactGroup(c);
|
||||
try {
|
||||
LatestUpdate latest = findLatest(txn, g.getId(), local);
|
||||
if (latest != null) {
|
||||
BdfList body =
|
||||
clientHelper.getMessageAsList(txn, latest.messageId);
|
||||
p = parseProperties(body);
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
private void storeMessageReplaceLatest(Transaction txn, GroupId g,
|
||||
@Nullable MailboxPropertiesUpdate p) throws DbException {
|
||||
try {
|
||||
LatestUpdate latest = findLatest(txn, g, true);
|
||||
long version = latest == null ? 1 : latest.version + 1;
|
||||
Message m = clientHelper.createMessage(g, clock.currentTimeMillis(),
|
||||
encodeProperties(version, p));
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(MSG_KEY_VERSION, version);
|
||||
meta.put(MSG_KEY_LOCAL, true);
|
||||
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||
if (latest != null) {
|
||||
db.removeMessage(txn, latest.messageId);
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local)
|
||||
throws DbException, FormatException {
|
||||
Map<MessageId, BdfDictionary> metadata =
|
||||
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||
// We should have at most 1 local and 1 remote
|
||||
if (metadata.size() > 2) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
|
||||
BdfDictionary meta = e.getValue();
|
||||
if (meta.getBoolean(MSG_KEY_LOCAL) == local) {
|
||||
return new LatestUpdate(e.getKey(),
|
||||
meta.getLong(MSG_KEY_VERSION));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private BdfList encodeProperties(long version,
|
||||
@Nullable MailboxPropertiesUpdate p) {
|
||||
BdfDictionary dict = new BdfDictionary();
|
||||
if (p != null) {
|
||||
dict.put(PROP_KEY_ONION, p.getOnion());
|
||||
dict.put(PROP_KEY_AUTHTOKEN, p.getAuthToken().getBytes());
|
||||
dict.put(PROP_KEY_INBOXID, p.getInboxId().getBytes());
|
||||
dict.put(PROP_KEY_OUTBOXID, p.getOutboxId().getBytes());
|
||||
}
|
||||
return BdfList.of(version, dict);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private MailboxPropertiesUpdate parseProperties(BdfList body)
|
||||
throws FormatException {
|
||||
BdfDictionary dict = body.getDictionary(1);
|
||||
return clientHelper.parseAndValidateMailboxPropertiesUpdate(dict);
|
||||
}
|
||||
|
||||
private Group getContactGroup(Contact c) {
|
||||
return contactGroupFactory.createContactGroup(CLIENT_ID, MAJOR_VERSION,
|
||||
c);
|
||||
}
|
||||
|
||||
private static class LatestUpdate {
|
||||
|
||||
private final MessageId messageId;
|
||||
private final long version;
|
||||
|
||||
private LatestUpdate(MessageId messageId, long version) {
|
||||
this.messageId = messageId;
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.BdfMessageContext;
|
||||
import org.briarproject.bramble.api.client.BdfMessageValidator;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.InvalidMessageException;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_VERSION;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class MailboxPropertyValidator extends BdfMessageValidator {
|
||||
|
||||
MailboxPropertyValidator(ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
super(clientHelper, metadataEncoder, clock);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BdfMessageContext validateMessage(Message m, Group g,
|
||||
BdfList body) throws InvalidMessageException, FormatException {
|
||||
// Version, properties
|
||||
checkSize(body, 2);
|
||||
// Version
|
||||
long version = body.getLong(0);
|
||||
if (version < 0) throw new FormatException();
|
||||
// Properties
|
||||
BdfDictionary dictionary = body.getDictionary(1);
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(dictionary);
|
||||
// Return the metadata
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(MSG_KEY_VERSION, version);
|
||||
meta.put(MSG_KEY_LOCAL, false);
|
||||
return new BdfMessageContext(meta);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,10 +8,16 @@ import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||
import org.briarproject.bramble.api.mailbox.OwnMailboxConnectionStatusEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
@@ -26,18 +32,25 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
static final String SETTINGS_NAMESPACE = "mailbox";
|
||||
static final String SETTINGS_KEY_ONION = "onion";
|
||||
static final String SETTINGS_KEY_TOKEN = "token";
|
||||
static final String SETTINGS_KEY_SERVER_SUPPORTS = "serverSupports";
|
||||
static final String SETTINGS_KEY_LAST_ATTEMPT = "lastAttempt";
|
||||
static final String SETTINGS_KEY_LAST_SUCCESS = "lastSuccess";
|
||||
static final String SETTINGS_KEY_ATTEMPTS = "attempts";
|
||||
static final String SETTINGS_UPLOADS_NAMESPACE = "mailbox-uploads";
|
||||
|
||||
private final SettingsManager settingsManager;
|
||||
private final List<MailboxHook> hooks = new CopyOnWriteArrayList<>();
|
||||
|
||||
@Inject
|
||||
MailboxSettingsManagerImpl(SettingsManager settingsManager) {
|
||||
this.settingsManager = settingsManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerMailboxHook(MailboxHook hook) {
|
||||
hooks.add(hook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailboxProperties getOwnMailboxProperties(Transaction txn)
|
||||
throws DbException {
|
||||
@@ -45,9 +58,19 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
String onion = s.get(SETTINGS_KEY_ONION);
|
||||
String token = s.get(SETTINGS_KEY_TOKEN);
|
||||
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return null;
|
||||
int[] ints = s.getIntArray(SETTINGS_KEY_SERVER_SUPPORTS);
|
||||
// We know we were paired, so we must have proper serverSupports
|
||||
// TODO is throwing sensible? But it's done like that below on "parse error"
|
||||
if (ints == null || ints.length == 0 || ints.length % 2 != 0) {
|
||||
throw new DbException();
|
||||
}
|
||||
List<MailboxVersion> serverSupports = new ArrayList<>();
|
||||
for (int i = 0; i < ints.length - 1; i += 2) {
|
||||
serverSupports.add(new MailboxVersion(ints[i], ints[i + 1]));
|
||||
}
|
||||
try {
|
||||
MailboxAuthToken tokenId = MailboxAuthToken.fromString(token);
|
||||
return new MailboxProperties(onion, tokenId, true);
|
||||
return new MailboxProperties(onion, tokenId, true, serverSupports);
|
||||
} catch (InvalidMailboxIdException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
@@ -59,7 +82,18 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
Settings s = new Settings();
|
||||
s.put(SETTINGS_KEY_ONION, p.getBaseUrl());
|
||||
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
|
||||
List<MailboxVersion> serverSupports = p.getServerSupports();
|
||||
int[] ints = new int[serverSupports.size() * 2];
|
||||
int i = 0;
|
||||
for (MailboxVersion v : serverSupports) {
|
||||
ints[i++] = v.getMajor();
|
||||
ints[i++] = v.getMinor();
|
||||
}
|
||||
s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints);
|
||||
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||
for (MailboxHook hook : hooks) {
|
||||
hook.mailboxPaired(txn, p.getOnion());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,6 +114,8 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
|
||||
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||
MailboxStatus status = new MailboxStatus(now, now, 0);
|
||||
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,11 +123,14 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
throws DbException {
|
||||
Settings oldSettings =
|
||||
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
|
||||
int attempts = oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||
int newAttempts = 1 + oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||
long lastSuccess = oldSettings.getLong(SETTINGS_KEY_LAST_SUCCESS, 0);
|
||||
Settings newSettings = new Settings();
|
||||
newSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
||||
newSettings.putInt(SETTINGS_KEY_ATTEMPTS, attempts + 1);
|
||||
newSettings.putInt(SETTINGS_KEY_ATTEMPTS, newAttempts);
|
||||
settingsManager.mergeSettings(txn, newSettings, SETTINGS_NAMESPACE);
|
||||
MailboxStatus status = new MailboxStatus(now, lastSuccess, newAttempts);
|
||||
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -11,6 +11,7 @@ public interface CircumventionProvider {
|
||||
enum BridgeType {
|
||||
DEFAULT_OBFS4,
|
||||
NON_DEFAULT_OBFS4,
|
||||
VANILLA,
|
||||
MEEK
|
||||
}
|
||||
|
||||
@@ -20,30 +21,30 @@ public interface CircumventionProvider {
|
||||
* See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
|
||||
* and https://trac.torproject.org/projects/tor/wiki/doc/OONI/censorshipwiki
|
||||
*/
|
||||
String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE", "RU"};
|
||||
String[] BLOCKED = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
|
||||
|
||||
/**
|
||||
* Countries where obfs4 or meek 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
|
||||
* {@link #DEFAULT_OBFS4_BRIDGES}, {@link #NON_DEFAULT_OBFS4_BRIDGES} and
|
||||
* {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and
|
||||
* {@link #MEEK_BRIDGES}.
|
||||
*/
|
||||
String[] BRIDGES = {"CN", "IR", "EG", "BY", "TR", "SY", "VE", "RU"};
|
||||
String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
|
||||
|
||||
/**
|
||||
* Countries where default obfs4 bridges are likely to work.
|
||||
* Countries where default obfs4 or vanilla bridges are likely to work.
|
||||
* Should be a subset of {@link #BRIDGES}.
|
||||
*/
|
||||
String[] DEFAULT_OBFS4_BRIDGES = {"EG", "BY", "TR", "SY", "VE"};
|
||||
String[] DEFAULT_BRIDGES = {"EG", "VE"};
|
||||
|
||||
/**
|
||||
* Countries where non-default obfs4 bridges are likely to work.
|
||||
* Countries where non-default obfs4 or vanilla bridges are likely to work.
|
||||
* Should be a subset of {@link #BRIDGES}.
|
||||
*/
|
||||
String[] NON_DEFAULT_OBFS4_BRIDGES = {"RU"};
|
||||
String[] NON_DEFAULT_BRIDGES = {"BY", "RU", "TM"};
|
||||
|
||||
/**
|
||||
* Countries where obfs4 bridges won't work and meek is needed.
|
||||
* Countries where obfs4 and vanilla bridges won't work and meek is needed.
|
||||
* Should be a subset of {@link #BRIDGES}.
|
||||
*/
|
||||
String[] MEEK_BRIDGES = {"CN", "IR"};
|
||||
@@ -60,10 +61,11 @@ public interface CircumventionProvider {
|
||||
boolean doBridgesWork(String countryCode);
|
||||
|
||||
/**
|
||||
* Returns the best type of bridge connection for the given country, or
|
||||
* {@link #DEFAULT_OBFS4_BRIDGES} if no bridge type is known to work.
|
||||
* Returns the types of bridge connection that are suitable for the given
|
||||
* country, or {@link #DEFAULT_BRIDGES} if no bridge type is known
|
||||
* to work.
|
||||
*/
|
||||
BridgeType getBestBridgeType(String countryCode);
|
||||
List<BridgeType> getSuitableBridgeTypes(String countryCode);
|
||||
|
||||
@IoExecutor
|
||||
List<String> getBridges(BridgeType type);
|
||||
|
||||
@@ -14,10 +14,12 @@ import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -30,9 +32,9 @@ class CircumventionProviderImpl implements CircumventionProvider {
|
||||
private static final Set<String> BRIDGE_COUNTRIES =
|
||||
new HashSet<>(asList(BRIDGES));
|
||||
private static final Set<String> DEFAULT_OBFS4_BRIDGE_COUNTRIES =
|
||||
new HashSet<>(asList(DEFAULT_OBFS4_BRIDGES));
|
||||
private static final Set<String> NON_DEFAULT_OBFS4_BRIDGE_COUNTRIES =
|
||||
new HashSet<>(asList(NON_DEFAULT_OBFS4_BRIDGES));
|
||||
new HashSet<>(asList(DEFAULT_BRIDGES));
|
||||
private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES =
|
||||
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
|
||||
private static final Set<String> MEEK_COUNTRIES =
|
||||
new HashSet<>(asList(MEEK_BRIDGES));
|
||||
|
||||
@@ -51,15 +53,15 @@ class CircumventionProviderImpl implements CircumventionProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BridgeType getBestBridgeType(String countryCode) {
|
||||
public List<BridgeType> getSuitableBridgeTypes(String countryCode) {
|
||||
if (DEFAULT_OBFS4_BRIDGE_COUNTRIES.contains(countryCode)) {
|
||||
return DEFAULT_OBFS4;
|
||||
} else if (NON_DEFAULT_OBFS4_BRIDGE_COUNTRIES.contains(countryCode)) {
|
||||
return NON_DEFAULT_OBFS4;
|
||||
return asList(DEFAULT_OBFS4, VANILLA);
|
||||
} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
|
||||
return asList(NON_DEFAULT_OBFS4, VANILLA);
|
||||
} else if (MEEK_COUNTRIES.contains(countryCode)) {
|
||||
return MEEK;
|
||||
return singletonList(MEEK);
|
||||
} else {
|
||||
return DEFAULT_OBFS4;
|
||||
return asList(DEFAULT_OBFS4, VANILLA);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,12 +75,10 @@ class CircumventionProviderImpl implements CircumventionProvider {
|
||||
List<String> bridges = new ArrayList<>();
|
||||
while (scanner.hasNextLine()) {
|
||||
String line = scanner.nextLine();
|
||||
boolean isDefaultObfs4 = line.startsWith("d ");
|
||||
boolean isNonDefaultObfs4 = line.startsWith("n ");
|
||||
boolean isMeek = line.startsWith("m ");
|
||||
if ((type == DEFAULT_OBFS4 && isDefaultObfs4) ||
|
||||
(type == NON_DEFAULT_OBFS4 && isNonDefaultObfs4) ||
|
||||
(type == MEEK && isMeek)) {
|
||||
if ((type == DEFAULT_OBFS4 && line.startsWith("d ")) ||
|
||||
(type == NON_DEFAULT_OBFS4 && line.startsWith("n ")) ||
|
||||
(type == VANILLA && line.startsWith("v ")) ||
|
||||
(type == MEEK && line.startsWith("m "))) {
|
||||
bridges.add(line.substring(2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,6 @@ import static org.briarproject.bramble.api.plugin.TorConstants.ID;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_AUTOMATIC;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WITH_BRIDGES;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
|
||||
@@ -93,7 +92,9 @@ 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_COUNTRY_BLOCKED;
|
||||
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.NON_DEFAULT_OBFS4;
|
||||
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.tryToClose;
|
||||
@@ -108,7 +109,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private static final Logger LOG = getLogger(TorPlugin.class.getName());
|
||||
|
||||
private static final String[] EVENTS = {
|
||||
"CIRC", "ORCONN", "HS_DESC", "NOTICE", "WARN", "ERR"
|
||||
"CIRC",
|
||||
"ORCONN",
|
||||
"STATUS_GENERAL",
|
||||
"STATUS_CLIENT",
|
||||
"HS_DESC",
|
||||
"NOTICE",
|
||||
"WARN",
|
||||
"ERR"
|
||||
};
|
||||
private static final String OWNER = "__OwningControllerProcess";
|
||||
private static final int COOKIE_TIMEOUT_MS = 3000;
|
||||
@@ -228,7 +236,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
}
|
||||
// Load the settings
|
||||
settings = migrateSettings(callback.getSettings());
|
||||
settings = callback.getSettings();
|
||||
// Install or update the assets if necessary
|
||||
if (!assetsAreUpToDate()) installAssets();
|
||||
if (cookieFile.exists() && !cookieFile.delete())
|
||||
@@ -302,11 +310,17 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
controlConnection.setEventHandler(this);
|
||||
controlConnection.setEvents(asList(EVENTS));
|
||||
// Check whether Tor has already bootstrapped
|
||||
String phase = controlConnection.getInfo("status/bootstrap-phase");
|
||||
if (phase != null && phase.contains("PROGRESS=100")) {
|
||||
String info = controlConnection.getInfo("status/bootstrap-phase");
|
||||
if (info != null && info.contains("PROGRESS=100")) {
|
||||
LOG.info("Tor has already bootstrapped");
|
||||
state.setBootstrapped();
|
||||
}
|
||||
// Check whether Tor has already built a circuit
|
||||
info = controlConnection.getInfo("status/circuit-established");
|
||||
if ("1".equals(info)) {
|
||||
LOG.info("Tor has already built a circuit");
|
||||
state.getAndSetCircuitBuilt(true);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
@@ -318,18 +332,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
bind();
|
||||
}
|
||||
|
||||
// TODO: Remove after a reasonable migration period (added 2020-06-25)
|
||||
private Settings migrateSettings(Settings settings) {
|
||||
int network = settings.getInt(PREF_TOR_NETWORK,
|
||||
DEFAULT_PREF_TOR_NETWORK);
|
||||
if (network == PREF_TOR_NETWORK_NEVER) {
|
||||
settings.putInt(PREF_TOR_NETWORK, DEFAULT_PREF_TOR_NETWORK);
|
||||
settings.putBoolean(PREF_PLUGIN_ENABLE, false);
|
||||
callback.mergeSettings(settings);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
private boolean assetsAreUpToDate() {
|
||||
return doneFile.lastModified() > getLastUpdateTime();
|
||||
}
|
||||
@@ -339,9 +341,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
// The done file may already exist from a previous installation
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
doneFile.delete();
|
||||
// The GeoIP file may exist from a previous installation - we can
|
||||
// save some space by deleting it.
|
||||
// TODO: Remove after a reasonable migration period
|
||||
// (added 2022-03-29)
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
geoIpFile.delete();
|
||||
installTorExecutable();
|
||||
installObfs4Executable();
|
||||
extract(getGeoIpInputStream(), geoIpFile);
|
||||
extract(getConfigInputStream(), configFile);
|
||||
if (!doneFile.createNewFile())
|
||||
LOG.warning("Failed to create done file");
|
||||
@@ -379,14 +386,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
return zin;
|
||||
}
|
||||
|
||||
private InputStream getGeoIpInputStream() throws IOException {
|
||||
InputStream in = resourceProvider.getResourceInputStream("geoip",
|
||||
".zip");
|
||||
ZipInputStream zin = new ZipInputStream(in);
|
||||
if (zin.getNextEntry() == null) throw new IOException();
|
||||
return zin;
|
||||
}
|
||||
|
||||
private InputStream getObfs4InputStream() throws IOException {
|
||||
InputStream in = resourceProvider
|
||||
.getResourceInputStream("obfs4proxy_" + architecture, ".zip");
|
||||
@@ -544,20 +543,24 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
|
||||
}
|
||||
|
||||
private void enableBridges(boolean enable, BridgeType bridgeType)
|
||||
private void enableBridges(boolean enable, List<BridgeType> bridgeTypes)
|
||||
throws IOException {
|
||||
if (enable) {
|
||||
Collection<String> conf = new ArrayList<>();
|
||||
conf.add("UseBridges 1");
|
||||
File obfs4File = getObfs4ExecutableFile();
|
||||
if (bridgeType == MEEK) {
|
||||
if (bridgeTypes.contains(MEEK)) {
|
||||
conf.add("ClientTransportPlugin meek_lite exec " +
|
||||
obfs4File.getAbsolutePath());
|
||||
} else {
|
||||
}
|
||||
if (bridgeTypes.contains(DEFAULT_OBFS4) ||
|
||||
bridgeTypes.contains(NON_DEFAULT_OBFS4)) {
|
||||
conf.add("ClientTransportPlugin obfs4 exec " +
|
||||
obfs4File.getAbsolutePath());
|
||||
}
|
||||
conf.addAll(circumventionProvider.getBridges(bridgeType));
|
||||
for (BridgeType bridgeType : bridgeTypes) {
|
||||
conf.addAll(circumventionProvider.getBridges(bridgeType));
|
||||
}
|
||||
controlConnection.setConf(conf);
|
||||
} else {
|
||||
controlConnection.setConf("UseBridges", "0");
|
||||
@@ -646,7 +649,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Could not connect to v3 "
|
||||
+ scrubOnion(onion3) + ": " + e.toString());
|
||||
+ scrubOnion(onion3) + ": " + e);
|
||||
}
|
||||
tryToClose(s, LOG, WARNING);
|
||||
return null;
|
||||
@@ -682,8 +685,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
byte[] localSeed = alice ? aliceSeed : bobSeed;
|
||||
byte[] remoteSeed = alice ? bobSeed : aliceSeed;
|
||||
String blob = torRendezvousCrypto.getPrivateKeyBlob(localSeed);
|
||||
String localOnion = torRendezvousCrypto.getOnionAddress(localSeed);
|
||||
String remoteOnion = torRendezvousCrypto.getOnionAddress(remoteSeed);
|
||||
String localOnion = torRendezvousCrypto.getOnion(localSeed);
|
||||
String remoteOnion = torRendezvousCrypto.getOnion(remoteSeed);
|
||||
TransportProperties remoteProperties = new TransportProperties();
|
||||
remoteProperties.put(PROP_ONION_V3, remoteOnion);
|
||||
try {
|
||||
@@ -727,9 +730,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
@Override
|
||||
public void circuitStatus(String status, String id, String path) {
|
||||
if (status.equals("BUILT") &&
|
||||
state.getAndSetCircuitBuilt()) {
|
||||
LOG.info("First circuit built");
|
||||
// In case of races between receiving CIRCUIT_ESTABLISHED and setting
|
||||
// DisableNetwork, set our circuitBuilt flag if not already set
|
||||
if (status.equals("BUILT") && !state.getAndSetCircuitBuilt(true)) {
|
||||
LOG.info("Circuit built");
|
||||
backoff.reset();
|
||||
}
|
||||
}
|
||||
@@ -740,9 +744,16 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
@Override
|
||||
public void orConnStatus(String status, String orName) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("OR connection " + status + " " + orName);
|
||||
if (status.equals("CLOSED") || status.equals("FAILED")) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
|
||||
|
||||
//noinspection IfCanBeSwitch
|
||||
if (status.equals("LAUNCHED")) state.onOrConnectionLaunched();
|
||||
else if (status.equals("FAILED")) state.onOrConnectionFailed();
|
||||
else if (status.equals("CONNECTED")) state.onOrConnectionConnected();
|
||||
else if (status.equals("CLOSED")) state.onOrConnectionClosed();
|
||||
|
||||
if ((status.equals("FAILED") || status.equals("CLOSED")) &&
|
||||
state.getNumOrConnections() == 0) {
|
||||
// Check whether we've lost connectivity
|
||||
updateConnectionStatus(networkManager.getNetworkStatus(),
|
||||
batteryManager.isCharging());
|
||||
@@ -760,24 +771,81 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
@Override
|
||||
public void message(String severity, String msg) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
|
||||
if (severity.equals("NOTICE") && msg.startsWith("Bootstrapped 100%")) {
|
||||
state.setBootstrapped();
|
||||
backoff.reset();
|
||||
if (msg.startsWith("Switching to guard context")) {
|
||||
state.onSwitchingGuardContext();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unrecognized(String type, String msg) {
|
||||
if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
String[] words = msg.split(" ");
|
||||
if (words.length > 1 && ONION_V3.matcher(words[1]).matches()) {
|
||||
LOG.info("V3 descriptor uploaded");
|
||||
} else {
|
||||
LOG.info("V2 descriptor uploaded");
|
||||
if (type.equals("STATUS_CLIENT")) {
|
||||
handleClientStatus(removeSeverity(msg));
|
||||
} else if (type.equals("STATUS_GENERAL")) {
|
||||
handleGeneralStatus(removeSeverity(msg));
|
||||
} else if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) {
|
||||
String[] parts = msg.split(" ");
|
||||
if (parts.length < 2) {
|
||||
LOG.warning("Failed to parse HS_DESC UPLOADED event");
|
||||
} else if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("V3 descriptor uploaded for " + scrubOnion(parts[1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String removeSeverity(String msg) {
|
||||
return msg.replaceFirst("[^ ]+ ", "");
|
||||
}
|
||||
|
||||
private void handleClientStatus(String msg) {
|
||||
if (msg.startsWith("BOOTSTRAP PROGRESS=100")) {
|
||||
LOG.info("Bootstrapped");
|
||||
state.setBootstrapped();
|
||||
backoff.reset();
|
||||
} else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
|
||||
if (!state.getAndSetCircuitBuilt(true)) {
|
||||
LOG.info("Circuit built");
|
||||
backoff.reset();
|
||||
}
|
||||
} else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
|
||||
if (state.getAndSetCircuitBuilt(false)) {
|
||||
LOG.info("Circuit not built");
|
||||
// TODO: Disable and re-enable network to prompt Tor to rebuild
|
||||
// its guard/bridge connections? This will also close any
|
||||
// established circuits, which might still be functioning
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleGeneralStatus(String msg) {
|
||||
if (msg.startsWith("CLOCK_JUMPED")) {
|
||||
Long time = parseLongArgument(msg, "TIME");
|
||||
if (time != null && LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Clock jumped " + time + " seconds");
|
||||
}
|
||||
} else if (msg.startsWith("CLOCK_SKEW")) {
|
||||
Long skew = parseLongArgument(msg, "SKEW");
|
||||
if (skew != null && LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Clock is skewed by " + skew + " seconds");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Long parseLongArgument(String msg, String argName) {
|
||||
String[] args = msg.split(" ");
|
||||
for (String arg : args) {
|
||||
if (arg.startsWith(argName + "=")) {
|
||||
try {
|
||||
return Long.parseLong(arg.substring(argName.length() + 1));
|
||||
} catch (NumberFormatException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Failed to parse " + argName + " from '" + msg + "'");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -847,8 +915,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
int reasonsDisabled = 0;
|
||||
boolean enableNetwork = false, enableBridges = false;
|
||||
boolean enableConnectionPadding = false;
|
||||
BridgeType bridgeType =
|
||||
circumventionProvider.getBestBridgeType(country);
|
||||
List<BridgeType> bridgeTypes =
|
||||
circumventionProvider.getSuitableBridgeTypes(country);
|
||||
|
||||
if (!online) {
|
||||
LOG.info("Disabling network, device is offline");
|
||||
@@ -877,10 +945,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
enableNetwork = true;
|
||||
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
|
||||
(automatic && bridgesWork)) {
|
||||
if (ipv6Only) bridgeType = MEEK;
|
||||
if (ipv6Only) bridgeTypes = singletonList(MEEK);
|
||||
enableBridges = true;
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Using bridge type " + bridgeType);
|
||||
LOG.info("Using bridge types " + bridgeTypes);
|
||||
}
|
||||
} else {
|
||||
LOG.info("Not using bridges");
|
||||
@@ -898,7 +966,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
try {
|
||||
if (enableNetwork) {
|
||||
enableBridges(enableBridges, bridgeType);
|
||||
enableBridges(enableBridges, bridgeTypes);
|
||||
enableConnectionPadding(enableConnectionPadding);
|
||||
useIpv6(ipv6Only);
|
||||
}
|
||||
@@ -938,6 +1006,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
@Nullable
|
||||
private ServerSocket serverSocket = null;
|
||||
|
||||
@GuardedBy("this")
|
||||
private int orConnectionsPending = 0, orConnectionsConnected = 0;
|
||||
|
||||
private synchronized void setStarted() {
|
||||
started = true;
|
||||
callback.pluginStateChanged(getState());
|
||||
@@ -961,11 +1032,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
callback.pluginStateChanged(getState());
|
||||
}
|
||||
|
||||
private synchronized boolean getAndSetCircuitBuilt() {
|
||||
boolean firstCircuit = !circuitBuilt;
|
||||
circuitBuilt = true;
|
||||
callback.pluginStateChanged(getState());
|
||||
return firstCircuit;
|
||||
private synchronized boolean getAndSetCircuitBuilt(boolean built) {
|
||||
boolean old = circuitBuilt;
|
||||
circuitBuilt = built;
|
||||
if (built != old) callback.pluginStateChanged(getState());
|
||||
return old;
|
||||
}
|
||||
|
||||
private synchronized void enableNetwork(boolean enable) {
|
||||
@@ -1006,5 +1077,58 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private synchronized int getReasonsDisabled() {
|
||||
return getState() == DISABLED ? reasonsDisabled : 0;
|
||||
}
|
||||
|
||||
private synchronized void onOrConnectionLaunched() {
|
||||
orConnectionsPending++;
|
||||
logOrConnections();
|
||||
}
|
||||
|
||||
private synchronized void onOrConnectionFailed() {
|
||||
orConnectionsPending--;
|
||||
if (orConnectionsPending < 0) {
|
||||
LOG.warning("Count was zero before connection failed");
|
||||
orConnectionsPending = 0;
|
||||
}
|
||||
logOrConnections();
|
||||
}
|
||||
|
||||
private synchronized void onOrConnectionConnected() {
|
||||
orConnectionsPending--;
|
||||
if (orConnectionsPending < 0) {
|
||||
LOG.warning("Count was zero before connection connected");
|
||||
orConnectionsPending = 0;
|
||||
}
|
||||
orConnectionsConnected++;
|
||||
logOrConnections();
|
||||
}
|
||||
|
||||
private synchronized void onOrConnectionClosed() {
|
||||
orConnectionsConnected--;
|
||||
if (orConnectionsConnected < 0) {
|
||||
LOG.warning("Count was zero before connection closed");
|
||||
orConnectionsConnected = 0;
|
||||
}
|
||||
logOrConnections();
|
||||
}
|
||||
|
||||
private synchronized void onSwitchingGuardContext() {
|
||||
// Tor doesn't seem to report events for connections belonging to
|
||||
// the old guard context, so we have to reset the counters
|
||||
orConnectionsPending = 0;
|
||||
orConnectionsConnected = 0;
|
||||
logOrConnections();
|
||||
}
|
||||
|
||||
private synchronized int getNumOrConnections() {
|
||||
return orConnectionsPending + orConnectionsConnected;
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private void logOrConnections() {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("OR connections: " + orConnectionsPending
|
||||
+ " pending, " + orConnectionsConnected + " connected");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ interface TorRendezvousCrypto {
|
||||
|
||||
static final int SEED_BYTES = 32;
|
||||
|
||||
String getOnionAddress(byte[] seed);
|
||||
String getOnion(byte[] seed);
|
||||
|
||||
String getPrivateKeyBlob(byte[] seed);
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ class TorRendezvousCryptoImpl implements TorRendezvousCrypto {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOnionAddress(byte[] seed) {
|
||||
public String getOnion(byte[] seed) {
|
||||
EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC);
|
||||
return crypto.encodeOnionAddress(spec.getA().toByteArray());
|
||||
return crypto.encodeOnion(spec.getA().toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -7,14 +7,13 @@ import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static java.net.NetworkInterface.getNetworkInterfaces;
|
||||
import static java.util.Collections.list;
|
||||
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -25,14 +24,11 @@ abstract class AbstractSecureRandomProvider implements SecureRandomProvider {
|
||||
out.writeLong(System.currentTimeMillis());
|
||||
out.writeLong(System.nanoTime());
|
||||
out.writeLong(Runtime.getRuntime().freeMemory());
|
||||
Enumeration<NetworkInterface> ifaces = getNetworkInterfaces();
|
||||
if (ifaces != null) {
|
||||
for (NetworkInterface i : list(ifaces)) {
|
||||
for (InetAddress a : list(i.getInetAddresses()))
|
||||
out.write(a.getAddress());
|
||||
byte[] hardware = i.getHardwareAddress();
|
||||
if (hardware != null) out.write(hardware);
|
||||
}
|
||||
for (NetworkInterface i : getNetworkInterfaces()) {
|
||||
for (InetAddress a : list(i.getInetAddresses()))
|
||||
out.write(a.getAddress());
|
||||
byte[] hardware = i.getHardwareAddress();
|
||||
if (hardware != null) out.write(hardware);
|
||||
}
|
||||
for (Entry<String, String> e : System.getenv().entrySet()) {
|
||||
out.writeUTF(e.getKey());
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
d Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
|
||||
d Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
|
||||
d Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1OpbAH0wNqOT6H6BmRQ iat-mode=1
|
||||
d Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
|
||||
d Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
|
||||
d Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
|
||||
@@ -6,15 +8,19 @@ d Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cer
|
||||
d Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
|
||||
d Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
|
||||
d Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
|
||||
d Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
|
||||
d Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
|
||||
d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
|
||||
d Bridge obfs4 185.100.85.3:443 5B403DFE34F4872EB027059CECAE30B0C864B3A2 cert=bWUdFUe8io9U6JkSLoGAvSAUDcB779/shovCYmYAQb/pW/iEAMZtO/lCd94OokOF909TPA iat-mode=2
|
||||
n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cert=t8tA9q2AeGlmp/dO6oW9bkY5RqqmvqjArCEM9wjJoDnk6XtnaejkF0JTA7VamdyOzcvuBg iat-mode=0
|
||||
n Bridge obfs4 23.88.49.56:443 1CDA1660823AE2565D7F50DE8EB99DFDDE96074B cert=4bwNXedHutVD0ZqCm6ph90Vik9dRY4n9qnBHiLiqQOSsIvui4iHwuMFQK6oqiK8tyhVcDw 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 185.161.70.200:9003 7A81D0CD19870DFA3FD13C5DE232D8ADD026DC40 cert=aIcWxyZS3JcWsowlD9hcw9fttA44Bq/W2laFjVWlhuXqrIlAAwrXvq1O9lm9XrkV8GG/ZA iat-mode=0
|
||||
n Bridge obfs4 172.105.22.69:80 CBD17B33192A879433AB37C9E142541BD3459ABD cert=rk5YmpKypLsjlS4tjkYaZNBweYMa5tWQRhZ8Q2WRleNOgrhSceKo59BA8kp6kVfaMPXnSw iat-mode=0
|
||||
n Bridge obfs4 46.128.93.192:7346 5D28B8E1D117B8720D56A8513CF32509DCA1D84F cert=ED6tZP50eF0vno09F5gFvoWTMdcWFEX2FtwXOUYRevjzKg30/y701f61Vycnh6HO9gkaMw 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 192.3.163.88:57145 DEB62DE9643E5956CA4FA78035B48C9BBABE7F29 cert=RMz2z9uVVrioUhx0A8xUmiftRe2RpcXiqIuDfisdIomcHDf82nzfn83X/ixGUiA4rLCAdw 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
|
||||
v Bridge 5.45.96.40:9001 8723B591712AAA03FB92000370BD356AB4997FA7
|
||||
v Bridge 135.181.113.164:54444 74AF4CCA614C454B7D3E81FF8BACD78CEBC7D7DE
|
||||
v Bridge 152.44.197.85:10507 FF07DF6B4720DA4C50F1A025662D50916D6223F6
|
||||
v Bridge 209.216.78.21:443 C870D381E7264CDB83BAEEBF074804808CCCDB8D
|
||||
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.bramble.client;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
@@ -20,13 +21,15 @@ import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -41,15 +44,22 @@ import java.util.Random;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_AUTHTOKEN;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_INBOXID;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_ONION;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_OUTBOXID;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSignaturePrivateKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSignaturePublicKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.mailboxPropertiesUpdateEqual;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class ClientHelperImplTest extends BrambleMockTestCase {
|
||||
@@ -78,13 +88,35 @@ public class ClientHelperImplTest extends BrambleMockTestCase {
|
||||
private final long timestamp = message.getTimestamp();
|
||||
private final Metadata metadata = new Metadata();
|
||||
private final BdfList list = BdfList.of("Sign this!", getRandomBytes(42));
|
||||
private final String label = StringUtils.getRandomString(5);
|
||||
private final String label = getRandomString(5);
|
||||
private final Author author = getAuthor();
|
||||
|
||||
private final ClientHelper clientHelper = new ClientHelperImpl(db,
|
||||
messageFactory, bdfReaderFactory, bdfWriterFactory, metadataParser,
|
||||
metadataEncoder, cryptoComponent, authorFactory);
|
||||
|
||||
private final MailboxPropertiesUpdate validMailboxPropsUpdate;
|
||||
|
||||
public ClientHelperImplTest() {
|
||||
validMailboxPropsUpdate = new MailboxPropertiesUpdate(
|
||||
"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd",
|
||||
new MailboxAuthToken(getRandomId()),
|
||||
new MailboxFolderId(getRandomId()),
|
||||
new MailboxFolderId(getRandomId()));
|
||||
}
|
||||
|
||||
private BdfDictionary getValidMailboxPropsUpdateDict() {
|
||||
BdfDictionary dict = new BdfDictionary();
|
||||
dict.put(PROP_KEY_ONION, validMailboxPropsUpdate.getOnion());
|
||||
dict.put(PROP_KEY_AUTHTOKEN, validMailboxPropsUpdate.getAuthToken()
|
||||
.getBytes());
|
||||
dict.put(PROP_KEY_INBOXID, validMailboxPropsUpdate.getInboxId()
|
||||
.getBytes());
|
||||
dict.put(PROP_KEY_OUTBOXID, validMailboxPropsUpdate.getOutboxId()
|
||||
.getBytes());
|
||||
return dict;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddLocalMessage() throws Exception {
|
||||
boolean shared = new Random().nextBoolean();
|
||||
@@ -513,4 +545,95 @@ public class ClientHelperImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(eof));
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseEmptyMailboxPropsUpdate() throws Exception {
|
||||
BdfDictionary emptyPropsDict = new BdfDictionary();
|
||||
MailboxPropertiesUpdate parsedProps = clientHelper
|
||||
.parseAndValidateMailboxPropertiesUpdate(emptyPropsDict);
|
||||
assertNull(parsedProps);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseValidMailboxPropsUpdate() throws Exception {
|
||||
MailboxPropertiesUpdate parsedProps = clientHelper
|
||||
.parseAndValidateMailboxPropertiesUpdate(
|
||||
getValidMailboxPropsUpdateDict());
|
||||
assertTrue(mailboxPropertiesUpdateEqual(validMailboxPropsUpdate,
|
||||
parsedProps));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateOnionNotDecodable()
|
||||
throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
String badOnion = "!" + propsDict.getString(PROP_KEY_ONION)
|
||||
.substring(1);
|
||||
propsDict.put(PROP_KEY_ONION, badOnion);
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateOnionWrongLength()
|
||||
throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
String tooLongOnion = propsDict.getString(PROP_KEY_ONION) + "!";
|
||||
propsDict.put(PROP_KEY_ONION, tooLongOnion);
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateInboxIdWrongLength()
|
||||
throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
propsDict.put(PROP_KEY_INBOXID, getRandomBytes(UniqueId.LENGTH + 1));
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateOutboxIdWrongLength()
|
||||
throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
propsDict.put(PROP_KEY_OUTBOXID, getRandomBytes(UniqueId.LENGTH + 1));
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateAuthTokenWrongLength()
|
||||
throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
propsDict.put(PROP_KEY_AUTHTOKEN, getRandomBytes(UniqueId.LENGTH + 1));
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateMissingOnion() throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
propsDict.remove(PROP_KEY_ONION);
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateMissingAuthToken()
|
||||
throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
propsDict.remove(PROP_KEY_AUTHTOKEN);
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateMissingInboxId() throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
propsDict.remove(PROP_KEY_INBOXID);
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateMissingOutboxId()
|
||||
throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
propsDict.remove(PROP_KEY_OUTBOXID);
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -922,11 +922,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(ids));
|
||||
oneOf(database).getMessage(txn, messageId);
|
||||
will(returnValue(message));
|
||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId,
|
||||
oneOf(database).updateRetransmissionData(txn, contactId, messageId,
|
||||
maxLatency);
|
||||
oneOf(database).getMessage(txn, messageId1);
|
||||
will(returnValue(message1));
|
||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId1,
|
||||
oneOf(database).updateRetransmissionData(txn, contactId, messageId1,
|
||||
maxLatency);
|
||||
oneOf(database).lowerRequestedFlag(txn, contactId, ids);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
@@ -951,9 +951,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(true));
|
||||
oneOf(database).getMessagesToOffer(txn, contactId, 123, maxLatency);
|
||||
will(returnValue(ids));
|
||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId,
|
||||
oneOf(database).updateRetransmissionData(txn, contactId, messageId,
|
||||
maxLatency);
|
||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId1,
|
||||
oneOf(database).updateRetransmissionData(txn, contactId, messageId1,
|
||||
maxLatency);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
@@ -1005,12 +1005,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(ids));
|
||||
oneOf(database).getMessage(txn, messageId);
|
||||
will(returnValue(message));
|
||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId,
|
||||
maxLatency);
|
||||
oneOf(database).updateRetransmissionData(txn, contactId,
|
||||
messageId, maxLatency);
|
||||
oneOf(database).getMessage(txn, messageId1);
|
||||
will(returnValue(message1));
|
||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId1,
|
||||
maxLatency);
|
||||
oneOf(database).updateRetransmissionData(txn, contactId,
|
||||
messageId1, maxLatency);
|
||||
oneOf(database).lowerRequestedFlag(txn, contactId, ids);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class)));
|
||||
|
||||
@@ -72,9 +72,6 @@ import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERE
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS;
|
||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
|
||||
@@ -444,7 +441,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertOneMessageToSendEagerly(db, txn);
|
||||
|
||||
// Mark the message as sent
|
||||
db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY);
|
||||
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||
|
||||
// The message should no longer be sendable via lazy retransmission,
|
||||
// but it should still be sendable via eager retransmission
|
||||
@@ -1811,7 +1808,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertFalse(status.isSeen());
|
||||
|
||||
// Pretend the message was sent to the contact
|
||||
db.updateExpiryTimeAndEta(txn, contactId, messageId, Integer.MAX_VALUE);
|
||||
db.updateRetransmissionData(txn, contactId, messageId,
|
||||
Integer.MAX_VALUE);
|
||||
|
||||
// The message should be sent but not seen
|
||||
status = db.getMessageStatus(txn, contactId, messageId);
|
||||
@@ -2052,12 +2050,12 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Update the message's expiry time as though we sent it - now the
|
||||
// message should be sendable after one round-trip
|
||||
db.updateExpiryTimeAndEta(txn, contactId, messageId, 1000);
|
||||
db.updateRetransmissionData(txn, contactId, messageId, 1000);
|
||||
assertEquals(now + 2000, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Update the message's expiry time again - now it should be sendable
|
||||
// after two round-trips
|
||||
db.updateExpiryTimeAndEta(txn, contactId, messageId, 1000);
|
||||
db.updateRetransmissionData(txn, contactId, messageId, 1000);
|
||||
assertEquals(now + 4000, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Delete the message - there should be no messages to send
|
||||
@@ -2124,7 +2122,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Time: now
|
||||
// Mark the message as sent
|
||||
db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY);
|
||||
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||
|
||||
// The message should expire after 2 * MAX_LATENCY
|
||||
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
|
||||
@@ -2161,36 +2159,29 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||
|
||||
// Time: now
|
||||
// Retrieve the message from the database
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
|
||||
// Time: now
|
||||
// Mark the message as sent
|
||||
db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY);
|
||||
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||
|
||||
// The message should expire after 2 * MAX_LATENCY
|
||||
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Time: now
|
||||
// The message should not be sendable via the same transport
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
|
||||
// Time: now
|
||||
// The message should be sendable via a transport with a faster ETA
|
||||
// The message should be sendable via a transport with a lower latency
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE,
|
||||
MAX_LATENCY - 1);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
|
||||
// Time: now + 1
|
||||
// The message should no longer be sendable via the faster transport,
|
||||
// as the ETA is now equal
|
||||
time.set(now + 1);
|
||||
// The message should not be sendable via a slower transport
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE,
|
||||
MAX_LATENCY - 1);
|
||||
MAX_LATENCY + 1);
|
||||
assertTrue(ids.isEmpty());
|
||||
|
||||
db.commitTransaction(txn);
|
||||
@@ -2221,7 +2212,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Time: now
|
||||
// Mark the message as sent
|
||||
db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY);
|
||||
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||
|
||||
// The message should expire after 2 * MAX_LATENCY
|
||||
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
|
||||
@@ -2246,46 +2237,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompactionTime() throws Exception {
|
||||
MessageFactory messageFactory = new TestMessageFactory();
|
||||
long now = System.currentTimeMillis();
|
||||
AtomicLong time = new AtomicLong(now);
|
||||
Clock clock = new SettableClock(time);
|
||||
|
||||
// Time: now
|
||||
// The last compaction time should be initialised to the current time
|
||||
Database<Connection> db = open(false, messageFactory, clock);
|
||||
Connection txn = db.startTransaction();
|
||||
Settings s = db.getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||
assertEquals(now, s.getLong(LAST_COMPACTED_KEY, 0));
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
|
||||
// Time: now + MAX_COMPACTION_INTERVAL_MS
|
||||
// The DB should not be compacted, so the last compaction time should
|
||||
// not be updated
|
||||
time.set(now + MAX_COMPACTION_INTERVAL_MS);
|
||||
db = open(true, messageFactory, clock);
|
||||
txn = db.startTransaction();
|
||||
s = db.getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||
assertEquals(now, s.getLong(LAST_COMPACTED_KEY, 0));
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
|
||||
// Time: now + MAX_COMPACTION_INTERVAL_MS + 1
|
||||
// The DB should be compacted, so the last compaction time should be
|
||||
// updated
|
||||
time.set(now + MAX_COMPACTION_INTERVAL_MS + 1);
|
||||
db = open(true, messageFactory, clock);
|
||||
txn = db.startTransaction();
|
||||
s = db.getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||
assertEquals(now + MAX_COMPACTION_INTERVAL_MS + 1,
|
||||
s.getLong(LAST_COMPACTED_KEY, 0));
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPendingContacts() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
|
||||
@@ -37,6 +37,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.mailboxPropertiesEqual;
|
||||
import static org.briarproject.bramble.test.TestUtils.readBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.writeBytes;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
@@ -80,11 +81,20 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testSetup() throws Exception {
|
||||
String validResponse = "{\"token\":\"" + token2 + "\"}";
|
||||
String validVersions = "[ {\"major\":1,\"minor\":0} ]";
|
||||
String validResponse = makeSetupResponse(
|
||||
"\"" + token2 + "\"", validVersions);
|
||||
String invalidResponse = "{\"foo\":\"bar\"}";
|
||||
String invalidTokenResponse = "{\"token\":{\"foo\":\"bar\"}}";
|
||||
String invalidTokenResponse2 =
|
||||
"{\"token\":\"" + getRandomString(64) + "\"}";
|
||||
String invalidTokenResponse = makeSetupResponse(
|
||||
"{\"foo\":\"bar\"}", validVersions);
|
||||
String invalidTokenResponse2 = makeSetupResponse(
|
||||
"\"" + getRandomString(64) + "\"", validVersions);
|
||||
String invalidVersionsResponse = makeSetupResponse(
|
||||
"\"" + token2 + "\"", "42");
|
||||
String invalidVersionsResponse2 = makeSetupResponse(
|
||||
"\"" + token2 + "\"", "[ 1,0 ]");
|
||||
String invalidVersionsResponse3 = makeSetupResponse(
|
||||
"\"" + token2 + "\"", "[ {\"major\":1, \"minor\":-1} ]");
|
||||
|
||||
MockWebServer server = new MockWebServer();
|
||||
server.enqueue(new MockResponse().setBody(validResponse));
|
||||
@@ -94,15 +104,18 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
server.enqueue(new MockResponse().setResponseCode(500));
|
||||
server.enqueue(new MockResponse().setBody(invalidTokenResponse));
|
||||
server.enqueue(new MockResponse().setBody(invalidTokenResponse2));
|
||||
server.enqueue(new MockResponse().setBody(invalidVersionsResponse));
|
||||
server.enqueue(new MockResponse().setBody(invalidVersionsResponse2));
|
||||
server.enqueue(new MockResponse().setBody(invalidVersionsResponse3));
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
new MailboxProperties(baseUrl, token, true, new ArrayList<>());
|
||||
MailboxProperties properties2 =
|
||||
new MailboxProperties(baseUrl, token2, true);
|
||||
new MailboxProperties(baseUrl, token2, true, new ArrayList<>());
|
||||
|
||||
// valid response with valid token
|
||||
assertEquals(token2, api.setup(properties));
|
||||
mailboxPropertiesEqual(properties2, api.setup(properties));
|
||||
RecordedRequest request1 = server.takeRequest();
|
||||
assertEquals("/setup", request1.getPath());
|
||||
assertEquals("PUT", request1.getMethod());
|
||||
@@ -149,12 +162,38 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
assertEquals("/setup", request7.getPath());
|
||||
assertEquals("PUT", request7.getMethod());
|
||||
assertToken(request7, token);
|
||||
|
||||
// invalid non-array serverSupports response
|
||||
assertThrows(ApiException.class, () -> api.setup(properties));
|
||||
RecordedRequest request8 = server.takeRequest();
|
||||
assertEquals("/setup", request8.getPath());
|
||||
assertEquals("PUT", request8.getMethod());
|
||||
assertToken(request8, token);
|
||||
|
||||
// invalid non-object in serverSupports array response
|
||||
assertThrows(ApiException.class, () -> api.setup(properties));
|
||||
RecordedRequest request9 = server.takeRequest();
|
||||
assertEquals("/setup", request9.getPath());
|
||||
assertEquals("PUT", request9.getMethod());
|
||||
assertToken(request9, token);
|
||||
|
||||
// invalid negative minor version in serverSupports response
|
||||
assertThrows(ApiException.class, () -> api.setup(properties));
|
||||
RecordedRequest request10 = server.takeRequest();
|
||||
assertEquals("/setup", request10.getPath());
|
||||
assertEquals("PUT", request10.getMethod());
|
||||
assertToken(request10, token);
|
||||
}
|
||||
|
||||
private String makeSetupResponse(String token, String versions) {
|
||||
return "{\"token\":" + token + "," +
|
||||
"\"serverSupports\":" + versions + "}";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetupOnlyForOwner() {
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties("", token, false);
|
||||
new MailboxProperties("", token, false, new ArrayList<>());
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> api.setup(properties)
|
||||
@@ -170,9 +209,9 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
new MailboxProperties(baseUrl, token, true, new ArrayList<>());
|
||||
MailboxProperties properties2 =
|
||||
new MailboxProperties(baseUrl, token2, true);
|
||||
new MailboxProperties(baseUrl, token2, true, new ArrayList<>());
|
||||
|
||||
assertTrue(api.checkStatus(properties));
|
||||
RecordedRequest request1 = server.takeRequest();
|
||||
@@ -193,7 +232,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
@Test
|
||||
public void testStatusOnlyForOwner() {
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties("", token, false);
|
||||
new MailboxProperties("", token, false, new ArrayList<>());
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> api.checkStatus(properties)
|
||||
@@ -210,9 +249,9 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
new MailboxProperties(baseUrl, token, true, new ArrayList<>());
|
||||
MailboxProperties properties2 =
|
||||
new MailboxProperties(baseUrl, token2, true);
|
||||
new MailboxProperties(baseUrl, token2, true, new ArrayList<>());
|
||||
|
||||
api.wipeMailbox(properties);
|
||||
RecordedRequest request1 = server.takeRequest();
|
||||
@@ -242,7 +281,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
@Test
|
||||
public void testWipeOnlyForOwner() {
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties("", token, false);
|
||||
new MailboxProperties("", token, false, new ArrayList<>());
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
api.wipeMailbox(properties));
|
||||
}
|
||||
@@ -256,7 +295,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
new MailboxProperties(baseUrl, token, true, new ArrayList<>());
|
||||
|
||||
// contact gets added as expected
|
||||
api.addContact(properties, mailboxContact);
|
||||
@@ -288,7 +327,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
@Test
|
||||
public void testAddContactOnlyForOwner() {
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties("", token, false);
|
||||
new MailboxProperties("", token, false, new ArrayList<>());
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
api.addContact(properties, mailboxContact));
|
||||
}
|
||||
@@ -303,7 +342,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
new MailboxProperties(baseUrl, token, true, new ArrayList<>());
|
||||
|
||||
// contact gets deleted as expected
|
||||
api.deleteContact(properties, contactId);
|
||||
@@ -340,7 +379,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
@Test
|
||||
public void testDeleteContactOnlyForOwner() {
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties("", token, false);
|
||||
new MailboxProperties("", token, false, new ArrayList<>());
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
api.deleteContact(properties, contactId));
|
||||
}
|
||||
@@ -367,7 +406,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
new MailboxProperties(baseUrl, token, true, new ArrayList<>());
|
||||
|
||||
// valid response with two contacts
|
||||
assertEquals(singletonList(contactId), api.getContacts(properties));
|
||||
@@ -432,7 +471,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
@Test
|
||||
public void testGetContactsOnlyForOwner() {
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties("", token, false);
|
||||
new MailboxProperties("", token, false, new ArrayList<>());
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> api.getContacts(properties)
|
||||
@@ -452,7 +491,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
new MailboxProperties(baseUrl, token, true, new ArrayList<>());
|
||||
|
||||
// file gets uploaded as expected
|
||||
api.addFile(properties, contactInboxId, file);
|
||||
@@ -511,7 +550,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
new MailboxProperties(baseUrl, token, true, new ArrayList<>());
|
||||
|
||||
// valid response with one file
|
||||
List<MailboxFile> received1 = api.getFiles(properties, contactInboxId);
|
||||
@@ -607,7 +646,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
new MailboxProperties(baseUrl, token, true, new ArrayList<>());
|
||||
|
||||
// file gets downloaded as expected
|
||||
api.getFile(properties, contactOutboxId, name, file1);
|
||||
@@ -651,7 +690,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
new MailboxProperties(baseUrl, token, true, new ArrayList<>());
|
||||
|
||||
// file gets deleted as expected
|
||||
api.deleteFile(properties, contactInboxId, name);
|
||||
@@ -715,7 +754,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
new MailboxProperties(baseUrl, token, true, new ArrayList<>());
|
||||
|
||||
// valid response with one folders
|
||||
assertEquals(singletonList(id1), api.getFolders(properties));
|
||||
@@ -784,7 +823,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
||||
@Test
|
||||
public void testGetFoldersOnlyForOwner() {
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties("", token, false);
|
||||
new MailboxProperties("", token, false, new ArrayList<>());
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
api.getFolders(properties));
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@@ -89,17 +90,16 @@ public class MailboxIntegrationTest extends BrambleTestCase {
|
||||
assumeTrue(isOptionalTestEnabled(MailboxIntegrationTest.class));
|
||||
|
||||
if (ownerProperties != null) return;
|
||||
MailboxProperties setupProperties =
|
||||
new MailboxProperties(URL_BASE, SETUP_TOKEN, true);
|
||||
MailboxAuthToken ownerToken = api.setup(setupProperties);
|
||||
ownerProperties = new MailboxProperties(URL_BASE, ownerToken, true);
|
||||
MailboxProperties setupProperties = new MailboxProperties(
|
||||
URL_BASE, SETUP_TOKEN, true, new ArrayList<>());
|
||||
ownerProperties = api.setup(setupProperties);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
// we can't test wiping as a regular test as it stops the mailbox
|
||||
public static void wipe() throws IOException, ApiException {
|
||||
if (!isOptionalTestEnabled(MailboxIntegrationTest.class)) return;
|
||||
|
||||
|
||||
api.wipeMailbox(ownerProperties);
|
||||
|
||||
// check doesn't work anymore
|
||||
@@ -107,8 +107,8 @@ public class MailboxIntegrationTest extends BrambleTestCase {
|
||||
api.checkStatus(ownerProperties));
|
||||
|
||||
// new setup doesn't work as mailbox is stopping
|
||||
MailboxProperties setupProperties =
|
||||
new MailboxProperties(URL_BASE, SETUP_TOKEN, true);
|
||||
MailboxProperties setupProperties = new MailboxProperties(
|
||||
URL_BASE, SETUP_TOKEN, true, new ArrayList<>());
|
||||
assertThrows(ApiException.class, () -> api.setup(setupProperties));
|
||||
}
|
||||
|
||||
@@ -151,7 +151,8 @@ public class MailboxIntegrationTest extends BrambleTestCase {
|
||||
ContactId contactId = new ContactId(1);
|
||||
MailboxContact contact = getMailboxContact(contactId);
|
||||
MailboxProperties contactProperties = new MailboxProperties(
|
||||
ownerProperties.getBaseUrl(), contact.token, false);
|
||||
ownerProperties.getBaseUrl(), contact.token, false,
|
||||
new ArrayList<>());
|
||||
api.addContact(ownerProperties, contact);
|
||||
|
||||
// upload a file for our contact
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
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.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.mailbox.OwnMailboxConnectionStatusEvent;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
@@ -20,11 +23,15 @@ import org.junit.Test;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.hasEvent;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -33,16 +40,18 @@ import static org.junit.Assert.fail;
|
||||
public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final Executor executor = new ImmediateExecutor();
|
||||
private final TransactionManager db =
|
||||
context.mock(TransactionManager.class);
|
||||
private final DatabaseComponent db =
|
||||
context.mock(DatabaseComponent.class);
|
||||
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
private final Clock clock = context.mock(Clock.class);
|
||||
private final MailboxApi api = context.mock(MailboxApi.class);
|
||||
private final MailboxSettingsManager mailboxSettingsManager =
|
||||
context.mock(MailboxSettingsManager.class);
|
||||
private final MailboxPropertyManager mailboxPropertyManager =
|
||||
context.mock(MailboxPropertyManager.class);
|
||||
private final MailboxPairingTaskFactory factory =
|
||||
new MailboxPairingTaskFactoryImpl(executor, db, crypto, clock, api,
|
||||
mailboxSettingsManager);
|
||||
mailboxSettingsManager, mailboxPropertyManager);
|
||||
|
||||
private final String onion = getRandomString(56);
|
||||
private final byte[] onionBytes = getRandomBytes(32);
|
||||
@@ -53,10 +62,10 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
||||
new MailboxAuthToken(getRandomId());
|
||||
private final String validPayload = getValidPayload();
|
||||
private final long time = System.currentTimeMillis();
|
||||
private final MailboxProperties setupProperties =
|
||||
new MailboxProperties(onionAddress, setupToken, true);
|
||||
private final MailboxProperties ownerProperties =
|
||||
new MailboxProperties(onionAddress, ownerToken, true);
|
||||
private final MailboxProperties setupProperties = new MailboxProperties(
|
||||
onionAddress, setupToken, true, new ArrayList<>());
|
||||
private final MailboxProperties ownerProperties = new MailboxProperties(
|
||||
onionAddress, ownerToken, true, new ArrayList<>());
|
||||
|
||||
@Test
|
||||
public void testInitialQrCodeReceivedState() {
|
||||
@@ -87,19 +96,26 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testSuccessfulPairing() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(crypto).encodeOnionAddress(onionBytes);
|
||||
oneOf(crypto).encodeOnion(onionBytes);
|
||||
will(returnValue(onion));
|
||||
oneOf(api).setup(with(matches(setupProperties)));
|
||||
will(returnValue(ownerToken));
|
||||
will(returnValue(ownerProperties));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(time));
|
||||
}});
|
||||
Contact contact1 = getContact();
|
||||
Transaction txn = new Transaction(null, false);
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||
oneOf(mailboxSettingsManager).setOwnMailboxProperties(
|
||||
with(txn), with(matches(ownerProperties)));
|
||||
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, time);
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(Collections.singletonList(contact1)));
|
||||
oneOf(mailboxPropertyManager).getRemoteProperties(txn,
|
||||
contact1.getId());
|
||||
will(returnValue(null));
|
||||
oneOf(db).resetUnackedMessagesToSend(txn, contact1.getId());
|
||||
}});
|
||||
|
||||
AtomicInteger i = new AtomicInteger(0);
|
||||
@@ -118,6 +134,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
||||
i.getAndIncrement();
|
||||
});
|
||||
task.run();
|
||||
hasEvent(txn, OwnMailboxConnectionStatusEvent.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -141,7 +158,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
||||
private void testApiException(Exception e,
|
||||
Class<? extends MailboxPairingState> s) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(crypto).encodeOnionAddress(onionBytes);
|
||||
oneOf(crypto).encodeOnion(onionBytes);
|
||||
will(returnValue(onion));
|
||||
oneOf(api).setup(with(matches(setupProperties)));
|
||||
will(throwException(e));
|
||||
@@ -155,10 +172,10 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testDbException() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(crypto).encodeOnionAddress(onionBytes);
|
||||
oneOf(crypto).encodeOnion(onionBytes);
|
||||
will(returnValue(onion));
|
||||
oneOf(api).setup(with(matches(setupProperties)));
|
||||
will(returnValue(ownerToken));
|
||||
will(returnValue(ownerProperties));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(time));
|
||||
}});
|
||||
@@ -190,7 +207,8 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
||||
return new PredicateMatcher<>(MailboxProperties.class, p1 ->
|
||||
p1.getAuthToken().equals(p2.getAuthToken()) &&
|
||||
p1.getBaseUrl().equals(p2.getBaseUrl()) &&
|
||||
p1.isOwner() == p2.isOwner());
|
||||
p1.isOwner() == p2.isOwner() &&
|
||||
p1.getServerSupports().equals(p2.getServerSupports()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,702 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataParser;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.mailbox.RemoteMailboxPropertiesUpdateEvent;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.CLIENT_ID;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MAJOR_VERSION;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_VERSION;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_AUTHTOKEN;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_INBOXID;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_ONION;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_OUTBOXID;
|
||||
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.test.TestUtils.getContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.hasEvent;
|
||||
import static org.briarproject.bramble.test.TestUtils.mailboxPropertiesUpdateEqual;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
private final ClientVersioningManager clientVersioningManager =
|
||||
context.mock(ClientVersioningManager.class);
|
||||
private final MetadataParser metadataParser =
|
||||
context.mock(MetadataParser.class);
|
||||
private final ContactGroupFactory contactGroupFactory =
|
||||
context.mock(ContactGroupFactory.class);
|
||||
private final Clock clock = context.mock(Clock.class);
|
||||
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
private final MailboxSettingsManager mailboxSettingsManager =
|
||||
context.mock(MailboxSettingsManager.class);
|
||||
|
||||
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
private final BdfDictionary propsDict;
|
||||
private final BdfDictionary emptyPropsDict = new BdfDictionary();
|
||||
private final MailboxPropertiesUpdate props;
|
||||
private final MailboxProperties ownProps;
|
||||
|
||||
public MailboxPropertyManagerImplTest() {
|
||||
ownProps = new MailboxProperties("http://bar.onion",
|
||||
new MailboxAuthToken(getRandomId()), true, new ArrayList<>());
|
||||
props = new MailboxPropertiesUpdate(ownProps.getOnion(),
|
||||
new MailboxAuthToken(getRandomId()),
|
||||
new MailboxFolderId(getRandomId()),
|
||||
new MailboxFolderId(getRandomId()));
|
||||
propsDict = new BdfDictionary();
|
||||
propsDict.put(PROP_KEY_ONION, props.getOnion());
|
||||
propsDict.put(PROP_KEY_AUTHTOKEN, props.getAuthToken().getBytes());
|
||||
propsDict.put(PROP_KEY_INBOXID, props.getInboxId().getBytes());
|
||||
propsDict.put(PROP_KEY_OUTBOXID, props.getOutboxId().getBytes());
|
||||
}
|
||||
|
||||
private MailboxPropertyManagerImpl createInstance() {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID,
|
||||
MAJOR_VERSION);
|
||||
will(returnValue(localGroup));
|
||||
}});
|
||||
return new MailboxPropertyManagerImpl(db, clientHelper,
|
||||
clientVersioningManager, metadataParser, contactGroupFactory,
|
||||
clock, mailboxSettingsManager, crypto);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatesGroupsAtUnpairedStartup() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||
will(returnValue(false));
|
||||
oneOf(db).addGroup(txn, localGroup);
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(singletonList(contact)));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).addGroup(txn, contactGroup);
|
||||
oneOf(clientVersioningManager).getClientVisibility(txn,
|
||||
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||
will(returnValue(SHARED));
|
||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||
contactGroup.getId(), SHARED);
|
||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
||||
contact.getId());
|
||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||
will(returnValue(null));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
t.onDatabaseOpened(txn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatesGroupsAndCreatesAndSendsAtPairedStartup()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||
will(returnValue(false));
|
||||
oneOf(db).addGroup(txn, localGroup);
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(singletonList(contact)));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).addGroup(txn, contactGroup);
|
||||
oneOf(clientVersioningManager).getClientVisibility(txn,
|
||||
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||
will(returnValue(SHARED));
|
||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||
contactGroup.getId(), SHARED);
|
||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
||||
contact.getId());
|
||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||
will(returnValue(ownProps));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getAuthToken()));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getInboxId()));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getOutboxId()));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
expectStoreMessage(txn, contactGroup.getId(), propsDict, 1, true);
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
t.onDatabaseOpened(txn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotCreateGroupsAtStartupIfAlreadyCreated()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||
will(returnValue(true));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
t.onDatabaseOpened(txn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatesContactGroupWhenAddingContactUnpaired()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Create the group and share it with the contact
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).addGroup(txn, contactGroup);
|
||||
oneOf(clientVersioningManager).getClientVisibility(txn,
|
||||
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||
will(returnValue(SHARED));
|
||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||
contactGroup.getId(), SHARED);
|
||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
||||
contact.getId());
|
||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||
will(returnValue(null));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
t.addingContact(txn, contact);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatesContactGroupAndCreatesAndSendsWhenAddingContactPaired()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Create the group and share it with the contact
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).addGroup(txn, contactGroup);
|
||||
oneOf(clientVersioningManager).getClientVisibility(txn,
|
||||
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||
will(returnValue(SHARED));
|
||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||
contactGroup.getId(), SHARED);
|
||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
||||
contact.getId());
|
||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||
will(returnValue(ownProps));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getAuthToken()));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getInboxId()));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getOutboxId()));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
expectStoreMessage(txn, contactGroup.getId(), propsDict, 1, true);
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
t.addingContact(txn, contact);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemovesGroupWhenRemovingContact() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).removeGroup(txn, contactGroup);
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
t.removingContact(txn, contact);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotDeleteAnythingWhenFirstUpdateIsDelivered()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
GroupId contactGroupId = new GroupId(getRandomId());
|
||||
Message message = getMessage(contactGroupId);
|
||||
BdfList body = BdfList.of(1, propsDict);
|
||||
Metadata meta = new Metadata();
|
||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
// A local update should be ignored
|
||||
MessageId localUpdateId = new MessageId(getRandomId());
|
||||
messageMetadata.put(localUpdateId, BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true)
|
||||
));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(metadataParser).parse(meta);
|
||||
will(returnValue(metaDictionary));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroupId);
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getContactId(txn, contactGroupId);
|
||||
will(returnValue(contact.getId()));
|
||||
oneOf(clientHelper).getMessageAsList(txn, message.getId());
|
||||
will(returnValue(body));
|
||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||
propsDict);
|
||||
will(returnValue(props));
|
||||
oneOf(db).resetUnackedMessagesToSend(txn, contact.getId());
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||
t.incomingMessage(txn, message, meta));
|
||||
assertTrue(hasEvent(txn, RemoteMailboxPropertiesUpdateEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeletesOlderUpdateWhenUpdateIsDelivered()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
GroupId contactGroupId = new GroupId(getRandomId());
|
||||
Message message = getMessage(contactGroupId);
|
||||
BdfList body = BdfList.of(1, propsDict);
|
||||
Metadata meta = new Metadata();
|
||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 2),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
// This older version should be deleted
|
||||
MessageId updateId = new MessageId(getRandomId());
|
||||
messageMetadata.put(updateId, BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
));
|
||||
// A local update should be ignored
|
||||
MessageId localUpdateId = new MessageId(getRandomId());
|
||||
messageMetadata.put(localUpdateId, BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true)
|
||||
));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(metadataParser).parse(meta);
|
||||
will(returnValue(metaDictionary));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroupId);
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(db).deleteMessage(txn, updateId);
|
||||
oneOf(db).deleteMessageMetadata(txn, updateId);
|
||||
oneOf(clientHelper).getContactId(txn, contactGroupId);
|
||||
will(returnValue(contact.getId()));
|
||||
oneOf(clientHelper).getMessageAsList(txn, message.getId());
|
||||
will(returnValue(body));
|
||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||
propsDict);
|
||||
will(returnValue(props));
|
||||
oneOf(db).resetUnackedMessagesToSend(txn, contact.getId());
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||
t.incomingMessage(txn, message, meta));
|
||||
assertTrue(hasEvent(txn, RemoteMailboxPropertiesUpdateEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeletesObsoleteUpdateWhenDelivered() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
GroupId contactGroupId = new GroupId(getRandomId());
|
||||
Message message = getMessage(contactGroupId);
|
||||
Metadata meta = new Metadata();
|
||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 3),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
// This newer version should not be deleted
|
||||
MessageId updateId = new MessageId(getRandomId());
|
||||
messageMetadata.put(updateId, BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 4),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(metadataParser).parse(meta);
|
||||
will(returnValue(metaDictionary));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroupId);
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(db).deleteMessage(txn, message.getId());
|
||||
oneOf(db).deleteMessageMetadata(txn, message.getId());
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||
t.incomingMessage(txn, message, meta));
|
||||
assertFalse(hasEvent(txn, RemoteMailboxPropertiesUpdateEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatesAndStoresLocalPropertiesWithNewVersionOnPairing()
|
||||
throws Exception {
|
||||
Contact contact = getContact();
|
||||
List<Contact> contacts = singletonList(contact);
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
MessageId latestId = new MessageId(getRandomId());
|
||||
messageMetadata.put(latestId, BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true)
|
||||
));
|
||||
// Some remote props, ignored
|
||||
messageMetadata.put(new MessageId(getRandomId()), BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 3),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(contacts));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getAuthToken()));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getInboxId()));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getOutboxId()));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
expectStoreMessage(txn, contactGroup.getId(), propsDict, 2, true);
|
||||
oneOf(db).removeMessage(txn, latestId);
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
t.mailboxPaired(txn, ownProps.getOnion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoresEmptyLocalPropertiesWithNewVersionOnUnpairing()
|
||||
throws Exception {
|
||||
Contact contact = getContact();
|
||||
List<Contact> contacts = singletonList(contact);
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
MessageId latestId = new MessageId(getRandomId());
|
||||
messageMetadata.put(latestId, BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true)
|
||||
));
|
||||
// Some remote props, ignored
|
||||
messageMetadata.put(new MessageId(getRandomId()), BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 3),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(contacts));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
expectStoreMessage(txn, contactGroup.getId(), emptyPropsDict,
|
||||
2, true);
|
||||
oneOf(db).removeMessage(txn, latestId);
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
t.mailboxUnpaired(txn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRemoteProperties()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
MessageId fooUpdateId = new MessageId(getRandomId());
|
||||
messageMetadata.put(fooUpdateId, metaDictionary);
|
||||
BdfList fooUpdate = BdfList.of(1, propsDict);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
||||
will(returnValue(fooUpdate));
|
||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||
propsDict);
|
||||
will(returnValue(props));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
MailboxPropertiesUpdate remote =
|
||||
t.getRemoteProperties(txn, contact.getId());
|
||||
assertTrue(mailboxPropertiesUpdateEqual(remote, props));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRemotePropertiesReturnsNullBecauseNoUpdate()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
Map<MessageId, BdfDictionary> emptyMessageMetadata =
|
||||
new LinkedHashMap<>();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(emptyMessageMetadata));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
assertNull(t.getRemoteProperties(txn, contact.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRemotePropertiesReturnsNullBecauseEmptyUpdate()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
MessageId fooUpdateId = new MessageId(getRandomId());
|
||||
messageMetadata.put(fooUpdateId, metaDictionary);
|
||||
BdfList fooUpdate = BdfList.of(1, emptyPropsDict);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
||||
will(returnValue(fooUpdate));
|
||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||
emptyPropsDict);
|
||||
will(returnValue(null));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
assertNull(t.getRemoteProperties(txn, contact.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLocalProperties()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true)
|
||||
);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
MessageId fooUpdateId = new MessageId(getRandomId());
|
||||
messageMetadata.put(fooUpdateId, metaDictionary);
|
||||
BdfList fooUpdate = BdfList.of(1, propsDict);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
||||
will(returnValue(fooUpdate));
|
||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||
propsDict);
|
||||
will(returnValue(props));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
MailboxPropertiesUpdate local =
|
||||
t.getLocalProperties(txn, contact.getId());
|
||||
assertTrue(mailboxPropertiesUpdateEqual(local, props));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLocalPropertiesReturnsNullBecauseNoUpdate()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
Map<MessageId, BdfDictionary> emptyMessageMetadata =
|
||||
new LinkedHashMap<>();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(emptyMessageMetadata));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
assertNull(t.getLocalProperties(txn, contact.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLocalPropertiesReturnsNullBecauseEmptyUpdate()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true)
|
||||
);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
MessageId fooUpdateId = new MessageId(getRandomId());
|
||||
messageMetadata.put(fooUpdateId, metaDictionary);
|
||||
BdfList fooUpdate = BdfList.of(1, emptyPropsDict);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
||||
will(returnValue(fooUpdate));
|
||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||
emptyPropsDict);
|
||||
will(returnValue(null));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
assertNull(t.getLocalProperties(txn, contact.getId()));
|
||||
}
|
||||
|
||||
private void expectStoreMessage(Transaction txn, GroupId g,
|
||||
BdfDictionary properties, long version, boolean local)
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(version, properties);
|
||||
Message message = getMessage(g);
|
||||
long timestamp = message.getTimestamp();
|
||||
BdfDictionary meta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, version),
|
||||
new BdfEntry(MSG_KEY_LOCAL, local)
|
||||
);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(timestamp));
|
||||
oneOf(clientHelper).createMessage(g, timestamp, body);
|
||||
will(returnValue(message));
|
||||
oneOf(clientHelper).addLocalMessage(txn, message, meta, true,
|
||||
false);
|
||||
}});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class MailboxPropertyValidatorTest extends BrambleMockTestCase {
|
||||
|
||||
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
|
||||
private final BdfDictionary bdfDict;
|
||||
private final MailboxPropertiesUpdate mailboxProps;
|
||||
private final Group group;
|
||||
private final Message message;
|
||||
private final MailboxPropertyValidator mpv;
|
||||
|
||||
public MailboxPropertyValidatorTest() {
|
||||
// Just dummies, clientHelper is mocked so our test is a bit shallow;
|
||||
// not testing
|
||||
// {@link ClientHelper#parseAndValidateMailboxPropertiesUpdate(BdfDictionary)}
|
||||
bdfDict = BdfDictionary.of(new BdfEntry("foo", "bar"));
|
||||
mailboxProps = new MailboxPropertiesUpdate("baz",
|
||||
new MailboxAuthToken(getRandomId()),
|
||||
new MailboxFolderId(getRandomId()),
|
||||
new MailboxFolderId(getRandomId()));
|
||||
|
||||
group = getGroup(MailboxPropertyManager.CLIENT_ID,
|
||||
MailboxPropertyManager.MAJOR_VERSION);
|
||||
message = getMessage(group.getId());
|
||||
|
||||
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
|
||||
Clock clock = context.mock(Clock.class);
|
||||
mpv = new MailboxPropertyValidator(clientHelper, metadataEncoder,
|
||||
clock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateMessageBody() throws IOException {
|
||||
BdfList body = BdfList.of(4, bdfDict);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||
bdfDict);
|
||||
will(returnValue(mailboxProps));
|
||||
}});
|
||||
|
||||
BdfDictionary result =
|
||||
mpv.validateMessage(message, group, body).getDictionary();
|
||||
assertEquals(4, result.getLong("version").longValue());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateWrongVersionValue() throws IOException {
|
||||
BdfList body = BdfList.of(-1, bdfDict);
|
||||
mpv.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateWrongVersionType() throws IOException {
|
||||
BdfList body = BdfList.of(bdfDict, bdfDict);
|
||||
mpv.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyPropertiesReturnsNull() throws IOException {
|
||||
BdfDictionary emptyBdfDict = new BdfDictionary();
|
||||
BdfList body = BdfList.of(42, emptyBdfDict);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||
emptyBdfDict);
|
||||
will(returnValue(null));
|
||||
}});
|
||||
|
||||
BdfDictionary result =
|
||||
mpv.validateMessage(message, group, body).getDictionary();
|
||||
assertEquals(42, result.getLong("version").longValue());
|
||||
}
|
||||
}
|
||||
@@ -6,22 +6,28 @@ import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||
import org.briarproject.bramble.api.mailbox.OwnMailboxConnectionStatusEvent;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ATTEMPTS;
|
||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_ATTEMPT;
|
||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_SUCCESS;
|
||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ONION;
|
||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_SERVER_SUPPORTS;
|
||||
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_UPLOADS_NAMESPACE;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.hasEvent;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
@@ -38,6 +44,9 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
||||
private final Random random = new Random();
|
||||
private final String onion = getRandomString(64);
|
||||
private final MailboxAuthToken token = new MailboxAuthToken(getRandomId());
|
||||
private final List<MailboxVersion> serverSupports =
|
||||
asList(new MailboxVersion(1, 0), new MailboxVersion(1, 1));
|
||||
private final int[] serverSupportsInts = {1, 0, 1, 1};
|
||||
private final ContactId contactId1 = new ContactId(random.nextInt());
|
||||
private final ContactId contactId2 = new ContactId(random.nextInt());
|
||||
private final ContactId contactId3 = new ContactId(random.nextInt());
|
||||
@@ -65,6 +74,7 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
||||
Settings settings = new Settings();
|
||||
settings.put(SETTINGS_KEY_ONION, onion);
|
||||
settings.put(SETTINGS_KEY_TOKEN, token.toString());
|
||||
settings.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, serverSupportsInts);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
|
||||
@@ -75,6 +85,7 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
||||
assertNotNull(properties);
|
||||
assertEquals(onion, properties.getBaseUrl());
|
||||
assertEquals(token, properties.getAuthToken());
|
||||
assertEquals(serverSupports, properties.getServerSupports());
|
||||
assertTrue(properties.isOwner());
|
||||
}
|
||||
|
||||
@@ -84,8 +95,10 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
||||
Settings expectedSettings = new Settings();
|
||||
expectedSettings.put(SETTINGS_KEY_ONION, onion);
|
||||
expectedSettings.put(SETTINGS_KEY_TOKEN, token.toString());
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(onion, token, true);
|
||||
expectedSettings.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS,
|
||||
serverSupportsInts);
|
||||
MailboxProperties properties = new MailboxProperties(onion, token, true,
|
||||
serverSupports);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
|
||||
@@ -144,6 +157,7 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
manager.recordSuccessfulConnection(txn, now);
|
||||
hasEvent(txn, OwnMailboxConnectionStatusEvent.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -7,14 +7,16 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BLOCKED;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BRIDGES;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
|
||||
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.CircumventionProvider.DEFAULT_OBFS4_BRIDGES;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.DEFAULT_BRIDGES;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.MEEK_BRIDGES;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.NON_DEFAULT_OBFS4_BRIDGES;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.NON_DEFAULT_BRIDGES;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@@ -27,37 +29,39 @@ public class CircumventionProviderTest extends BrambleTestCase {
|
||||
public void testInvariants() {
|
||||
Set<String> blocked = new HashSet<>(asList(BLOCKED));
|
||||
Set<String> bridges = new HashSet<>(asList(BRIDGES));
|
||||
Set<String> defaultObfs4Bridges =
|
||||
new HashSet<>(asList(DEFAULT_OBFS4_BRIDGES));
|
||||
Set<String> nonDefaultObfs4Bridges =
|
||||
new HashSet<>(asList(NON_DEFAULT_OBFS4_BRIDGES));
|
||||
Set<String> defaultBridges = new HashSet<>(asList(DEFAULT_BRIDGES));
|
||||
Set<String> nonDefaultBridges =
|
||||
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
|
||||
Set<String> meekBridges = new HashSet<>(asList(MEEK_BRIDGES));
|
||||
// BRIDGES should be a subset of BLOCKED
|
||||
assertTrue(blocked.containsAll(bridges));
|
||||
// BRIDGES should be the union of the bridge type sets
|
||||
Set<String> union = new HashSet<>(defaultObfs4Bridges);
|
||||
union.addAll(nonDefaultObfs4Bridges);
|
||||
Set<String> union = new HashSet<>(defaultBridges);
|
||||
union.addAll(nonDefaultBridges);
|
||||
union.addAll(meekBridges);
|
||||
assertEquals(bridges, union);
|
||||
// The bridge type sets should not overlap
|
||||
assertEmptyIntersection(defaultObfs4Bridges, nonDefaultObfs4Bridges);
|
||||
assertEmptyIntersection(defaultObfs4Bridges, meekBridges);
|
||||
assertEmptyIntersection(nonDefaultObfs4Bridges, meekBridges);
|
||||
assertEmptyIntersection(defaultBridges, nonDefaultBridges);
|
||||
assertEmptyIntersection(defaultBridges, meekBridges);
|
||||
assertEmptyIntersection(nonDefaultBridges, meekBridges);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBestBridgeType() {
|
||||
for (String country : DEFAULT_OBFS4_BRIDGES) {
|
||||
assertEquals(DEFAULT_OBFS4, provider.getBestBridgeType(country));
|
||||
for (String country : DEFAULT_BRIDGES) {
|
||||
assertEquals(asList(DEFAULT_OBFS4, VANILLA),
|
||||
provider.getSuitableBridgeTypes(country));
|
||||
}
|
||||
for (String country : NON_DEFAULT_OBFS4_BRIDGES) {
|
||||
assertEquals(NON_DEFAULT_OBFS4,
|
||||
provider.getBestBridgeType(country));
|
||||
for (String country : NON_DEFAULT_BRIDGES) {
|
||||
assertEquals(asList(NON_DEFAULT_OBFS4, VANILLA),
|
||||
provider.getSuitableBridgeTypes(country));
|
||||
}
|
||||
for (String country : MEEK_BRIDGES) {
|
||||
assertEquals(MEEK, provider.getBestBridgeType(country));
|
||||
assertEquals(singletonList(MEEK),
|
||||
provider.getSuitableBridgeTypes(country));
|
||||
}
|
||||
assertEquals(DEFAULT_OBFS4, provider.getBestBridgeType("ZZ"));
|
||||
assertEquals(asList(DEFAULT_OBFS4, VANILLA),
|
||||
provider.getSuitableBridgeTypes("ZZ"));
|
||||
}
|
||||
|
||||
private <T> void assertEmptyIntersection(Set<T> a, Set<T> b) {
|
||||
|
||||
@@ -8,12 +8,9 @@ import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataParser;
|
||||
import org.briarproject.bramble.api.db.CommitAction;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.EventAction;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.properties.event.RemoteTransportPropertiesUpdatedEvent;
|
||||
@@ -48,6 +45,7 @@ import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.hasEvent;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -855,15 +853,4 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
false);
|
||||
}});
|
||||
}
|
||||
|
||||
private boolean hasEvent(Transaction txn,
|
||||
Class<? extends Event> eventClass) {
|
||||
for (CommitAction action : txn.getActions()) {
|
||||
if (action instanceof EventAction) {
|
||||
Event event = ((EventAction) action).getEvent();
|
||||
if (eventClass.isInstance(event)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ dependencies {
|
||||
def jna_version = '4.5.2'
|
||||
implementation "net.java.dev.jna:jna:$jna_version"
|
||||
implementation "net.java.dev.jna:jna-platform:$jna_version"
|
||||
tor "org.briarproject:tor:$tor_version"
|
||||
tor "org.briarproject:obfs4proxy:$obfs4proxy_version@zip"
|
||||
tor "org.briarproject:tor-linux:$tor_version"
|
||||
tor "org.briarproject:obfs4proxy-linux:$obfs4proxy_version"
|
||||
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons;
|
||||
import org.briarproject.bramble.api.Multiset;
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
@@ -28,10 +29,12 @@ import org.junit.runners.Parameterized.Parameters;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.inject.Inject;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
@@ -44,6 +47,7 @@ import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_SOCKS_POR
|
||||
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.NON_DEFAULT_OBFS4;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
|
||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
|
||||
@@ -60,24 +64,31 @@ public class BridgeTest extends BrambleTestCase {
|
||||
DaggerBrambleJavaIntegrationTestComponent.builder().build();
|
||||
BrambleCoreIntegrationTestEagerSingletons.Helper
|
||||
.injectEagerSingletons(component);
|
||||
// Share a failure counter among all the test instances
|
||||
AtomicInteger failures = new AtomicInteger(0);
|
||||
// Share stats among all the test instances
|
||||
Stats stats = new Stats();
|
||||
CircumventionProvider provider = component.getCircumventionProvider();
|
||||
List<Params> states = new ArrayList<>();
|
||||
for (String bridge : provider.getBridges(DEFAULT_OBFS4)) {
|
||||
states.add(new Params(bridge, DEFAULT_OBFS4, failures, false));
|
||||
}
|
||||
for (String bridge : provider.getBridges(NON_DEFAULT_OBFS4)) {
|
||||
states.add(new Params(bridge, NON_DEFAULT_OBFS4, failures, false));
|
||||
}
|
||||
for (String bridge : provider.getBridges(MEEK)) {
|
||||
states.add(new Params(bridge, MEEK, failures, true));
|
||||
for (int i = 0; i < ATTEMPTS_PER_BRIDGE; i++) {
|
||||
for (String bridge : provider.getBridges(DEFAULT_OBFS4)) {
|
||||
states.add(new Params(bridge, DEFAULT_OBFS4, stats, false));
|
||||
}
|
||||
for (String bridge : provider.getBridges(NON_DEFAULT_OBFS4)) {
|
||||
states.add(new Params(bridge, NON_DEFAULT_OBFS4, stats, false));
|
||||
}
|
||||
for (String bridge : provider.getBridges(VANILLA)) {
|
||||
states.add(new Params(bridge, VANILLA, stats, false));
|
||||
}
|
||||
for (String bridge : provider.getBridges(MEEK)) {
|
||||
states.add(new Params(bridge, MEEK, stats, true));
|
||||
}
|
||||
}
|
||||
return states;
|
||||
}
|
||||
|
||||
private final static long TIMEOUT = MINUTES.toMillis(5);
|
||||
private final static int NUM_FAILURES_ALLOWED = 1;
|
||||
private final static long OBFS4_TIMEOUT = MINUTES.toMillis(2);
|
||||
private final static long MEEK_TIMEOUT = MINUTES.toMillis(6);
|
||||
private final static int UNREACHABLE_BRIDGES_ALLOWED = 6;
|
||||
private final static int ATTEMPTS_PER_BRIDGE = 5;
|
||||
|
||||
private final static Logger LOG = getLogger(BridgeTest.class.getName());
|
||||
|
||||
@@ -92,8 +103,6 @@ public class BridgeTest extends BrambleTestCase {
|
||||
@Inject
|
||||
ResourceProvider resourceProvider;
|
||||
@Inject
|
||||
CircumventionProvider circumventionProvider;
|
||||
@Inject
|
||||
BatteryManager batteryManager;
|
||||
@Inject
|
||||
EventBus eventBus;
|
||||
@@ -143,8 +152,8 @@ public class BridgeTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BridgeType getBestBridgeType(String countryCode) {
|
||||
return params.bridgeType;
|
||||
public List<BridgeType> getSuitableBridgeTypes(String countryCode) {
|
||||
return singletonList(params.bridgeType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -175,18 +184,16 @@ public class BridgeTest extends BrambleTestCase {
|
||||
try {
|
||||
plugin.start();
|
||||
long start = clock.currentTimeMillis();
|
||||
while (clock.currentTimeMillis() - start < TIMEOUT) {
|
||||
long timeout = params.bridgeType == MEEK
|
||||
? MEEK_TIMEOUT : OBFS4_TIMEOUT;
|
||||
while (clock.currentTimeMillis() - start < timeout) {
|
||||
if (plugin.getState() == ACTIVE) return;
|
||||
clock.sleep(500);
|
||||
}
|
||||
if (plugin.getState() != ACTIVE) {
|
||||
LOG.warning("Could not connect to Tor within timeout");
|
||||
if (params.failures.incrementAndGet() > NUM_FAILURES_ALLOWED) {
|
||||
fail(params.failures.get() + " bridges are unreachable");
|
||||
}
|
||||
if (params.mustSucceed) {
|
||||
fail("essential bridge is unreachable");
|
||||
}
|
||||
LOG.warning("Could not connect to Tor within timeout: "
|
||||
+ params.bridge);
|
||||
params.stats.countFailure(params.bridge, params.essential);
|
||||
}
|
||||
} finally {
|
||||
plugin.stop();
|
||||
@@ -197,15 +204,39 @@ public class BridgeTest extends BrambleTestCase {
|
||||
|
||||
private final String bridge;
|
||||
private final BridgeType bridgeType;
|
||||
private final AtomicInteger failures;
|
||||
private final boolean mustSucceed;
|
||||
private final Stats stats;
|
||||
private final boolean essential;
|
||||
|
||||
private Params(String bridge, BridgeType bridgeType,
|
||||
AtomicInteger failures, boolean mustSucceed) {
|
||||
Stats stats, boolean essential) {
|
||||
this.bridge = bridge;
|
||||
this.bridgeType = bridgeType;
|
||||
this.failures = failures;
|
||||
this.mustSucceed = mustSucceed;
|
||||
this.stats = stats;
|
||||
this.essential = essential;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Stats {
|
||||
|
||||
@GuardedBy("this")
|
||||
private final Multiset<String> failures = new Multiset<>();
|
||||
@GuardedBy("this")
|
||||
private final Set<String> unreachable = new TreeSet<>();
|
||||
|
||||
private synchronized void countFailure(String bridge,
|
||||
boolean essential) {
|
||||
if (failures.add(bridge) == ATTEMPTS_PER_BRIDGE) {
|
||||
LOG.warning("Bridge is unreachable after "
|
||||
+ ATTEMPTS_PER_BRIDGE + " attempts: " + bridge);
|
||||
unreachable.add(bridge);
|
||||
if (unreachable.size() > UNREACHABLE_BRIDGES_ALLOWED) {
|
||||
fail(unreachable.size() + " bridges are unreachable: "
|
||||
+ unreachable);
|
||||
}
|
||||
if (essential) {
|
||||
fail("essential bridge is unreachable");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ dependencyVerification {
|
||||
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
|
||||
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
||||
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
||||
'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a:obfs4proxy-0.0.12-dev-40245c4a.zip:172029e7058b3a83ac93ac4991a44bf76e16ce8d46f558f5836d57da3cb3a766',
|
||||
'org.briarproject:tor:0.3.5.17:tor-0.3.5.17.jar:ce0e1f4d8f14878e61b23a35a452bc0f2a8e3117ced5a74773cd78475fa7af39',
|
||||
'org.briarproject:obfs4proxy-linux:0.0.12:obfs4proxy-linux-0.0.12.jar:3dd83aff25fe1cb3e4eab78a02c76ac921f552be6877b3af83a472438525df2a',
|
||||
'org.briarproject:tor-linux:0.4.5.12-2:tor-linux-0.4.5.12-2.jar:d275f323faf5e70b33d2c8a1bdab1bb3ab5a0d8f4e23c4a6dda03d86f4e95838',
|
||||
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
||||
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
||||
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
|
||||
|
||||
8
briar-android/.gitignore
vendored
8
briar-android/.gitignore
vendored
@@ -11,5 +11,9 @@ src/main/res/values-iw
|
||||
/fastlane/metadata/android/*/images
|
||||
/fastlane/report.xml
|
||||
/fastlane/README.md
|
||||
|
||||
/fastlane/metadata/android/*/changelogs
|
||||
/fastlane/metadata/android/*/changelogs
|
||||
# Local Fastlane installation
|
||||
/fastlane/vendor
|
||||
/fastlane/.bundle
|
||||
/fastlane/Gemfile
|
||||
/fastlane/Gemfile.lock
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
host = https://www.transifex.com
|
||||
lang_map = pt_BR: pt-rBR, nb_NO: nb, zh-Hans: zh-rCN, zh-Hant: zh-rTW
|
||||
|
||||
[briar.stringsxml-5]
|
||||
file_filter = src/main/res/values-<lang>/strings.xml
|
||||
source_file = src/main/res/values/strings.xml
|
||||
source_lang = en
|
||||
type = ANDROID
|
||||
[o:otf:p:briar:r:google-play-full-description]
|
||||
file_filter = fastlane/metadata/android/<lang>/full_description.txt
|
||||
source_file = fastlane/metadata/android/en-US/full_description.txt
|
||||
source_lang = en
|
||||
type = TXT
|
||||
minimum_perc = 100
|
||||
# https://support.google.com/googleplay/android-developer/answer/9844778?hl=en#zippy=%2Cview-list-of-available-languages
|
||||
lang_map = de: de-DE, is: is-IS, ja: ja-JP, tr: tr-TR, zh-Hans: zh-CN, ru: ru-RU, sv: sv-SE, hu: hu-HU, it: it-IT, my: my-MM, en: en-US, es: es-ES, fr: fr-FR, gl: gl-ES, pl: pl-PL, el: el-GR
|
||||
|
||||
[o:otf:p:briar:r:google-play-short-description]
|
||||
file_filter = fastlane/metadata/android/<lang>/short_description.txt
|
||||
source_file = fastlane/metadata/android/en-US/short_description.txt
|
||||
source_lang = en
|
||||
type = TXT
|
||||
minimum_perc = 100
|
||||
lang_map = en: en-US, es: es-ES, is: is-IS, tr: tr-TR, gl: gl-ES, it: it-IT, my: my-MM, sv: sv-SE, zh-Hans: zh-CN, de: de-DE, ja: ja-JP, ru: ru-RU, fr: fr-FR, hu: hu-HU, pl: pl-PL, el: el-GR
|
||||
|
||||
[o:otf:p:briar:r:stringsxml-5]
|
||||
file_filter = src/main/res/values-<lang>/strings.xml
|
||||
source_file = src/main/res/values/strings.xml
|
||||
source_lang = en
|
||||
type = ANDROID
|
||||
minimum_perc = 80
|
||||
|
||||
[briar.google-play-short-description]
|
||||
# https://support.google.com/googleplay/android-developer/answer/9844778?hl=en#zippy=%2Cview-list-of-available-languages
|
||||
lang_map = en: en-US, de: de-DE, es: es-ES, gl: gl-ES, tr: tr-TR, zh-Hans: zh-CN
|
||||
file_filter = fastlane/metadata/android/<lang>/short_description.txt
|
||||
source_file = fastlane/metadata/android/en-US/short_description.txt
|
||||
source_lang = en
|
||||
type = TXT
|
||||
minimum_perc = 100
|
||||
|
||||
[briar.google-play-full-description]
|
||||
lang_map = en: en-US, de: de-DE, es: es-ES, gl: gl-ES, tr: tr-TR, zh-Hans: zh-CN
|
||||
file_filter = fastlane/metadata/android/<lang>/full_description.txt
|
||||
source_file = fastlane/metadata/android/en-US/full_description.txt
|
||||
source_lang = en
|
||||
type = TXT
|
||||
minimum_perc = 100
|
||||
|
||||
@@ -26,8 +26,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
versionCode 10405
|
||||
versionName "1.4.5"
|
||||
versionCode 10406
|
||||
versionName "1.4.6"
|
||||
applicationId "org.briarproject.briar.android"
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
@@ -61,8 +61,9 @@ android {
|
||||
productFlavors {
|
||||
screenshot {
|
||||
dimension "version"
|
||||
minSdkVersion 18
|
||||
minSdkVersion 21
|
||||
applicationIdSuffix ".screenshot" // = org.briarproject.briar.android.screenshot.debug
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt', 'proguard-test.txt'
|
||||
}
|
||||
official {
|
||||
dimension "version"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
Briar е приложение за обменяне на съобщения, предназначено за активисти, журналисти и всички други, които имат нужда от безопасен, лесен и стабилен начин за общуване. За разлика от останалите такива приложения, Briar може да използва Bluetooth или Wi-Fi, за да поддържа потока на информация по време на криза. При наличие на интернет, Briar използва мрежата на Tor и така предпазва потребителите от наблюдение.
|
||||
|
||||
Приложението предлага лични съобщения, групи, форуми, а също и блогове. Вградена поддръжка на Tor. Всичко, което правите в Briar се съхранява на устройството ви, освен ако не решите да го споделите.
|
||||
|
||||
Няма реклами и проследявания. Изходният код е достъпен за преглед от всеки и е преминал професионален одит. Всички издания на Briar могат да бъдат пресъздадени и така може да бъде проверено, че публикувания изходен код отговаря на публикуваното тук приложение. Разработката се извършва от малък, неплатен екип.
|
||||
@@ -0,0 +1 @@
|
||||
Сигурни съобщения, навсякъде
|
||||
1
briar-android/fastlane/metadata/android/bg/title.txt
Normal file
1
briar-android/fastlane/metadata/android/bg/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Briar
|
||||
@@ -1,4 +1,4 @@
|
||||
Briar ist eine Messaging-App für Aktivisten, Journalisten und jeden, der eine sichere, einfache und robuste Art der Kommunikation benötigt. Im Gegensatz zu herkömmlichen Messaging-Apps benötigt Briar keinen zentralen Server. Nachrichten werden direkt zwischen den Endgeräten der Benutzer ausgetauscht. Wenn das Internet ausfällt, kann Briar diese auch über Bluetooth oder WLAN austauschen, um den Informationsaustausch in einer Krise aufrecht zu erhalten. Mit einer Internet Verbindung synchronisiert sich Briar über das Tor-Netzwerk und schützt so die Benutzer und ihre Kontakte vor Überwachung.
|
||||
Briar ist eine Messaging-App für Aktivisten, Journalisten und jeden, der eine sichere, einfache und robuste Art der Kommunikation benötigt. Im Gegensatz zu herkömmlichen Messaging-Apps benötigt Briar keinen zentralen Server. Nachrichten werden direkt zwischen den Geräten der Benutzer ausgetauscht. Wenn das Internet ausfällt, kann Briar diese auch über Bluetooth oder WLAN austauschen, um den Informationsaustausch in einer Krise aufrecht zu erhalten. Mit einer Internetverbindung kann Briar sich über das Tor-Netzwerk synchronisieren und schützt so die Nutzer und ihre Kontakte vor Überwachung.
|
||||
|
||||
Die App bietet private Nachrichten, Gruppen und Foren sowie Blogs. Die Unterstützung für das Tor-Netzwerk ist in die App integriert. Alles, was du in Briar machst, wird nur auf deinem Gerät gespeichert, es sei denn, du entscheidest dich, es mit anderen Benutzern zu teilen.
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
Το Briar είναι μία εφαρμογή ανταλλαγής μηνυμάτων σχεδιασμένο για ακτιβιστές, δημοσιογράφους και οποιονδήποτε άλλο που χρειάζεται ένα ασφαλές, εύκολο και εύρωστο τρόπο επικοινωνίας. Σε αντίθεση με τις παραδοσιακές εφαρμογές ανταλλαγής μηνυμάτων, το Briar δεν βασίζεται σε κεντρικό διακομιστή - τα μηνύματα συγχρονίζονται απευθείας μεταξύ των συσκευών των χρηστών. Εάν το Διαδίκτυο είναι εκτός λειτουργίας, το Briar μπορεί να συγχρονιστεί μέσω Bluetooth ή Wi-Fi, διατηρώντας τη ροή των πληροφοριών σε περίπτωση κρίσης. Εάν το διαδίκτυο είναι εκτός λειτουργίας, το Briar μπορεί να συγχρονιστεί μέσω του δικτύου Tor, προστατεύοντας τους χρήστες και τις σχέσεις τους από την επιτήρηση.
|
||||
|
||||
Η εφαρμογή διαθέτει προσωπικά μηνύματα, ομάδες και forum καθώς και blogs. Η υποστήριξη για το δίκτυο Tor είναι ενσωματωμένη στην εφαρμογή. Ό,τι κάνετε στο Briar αποθηκεύεται μόνο στη συσκευή σας, εκτός εάν αποφασίσετε να το μοιραστείτε με άλλους χρήστες.
|
||||
|
||||
Δεν υπάρχουν διαφημίσεις και κανένα μέσο παρακολούθησης. Ο πηγαίος κώδικας της εφαρμογής είναι εντελώς ανοιχτός για έλεγχο από οποιονδήποτε και έχει ήδη ελεγχθεί επαγγελματικά. Όλες οι εκδόσεις του Briar μπορούν να αναπαραχθούν, καθιστώντας δυνατή την επαλήθευση ότι ο δημοσιευμένος πηγαίος κώδικας ταιριάζει ακριβώς με την εφαρμογή που δημοσιεύεται εδώ. Η ανάπτυξη γίνεται από μια μικρή μη κερδοσκοπική ομάδα.
|
||||
@@ -0,0 +1 @@
|
||||
Ασφαλής ανταλλαγή μηνυμάτων, οπουδήποτε.
|
||||
1
briar-android/fastlane/metadata/android/el-GR/title.txt
Normal file
1
briar-android/fastlane/metadata/android/el-GR/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Briar
|
||||
@@ -0,0 +1,5 @@
|
||||
Briar est une appli de messagerie conçue pour les activistes, les journalistes et quiconque a besoin d’un moyen sécurisé, simple et robuste de communiquer. Contrairement aux applis traditionnelles de messageries, Briar ne dépend pas d’un serveur centralisé. Les messages sont synchronisés directement entre les machines des utilisateurs. Si Internet est inaccessible, Briar peut se synchroniser par Bluetooth ou Wi-Fi, ce qui permet à l’information de circuler lors d’une crise. Si Internet est accessible, Briar peut se synchroniser par le réseau Tor, protégeant ainsi les utilisateurs et leurs relations contre la surveillance.
|
||||
|
||||
L’appli propose des messages, des groupes, des forums ainsi que des blogues, en toute confidentialité. La prise en charge du réseau Tor est intégrée à l’appli. Tout ce que vous effectuez dans Briar n’est enregistré que sur votre appareil, à moins que vous ne décidiez de le partager avec d’autres utilisateurs.
|
||||
|
||||
Il n’y a ni publicité ni aucun suivi à la trace. Le code source de l’appli est entièrement ouvert ; n’importe qui peut l’examiner et il a été vérifié de façon professionnelle. Toutes les versions de Briar sont reproductibles, ce qui permet de confirmer que le code source publié correspond exactement à l’appli publiée ici. La conception est effectuée par une petite équipe sans but lucratif.
|
||||
@@ -0,0 +1 @@
|
||||
Des messages sécurisés, partout.
|
||||
1
briar-android/fastlane/metadata/android/fr-FR/title.txt
Normal file
1
briar-android/fastlane/metadata/android/fr-FR/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Briar
|
||||
@@ -0,0 +1,5 @@
|
||||
Briar egy üzenetküldő alkalmazás, amit aktivisták, újságírók és bárki olyan számára terveztek, akinek szüksége van biztonságos és megbízható módon kommunikálni. A hagyományos üzenetküldő alkalmazásokkal ellentétben a Briar nem függ egy központi szervertől, az üzenetek közvetlenül a felhasználók készülékei között szinkronizálódnak. Ha az internet nem elérhető, a Briar Bluetooth vagy Wi-Fi kapcsolaton keresztül szinkronizál. Amikor az interent elérhető, a Briar a Tor hálózaton keresztül szinkronizál, megvédve a felhasználókat és kapcsolataikat a megfigyeléstől.
|
||||
|
||||
Az app biztosít privát üzenteket, csoportokat, fórumokat és blogokat is. A Tor hálózat támogatása beépített az app-ba. Bármi amit a Briar-ban csinálsz csak a saját eszközödön tárolt, amíg nem döntesz úgy, hogy megosztod másokkal.
|
||||
|
||||
Nincsennek hirdetések és nincs követés. A forráskódja az app-nak teljesen nyílt, bárki megvizsgálhatja, azonban professzionálisan auditált. Minden kiadása a Briar-nak reprodukálható, lehetővé téve azt, hogy ellenőrizd, hogy a forráskód és az app megegyezik. A fejlesztés egy kis nonprofit csapattal történik.
|
||||
@@ -0,0 +1 @@
|
||||
Biztonságos üzenetküldés, bárhol
|
||||
1
briar-android/fastlane/metadata/android/hu-HU/title.txt
Normal file
1
briar-android/fastlane/metadata/android/hu-HU/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Briar
|
||||
@@ -0,0 +1,5 @@
|
||||
Briar er samskiptaforrit hannað fyrir aðgerðasinna, blaðamenn og alla þá sem þurfa örygga, auðvelda og áreiðanlega leið til að eiga í samskiptum. Ólíkt hefðbundnum skilaboðaforritum reiðir Briar sig ekki á miðlæga þjóna - skilaboðin eru samstillt beint á milli tækja notendanna. Ef internetið liggur niðri getur Briar samstillt í gegnum Bluetooth eða Wi-Fi þráðlaus net og þannig haldið upplýsingaflæðinu gangandi á neyðarstundum. Ef internetið er starfhæft getur Briar samstillt í gegnum Tor-netkerfið, sem verndar notendurna og tengslanet þeirra fyrir eftirliti og ritskoðun.
|
||||
|
||||
Forritið býður upp á einkaskilaboð, hópa, spjallsvæði og blogg. Stuðningur við Tor er innbyggður í forritið. Allt sem þú gerir í Briar er einungis geymt á tækinu þínu, nema þú ákveðir að deila einhverju með öðrum.
|
||||
|
||||
Það eru engar auglýsingar og ekkert eftirlit. Grunnkóði forritsins er algerlega opinn og aðgengilegur öllum sem vilja skoða hann og hefur verið yfirfarinn af sérfræðingum. Allar útgáfur Briar eru endurgeranlegar, sem gerir kleift að sjá að útgefinn grunnkóði samsvari nákvæmlega forritinu sem birtist hér. Lítið samhent teymi stendur að þróun hugbúnaðarins, án hagnaðarsjónarmiða.
|
||||
@@ -0,0 +1 @@
|
||||
Örugg skilaboð, allstaðar.
|
||||
1
briar-android/fastlane/metadata/android/is-IS/title.txt
Normal file
1
briar-android/fastlane/metadata/android/is-IS/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Briar
|
||||
@@ -0,0 +1,5 @@
|
||||
Briar è un'app di messaggistica progettata per attivisti, giornalisti e chiunque altro abbia bisogno di un modo sicuro, facile e robusto di comunicare. A differenza delle app di messaggistica tradizionali, Briar non si basa su un server centrale - i messaggi sono sincronizzati direttamente tra i dispositivi degli utenti. Se non c'è internet, Briar può sincronizzare via Bluetooth o Wi-Fi, mantenendo attive le comunicazioni in caso di emergenza. Se c'è internet, Briar può sincronizzare tramite rete Tor, proteggendo gli utenti e le loro relazioni dalla sorveglianza.
|
||||
|
||||
L'app offre messaggi privati, gruppi, forum e blog. È integrato nell'app anche il supporto per la rete Tor. Qualsiasi cosa tu faccia su Briar è memorizzata solo nel tuo dispositivo finché non decidi di condividerla con altri utenti.
|
||||
|
||||
Non ci sono pubblicità e nessun tracciamento. Il codice sorgente dell'app è completamente aperto e ispezionabile da chiunque ed è già stato analizzato da professionisti. Tutte le versioni di Briar sono riproducibili, rendendo possibile verificare che il codice pubblicato corrisponda esattamente all'app pubblicata qui. Lo sviluppo è sostenuto da una piccola squadra non a scopo di lucro.
|
||||
@@ -0,0 +1 @@
|
||||
Messaggi sicuri, ovunque.
|
||||
1
briar-android/fastlane/metadata/android/it-IT/title.txt
Normal file
1
briar-android/fastlane/metadata/android/it-IT/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Briar
|
||||
@@ -0,0 +1,5 @@
|
||||
Briarは、活動家や記者など、安全で簡単かつ堅牢な通信手段を必要とする人々のために開発されたメッセージアプリです。従来のメッセージアプリとは異なり、Briarは中央のサーバーに依存せず、ユーザーの端末間で直接メッセージを同期させます。インターネットがダウンしても、BriarはBluetoothやWi-Fiを介して同期することができ、危機的状況下でも情報の流れを維持することができます。インターネットが使えない場合は、Torネットワークを介して同期することができ、ユーザーとその関係を監視から守ります。
|
||||
|
||||
このアプリには、プライベートメッセージ、グループ、フォーラム、ブログなどの機能があります。Torネットワークのサポートはアプリに組み込まれています。あなたがBriarで行ったことは、他のユーザーと共有することを決めない限り、あなたの端末にのみ保存されます。
|
||||
|
||||
広告や追跡は一切ありません。このアプリのソースコードは誰でも閲覧できるように完全に公開されており、すでに専門家による監査も受けています。すべてのBriarのリリースは再現可能であり、公開されたソースコードがここで公開されたアプリと正確に一致しているかどうかを検証することが可能です。開発は小さな非営利チームによって行われています。
|
||||
@@ -0,0 +1 @@
|
||||
安全なメッセージ、どこでも
|
||||
1
briar-android/fastlane/metadata/android/ja-JP/title.txt
Normal file
1
briar-android/fastlane/metadata/android/ja-JP/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Briar
|
||||
@@ -0,0 +1,5 @@
|
||||
Briar ဆိုသည်မှာ လုံခြုံမှုရှိ၍ လွယ်ကူလျင်မြန်အားကောင်းသော ဆက်သွယ်ရေး နည်းလမ်းတစ်ခုကို လိုအပ်သည့် လူမှုရေး (သို့) နိုင်ငံရေး တက်ကြွလှုပ်ရှားသူများ၊ ဂျာနယ်လစ်များအပြင် အခြားမည်သူအတွက်မဆို အထူးဖန်တီးပြုလုပ်ထားသော မက်ဆေ့ချ်ပေးပို့ခြင်း အက်ပ်တစ်ခု ဖြစ်ပါသည်။ သာမန်မက်ဆေ့ချ်ပေးပို့ခြင်းအက်ပ်များနှင့်မတူသည်မှာ Briar သည် ဗဟိုပြုဆာဗာတစ်ခုတည်းအပေါ် မှီခိုခြင်းမရှိဘဲ မက်ဆေ့ချ်များကို အသုံးပြုသူများ၏စက်ပစ္စည်းများအချင်းချင်းကြားတွင်သာ တိုက်ရိုက်ကူးလူးပို့ဆောင်ပေးခြင်းဖြစ်ပါသည်။ ကပ်ဘေးများကြုံနေရချိန် အင်တာနက်မရနိုင်လျှင်ပင် Briar သည် ဘလူးတုသ် (သို့) ဝိုင်ဖိုင် အသုံးပြုပြီး သတင်းအချက်အလက်များကို ကူးလူးပို့ဆောင်ပေးနေနိုင်ပါသည်။ အင်တာနက်ရရှိပါကလည်း Tor ကွန်ယက်ကို ကြားခံအသုံးပြုပြီး အသုံးပြုသူများနှင့် ၎င်းတို့၏ပတ်သက်ဆက်နွယ်သူများကို ကြားဖြတ်စောင့်ကြည့်ခြင်းမှ Briar က ကာကွယ်ပေးထားပါသည်။
|
||||
|
||||
ဤအက်ပ်တွင်သီးသန့် မက်ဆေ့ချ်များ၊ အုပ်စုများနှင့်ဖိုရမ်များအပြင် ဘလော့ဂ်များပါ ပါ၀င်သည်။ Tor ကွန်ယက်အတွက် အထောက်အပံ့ ဖြစ်စေရန်လည်း အက်ပလီကေးရှင်းတွင် တည်ဆောက်ထားသည်။ သင်၏ Briar တွင် သင်ပြုလုပ်သမျှသည် အခြားအသုံးပြုသူများနှင့် မျှဝေရန် သင်ဆုံးဖြတ်ခြင်း မရှိသေးသည်အထိ သင်၏ကိရိယာပေါ်တွင်သာ သိမ်းဆည်းထားသည်။
|
||||
|
||||
ကြော်ငြာများဖော်ပြခြင်း၊ ခြေရာခံနိုင်သော အထောက်အထားများ ကျန်ရှိမှုမျိုးများ မရှိပါ။ ပရိုဂရမ်၏အရင်းအမြစ်ကုဒ်သည် မည်သူမဆို စစ်ဆေးရန်အတွက် လုံးဝပွင့်လင်းပြီး ပညာရှင်ဆန်ဆန် စစ်ဆေးထားပြီး ဖြစ်သည်။ ထွက်ရှိထားပြီးသော Briar ဆော့ဖ်ဝဲကို ပြန်လည်ထုတ်ဝေနိုင်ပြီး၊ ထုတ်ဝေခဲ့ပြီးသော အရင်းအမြစ်ကုဒ်သည် ဤနေရာတွင်ထုတ်ဝေသော အက်ပလီကေးရှင်းနှင့် ကိုက်ညီမှု ရှိမရှိကို စစ်ဆေးနိုင်ပါသည်။ Briar ဆော့ဖ်ဝဲရေးသားခြင်းကို အကျိုးအမြတ်မယူသော အဖွဲ့ငယ်တစ်ခုက ပြုလုပ်သည်။
|
||||
@@ -0,0 +1 @@
|
||||
ဘယ်နေရာမှာပဲဖြစ်ဖြစ် လုံခြုံတဲ့ မက်ဆေ့ချ် ပေးပို့ခြင်း။
|
||||
1
briar-android/fastlane/metadata/android/my-MM/title.txt
Normal file
1
briar-android/fastlane/metadata/android/my-MM/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Briar
|
||||
@@ -0,0 +1,5 @@
|
||||
Briar to aplikacja do przesyłania wiadomości zaprojektowana z myślą o aktywistach, dziennikarzach i wszystkich innych osobach, które potrzebują bezpiecznego, łatwego i solidnego sposobu komunikacji. W przeciwieństwie do tradycyjnych aplikacji, Briar nie opiera się na centralnym serwerze - wiadomości są synchronizowane bezpośrednio między urządzeniami użytkowników. Jeśli Internet nie działa, Briar może synchronizować się przez Bluetooth lub sieć Wi-Fi, zapewniając przepływ informacji w sytuacjach kryzysowych. Jeśli internet jest włączony, Briar może synchronizować się za pośrednictwem sieci Tor, chroniąc użytkowników i ich kontakty przed inwigilacją.
|
||||
|
||||
Aplikacja oferuje prywatne wiadomości, grupy i fora, a także blogi. Wsparcie dla sieci Tor jest wbudowane w aplikację. Wszystko co robisz w Briar jest przechowywane tylko na Twoim urządzeniu, chyba że zdecydujesz się udostępnić to innym użytkownikom.
|
||||
|
||||
W aplikacji nie ma żadnych reklam i skryptów śledzących. Kod źródłowy aplikacji jest całkowicie otwarty do wglądu dla każdego i został już poddany profesjonalnemu audytowi. Wszystkie wydania Briar są powtarzalne, co pozwala na sprawdzenie, czy opublikowany kod źródłowy dokładnie odpowiada aplikacji opublikowanej tutaj. Rozwój aplikacji jest prowadzony przez mały zespół non-profit.
|
||||
@@ -0,0 +1 @@
|
||||
Bezpieczne wysyłanie wiadomości, gdziekolwiek jesteś.
|
||||
1
briar-android/fastlane/metadata/android/pl-PL/title.txt
Normal file
1
briar-android/fastlane/metadata/android/pl-PL/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Briar
|
||||
@@ -0,0 +1,5 @@
|
||||
Briar este un app de mesagerie concepută pentru activiști, jurnaliști și oricine altcineva care are nevoie de o modalitate sigură, ușoară și robustă de a comunica. Spre deosebire de app-urile tradiționale de mesagerie, Briar nu se bazează pe un server central - mesajele sunt sincronizate direct între dispozitivele utilizatorilor. Dacă Internetul nu este disponibil, Briar poate sincroniza prin Bluetooth sau Wi-Fi, permițând schimbul de informații într-o situație de criză. Dacă Internetul este disponibil, Briar poate sincroniza prin intermediul rețelei Tor, protejând utilizatorii și relațiile lor de supraveghere.
|
||||
|
||||
Aplicația oferă mesaje private, grupuri și forumuri, precum și bloguri. Suportul pentru rețeaua Tor este integrat în aplicație. Tot ceea ce faceți în Briar este stocat doar pe dispozitivul dumneavoastră, cu excepția cazului în care decideți să îl partajați cu alți utilizatori.
|
||||
|
||||
Nu există reclame și nici urmărire. Codul sursă al aplicației este complet deschis pentru a fi inspectat de oricine și a fost deja auditat profesional. Toate versiunile Briar sunt reproductibile, ceea ce face posibilă verificarea faptului că codul sursă publicat corespunde exact aplicației publicată aici. Dezvoltarea este realizată de o mică echipă non-profit.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user