mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
49 Commits
beta-1.5.2
...
client-ver
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72136cc627 | ||
|
|
411ace13aa | ||
|
|
952ee42ad1 | ||
|
|
f61b09d5a9 | ||
|
|
8f735d176e | ||
|
|
c47253fc5f | ||
|
|
7a0fb74c09 | ||
|
|
882f536b8d | ||
|
|
74f8e84a9b | ||
|
|
23df2a41c2 | ||
|
|
c77eaf16d9 | ||
|
|
9a6bb4b203 | ||
|
|
3d237a9104 | ||
|
|
fa216ffc6f | ||
|
|
a34631d36c | ||
|
|
45cda191e5 | ||
|
|
2495b6f5c0 | ||
|
|
03fc504f7d | ||
|
|
d19062e319 | ||
|
|
fdb429ab7a | ||
|
|
d0c59a6d75 | ||
|
|
3bb39c2aa3 | ||
|
|
917fc5e5b6 | ||
|
|
caa078585b | ||
|
|
e68c0c7f4b | ||
|
|
a6b3749fb6 | ||
|
|
a8f6e8e4bd | ||
|
|
4d884601f0 | ||
|
|
b71198d9b1 | ||
|
|
079c6e0475 | ||
|
|
3a0f8ed85c | ||
|
|
57f7501780 | ||
|
|
3cc5699fe0 | ||
|
|
7d761710e6 | ||
|
|
7461d3c943 | ||
|
|
9291613175 | ||
|
|
ce6739a9fd | ||
|
|
1f1a97f62d | ||
|
|
7a33d26533 | ||
|
|
f2c85f37be | ||
|
|
8e3fa872fd | ||
|
|
0d1e81ebdb | ||
|
|
bded4e7bc8 | ||
|
|
bf1a5cf218 | ||
|
|
dd7a638984 | ||
|
|
942222131e | ||
|
|
4a4b04bec3 | ||
|
|
462f57c966 | ||
|
|
8d20c5d8b8 |
@@ -12,9 +12,9 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
versionCode 10502
|
||||
versionName "1.5.2"
|
||||
targetSdkVersion 33
|
||||
versionCode 10504
|
||||
versionName "1.5.4"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.PowerManager;
|
||||
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.battery.event.BatteryEvent;
|
||||
@@ -16,10 +17,17 @@ import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import static android.content.Intent.ACTION_BATTERY_CHANGED;
|
||||
import static android.content.Intent.ACTION_POWER_CONNECTED;
|
||||
import static android.content.Intent.ACTION_POWER_DISCONNECTED;
|
||||
import static android.os.BatteryManager.EXTRA_PLUGGED;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
|
||||
import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
|
||||
import static android.os.PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED;
|
||||
import static android.os.PowerManager.ACTION_POWER_SAVE_MODE_CHANGED;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
|
||||
@@ -57,6 +65,12 @@ class AndroidBatteryManager implements BatteryManager, Service {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_POWER_CONNECTED);
|
||||
filter.addAction(ACTION_POWER_DISCONNECTED);
|
||||
filter.addAction(ACTION_POWER_SAVE_MODE_CHANGED);
|
||||
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
|
||||
if (SDK_INT >= 33) {
|
||||
filter.addAction(ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
|
||||
filter.addAction(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED);
|
||||
}
|
||||
appContext.registerReceiver(batteryReceiver, filter);
|
||||
}
|
||||
|
||||
@@ -76,6 +90,33 @@ class AndroidBatteryManager implements BatteryManager, Service {
|
||||
eventBus.broadcast(new BatteryEvent(true));
|
||||
else if (ACTION_POWER_DISCONNECTED.equals(action))
|
||||
eventBus.broadcast(new BatteryEvent(false));
|
||||
else if (SDK_INT >= 23 &&
|
||||
ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action) &&
|
||||
LOG.isLoggable(INFO)) {
|
||||
LOG.info("Device idle mode changed to: " +
|
||||
getPowerManager(ctx).isDeviceIdleMode());
|
||||
} else if (SDK_INT >= 23 &&
|
||||
ACTION_POWER_SAVE_MODE_CHANGED.equals(action) &&
|
||||
LOG.isLoggable(INFO)) {
|
||||
LOG.info("Power save mode changed to: " +
|
||||
getPowerManager(ctx).isPowerSaveMode());
|
||||
} else if (SDK_INT >= 33 && LOG.isLoggable(INFO) &&
|
||||
ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED.equals(action)) {
|
||||
PowerManager powerManager =
|
||||
ctx.getSystemService(PowerManager.class);
|
||||
LOG.info("Low power standby now is: " +
|
||||
powerManager.isLowPowerStandbyEnabled());
|
||||
} else if (SDK_INT >= 33 && LOG.isLoggable(INFO) &&
|
||||
ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED.equals(action)) {
|
||||
PowerManager powerManager = getPowerManager(ctx);
|
||||
LOG.info("Light idle mode now is: " +
|
||||
powerManager.isDeviceLightIdleMode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = 23)
|
||||
private PowerManager getPowerManager(Context ctx) {
|
||||
return ctx.getSystemService(PowerManager.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,10 +63,12 @@ public class AndroidUtils {
|
||||
return new Pair<>(address, "adapter");
|
||||
}
|
||||
// Return the address from settings if it's valid and not fake
|
||||
address = Settings.Secure.getString(ctx.getContentResolver(),
|
||||
"bluetooth_address");
|
||||
if (isValidBluetoothAddress(address)) {
|
||||
return new Pair<>(address, "settings");
|
||||
if (SDK_INT < 33) {
|
||||
address = Settings.Secure.getString(ctx.getContentResolver(),
|
||||
"bluetooth_address");
|
||||
if (isValidBluetoothAddress(address)) {
|
||||
return new Pair<>(address, "settings");
|
||||
}
|
||||
}
|
||||
// Try to get the address via reflection
|
||||
address = getBluetoothAddressByReflection(adapter);
|
||||
|
||||
@@ -29,8 +29,8 @@ dependencyVerification {
|
||||
'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b',
|
||||
'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011',
|
||||
'org.briarproject:obfs4proxy-android:0.0.14-tor2:obfs4proxy-android-0.0.14-tor2.jar:a0a93770d6760ce57d9dbd31cc7177687374e00c3361dac22ab75e3b6e0f289e',
|
||||
'org.briarproject:onionwrapper-android:0.0.2:onionwrapper-android-0.0.2.aar:d196f1fe5d8b750866ea09d80509716aae7645d39b2c85433994718dbebeb4d1',
|
||||
'org.briarproject:onionwrapper-core:0.0.2:onionwrapper-core-0.0.2.jar:7038e960c9e59803f0e2c19444dbb5214cd99e5a7463c0a01c45318e07a0eb80',
|
||||
'org.briarproject:onionwrapper-android:0.0.4:onionwrapper-android-0.0.4.aar:d761854dac454616b3e0ca099b2cd17060365ce4316afe495cc7ae86b6c81d15',
|
||||
'org.briarproject:onionwrapper-core:0.0.4:onionwrapper-core-0.0.4.jar:28a01a62e96aa763989a8afc325abd3bee54f8021269f91aa48b247a6e717870',
|
||||
'org.briarproject:snowflake-android:2.5.1:snowflake-android-2.5.1.jar:88ec81c17b1b6fa884d06839dec0330e328b45c89f88c970a213ce91ca8eac87',
|
||||
'org.briarproject:tor-android:0.4.7.13-2:tor-android-0.4.7.13-2.jar:453fd463b234a2104edd7f0d02d0649cbb5c5efbe47a76df3828f55a3f90f8b5',
|
||||
'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a',
|
||||
|
||||
@@ -54,6 +54,38 @@ public interface CryptoComponent {
|
||||
KeyPair ourKeyPair, byte[]... inputs)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Derives a shared secret from two static and two ephemeral key pairs.
|
||||
* <p>
|
||||
* Do not use this method for new protocols. The shared secret can be
|
||||
* re-derived using the ephemeral public keys and both static private
|
||||
* keys, so keys derived from the shared secret should not be used if
|
||||
* forward secrecy is required. Use {@link #deriveSharedSecret(String,
|
||||
* PublicKey, PublicKey, KeyPair, KeyPair, boolean, byte[]...)} instead.
|
||||
* <p>
|
||||
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
|
||||
* <p>
|
||||
*
|
||||
* @param label A namespaced label indicating the purpose of this shared
|
||||
* secret, to prevent it from being repurposed or colliding with a shared
|
||||
* secret derived for another purpose
|
||||
* @param theirStaticPublicKey The static public key of the remote party
|
||||
* @param theirEphemeralPublicKey The ephemeral public key of the remote
|
||||
* party
|
||||
* @param ourStaticKeyPair The static key pair of the local party
|
||||
* @param ourEphemeralKeyPair The ephemeral key pair of the local party
|
||||
* @param alice True if the local party is Alice
|
||||
* @param inputs Additional inputs that will be included in the
|
||||
* derivation of the shared secret
|
||||
* @return The shared secret
|
||||
*/
|
||||
@Deprecated
|
||||
SecretKey deriveSharedSecretBadly(String label,
|
||||
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
|
||||
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
|
||||
boolean alice, byte[]... inputs)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Derives a shared secret from two static and two ephemeral key pairs.
|
||||
*
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.bramble.api.network;
|
||||
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@@ -27,4 +28,20 @@ public class NetworkStatus {
|
||||
public boolean isIpv6Only() {
|
||||
return ipv6Only;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (connected ? 1 : 0) | (wifi ? 2 : 0) | (ipv6Only ? 4 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (o instanceof NetworkStatus) {
|
||||
NetworkStatus s = (NetworkStatus) o;
|
||||
return connected == s.connected
|
||||
&& wifi == s.wifi
|
||||
&& ipv6Only == s.ipv6Only;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,15 @@ public interface RecordReader {
|
||||
* 'accept' or 'ignore' predicates
|
||||
*/
|
||||
@Nullable
|
||||
Record readRecord(Predicate<Record> accept, Predicate<Record> ignore)
|
||||
Record readRecord(RecordPredicate accept, RecordPredicate ignore)
|
||||
throws IOException;
|
||||
|
||||
void close() throws IOException;
|
||||
|
||||
/**
|
||||
* Interface that reifies the generic interface {@code Predicate<Record>}
|
||||
* for easier testing.
|
||||
*/
|
||||
interface RecordPredicate extends Predicate<Record> {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -51,7 +52,7 @@ public class PrivacyUtils {
|
||||
}
|
||||
|
||||
private static String scrubIpv6Address(byte[] ipv6) {
|
||||
String hex = toHexString(ipv6).toLowerCase();
|
||||
String hex = toHexString(ipv6).toLowerCase(Locale.US);
|
||||
return hex.substring(0, 2) + "[scrubbed]" + hex.substring(30);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Predicate;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
||||
@@ -24,6 +23,7 @@ import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
@@ -61,12 +61,12 @@ class ContactExchangeManagerImpl implements ContactExchangeManager {
|
||||
getLogger(ContactExchangeManagerImpl.class.getName());
|
||||
|
||||
// Accept records with current protocol version, known record type
|
||||
private static final Predicate<Record> ACCEPT = r ->
|
||||
private static final RecordPredicate ACCEPT = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
isKnownRecordType(r.getRecordType());
|
||||
|
||||
// Ignore records with current protocol version, unknown record type
|
||||
private static final Predicate<Record> IGNORE = r ->
|
||||
private static final RecordPredicate IGNORE = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
!isKnownRecordType(r.getRecordType());
|
||||
|
||||
|
||||
@@ -5,14 +5,31 @@ import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
|
||||
interface HandshakeConstants {
|
||||
|
||||
/**
|
||||
* The current version of the handshake protocol.
|
||||
* The current major version of the handshake protocol.
|
||||
*/
|
||||
byte PROTOCOL_VERSION = 0;
|
||||
byte PROTOCOL_MAJOR_VERSION = 0;
|
||||
|
||||
/**
|
||||
* Label for deriving the master key.
|
||||
* The current minor version of the handshake protocol.
|
||||
*/
|
||||
String MASTER_KEY_LABEL = "org.briarproject.bramble.handshake/MASTER_KEY";
|
||||
byte PROTOCOL_MINOR_VERSION = 1;
|
||||
|
||||
/**
|
||||
* Label for deriving the master key when using the deprecated v0.0 key
|
||||
* derivation method.
|
||||
* <p>
|
||||
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
|
||||
*/
|
||||
@Deprecated
|
||||
String MASTER_KEY_LABEL_0_0 =
|
||||
"org.briarproject.bramble.handshake/MASTER_KEY";
|
||||
|
||||
/**
|
||||
* Label for deriving the master key when using the v0.1 key derivation
|
||||
* method.
|
||||
*/
|
||||
String MASTER_KEY_LABEL_0_1 =
|
||||
"org.briarproject.bramble.handshake/MASTER_KEY_0_1";
|
||||
|
||||
/**
|
||||
* Label for deriving Alice's proof of ownership from the master key.
|
||||
|
||||
@@ -13,11 +13,26 @@ interface HandshakeCrypto {
|
||||
KeyPair generateEphemeralKeyPair();
|
||||
|
||||
/**
|
||||
* Derives the master key from the given static and ephemeral keys.
|
||||
* Derives the master key from the given static and ephemeral keys using
|
||||
* the deprecated v0.0 key derivation method.
|
||||
* <p>
|
||||
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
|
||||
*
|
||||
* @param alice Whether the local peer is Alice
|
||||
*/
|
||||
SecretKey deriveMasterKey(PublicKey theirStaticPublicKey,
|
||||
@Deprecated
|
||||
SecretKey deriveMasterKey_0_0(PublicKey theirStaticPublicKey,
|
||||
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||
KeyPair ourEphemeralKeyPair, boolean alice)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Derives the master key from the given static and ephemeral keys using
|
||||
* the v0.1 key derivation method.
|
||||
*
|
||||
* @param alice Whether the local peer is Alice
|
||||
*/
|
||||
SecretKey deriveMasterKey_0_1(PublicKey theirStaticPublicKey,
|
||||
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||
KeyPair ourEphemeralKeyPair, boolean alice)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
@@ -13,7 +13,8 @@ import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.ALICE_PROOF_LABEL;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.BOB_PROOF_LABEL;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL_0_0;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL_0_1;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -32,7 +33,8 @@ class HandshakeCryptoImpl implements HandshakeCrypto {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey deriveMasterKey(PublicKey theirStaticPublicKey,
|
||||
@Deprecated
|
||||
public SecretKey deriveMasterKey_0_0(PublicKey theirStaticPublicKey,
|
||||
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||
KeyPair ourEphemeralKeyPair, boolean alice) throws
|
||||
GeneralSecurityException {
|
||||
@@ -46,9 +48,29 @@ class HandshakeCryptoImpl implements HandshakeCrypto {
|
||||
alice ? ourEphemeral : theirEphemeral,
|
||||
alice ? theirEphemeral : ourEphemeral
|
||||
};
|
||||
return crypto.deriveSharedSecret(MASTER_KEY_LABEL, theirStaticPublicKey,
|
||||
theirEphemeralPublicKey, ourStaticKeyPair, ourEphemeralKeyPair,
|
||||
alice, inputs);
|
||||
return crypto.deriveSharedSecretBadly(MASTER_KEY_LABEL_0_0,
|
||||
theirStaticPublicKey, theirEphemeralPublicKey,
|
||||
ourStaticKeyPair, ourEphemeralKeyPair, alice, inputs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey deriveMasterKey_0_1(PublicKey theirStaticPublicKey,
|
||||
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||
KeyPair ourEphemeralKeyPair, boolean alice) throws
|
||||
GeneralSecurityException {
|
||||
byte[] theirStatic = theirStaticPublicKey.getEncoded();
|
||||
byte[] theirEphemeral = theirEphemeralPublicKey.getEncoded();
|
||||
byte[] ourStatic = ourStaticKeyPair.getPublic().getEncoded();
|
||||
byte[] ourEphemeral = ourEphemeralKeyPair.getPublic().getEncoded();
|
||||
byte[][] inputs = {
|
||||
alice ? ourStatic : theirStatic,
|
||||
alice ? theirStatic : ourStatic,
|
||||
alice ? ourEphemeral : theirEphemeral,
|
||||
alice ? theirEphemeral : ourEphemeral
|
||||
};
|
||||
return crypto.deriveSharedSecret(MASTER_KEY_LABEL_0_1,
|
||||
theirStaticPublicKey, theirEphemeralPublicKey,
|
||||
ourStaticKeyPair, ourEphemeralKeyPair, alice, inputs);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.briarproject.bramble.contact;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Pair;
|
||||
import org.briarproject.bramble.api.Predicate;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.contact.HandshakeManager;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
@@ -12,12 +11,12 @@ import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||
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.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
@@ -28,15 +27,20 @@ import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.List;
|
||||
|
||||
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.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_BYTES;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.EPHEMERAL_PUBLIC_KEY;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.PROOF_OF_OWNERSHIP;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MAJOR_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MINOR_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_EPHEMERAL_PUBLIC_KEY;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_MINOR_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_PROOF_OF_OWNERSHIP;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
|
||||
@Immutable
|
||||
@@ -44,12 +48,14 @@ import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
class HandshakeManagerImpl implements HandshakeManager {
|
||||
|
||||
// Ignore records with current protocol version, unknown record type
|
||||
private static final Predicate<Record> IGNORE = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
private static final RecordPredicate IGNORE = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_MAJOR_VERSION &&
|
||||
!isKnownRecordType(r.getRecordType());
|
||||
|
||||
private static boolean isKnownRecordType(byte type) {
|
||||
return type == EPHEMERAL_PUBLIC_KEY || type == PROOF_OF_OWNERSHIP;
|
||||
return type == RECORD_TYPE_EPHEMERAL_PUBLIC_KEY ||
|
||||
type == RECORD_TYPE_PROOF_OF_OWNERSHIP ||
|
||||
type == RECORD_TYPE_MINOR_VERSION;
|
||||
}
|
||||
|
||||
private final TransactionManager db;
|
||||
@@ -61,7 +67,7 @@ class HandshakeManagerImpl implements HandshakeManager {
|
||||
private final RecordWriterFactory recordWriterFactory;
|
||||
|
||||
@Inject
|
||||
HandshakeManagerImpl(DatabaseComponent db,
|
||||
HandshakeManagerImpl(TransactionManager db,
|
||||
IdentityManager identityManager,
|
||||
ContactManager contactManager,
|
||||
TransportCrypto transportCrypto,
|
||||
@@ -95,19 +101,31 @@ class HandshakeManagerImpl implements HandshakeManager {
|
||||
.createRecordWriter(out.getOutputStream());
|
||||
KeyPair ourEphemeralKeyPair =
|
||||
handshakeCrypto.generateEphemeralKeyPair();
|
||||
PublicKey theirEphemeralPublicKey;
|
||||
Pair<Byte, PublicKey> theirMinorVersionAndKey;
|
||||
if (alice) {
|
||||
sendMinorVersion(recordWriter);
|
||||
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
|
||||
theirEphemeralPublicKey = receivePublicKey(recordReader);
|
||||
theirMinorVersionAndKey = receiveMinorVersionAndKey(recordReader);
|
||||
} else {
|
||||
theirEphemeralPublicKey = receivePublicKey(recordReader);
|
||||
theirMinorVersionAndKey = receiveMinorVersionAndKey(recordReader);
|
||||
sendMinorVersion(recordWriter);
|
||||
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
|
||||
}
|
||||
byte theirMinorVersion = theirMinorVersionAndKey.getFirst();
|
||||
PublicKey theirEphemeralPublicKey = theirMinorVersionAndKey.getSecond();
|
||||
SecretKey masterKey;
|
||||
try {
|
||||
masterKey = handshakeCrypto.deriveMasterKey(theirStaticPublicKey,
|
||||
theirEphemeralPublicKey, ourStaticKeyPair,
|
||||
ourEphemeralKeyPair, alice);
|
||||
if (theirMinorVersion > 0) {
|
||||
masterKey = handshakeCrypto.deriveMasterKey_0_1(
|
||||
theirStaticPublicKey, theirEphemeralPublicKey,
|
||||
ourStaticKeyPair, ourEphemeralKeyPair, alice);
|
||||
} else {
|
||||
// TODO: Remove this branch after a reasonable migration
|
||||
// period (added 2023-03-10).
|
||||
masterKey = handshakeCrypto.deriveMasterKey_0_0(
|
||||
theirStaticPublicKey, theirEphemeralPublicKey,
|
||||
ourStaticKeyPair, ourEphemeralKeyPair, alice);
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new FormatException();
|
||||
}
|
||||
@@ -128,34 +146,91 @@ class HandshakeManagerImpl implements HandshakeManager {
|
||||
}
|
||||
|
||||
private void sendPublicKey(RecordWriter w, PublicKey k) throws IOException {
|
||||
w.writeRecord(new Record(PROTOCOL_VERSION, EPHEMERAL_PUBLIC_KEY,
|
||||
k.getEncoded()));
|
||||
w.writeRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY, k.getEncoded()));
|
||||
w.flush();
|
||||
}
|
||||
|
||||
private PublicKey receivePublicKey(RecordReader r) throws IOException {
|
||||
byte[] key = readRecord(r, EPHEMERAL_PUBLIC_KEY).getPayload();
|
||||
/**
|
||||
* Receives the remote peer's protocol minor version and ephemeral public
|
||||
* key.
|
||||
* <p>
|
||||
* In version 0.1 of the protocol, each peer sends a minor version record
|
||||
* followed by an ephemeral public key record.
|
||||
* <p>
|
||||
* In version 0.0 of the protocol, each peer sends an ephemeral public key
|
||||
* record without a preceding minor version record.
|
||||
* <p>
|
||||
* Therefore the remote peer's minor version must be non-zero if a minor
|
||||
* version record is received, and is assumed to be zero if no minor
|
||||
* version record is received.
|
||||
*/
|
||||
private Pair<Byte, PublicKey> receiveMinorVersionAndKey(RecordReader r)
|
||||
throws IOException {
|
||||
byte theirMinorVersion;
|
||||
PublicKey theirEphemeralPublicKey;
|
||||
// The first record can be either a minor version record or an
|
||||
// ephemeral public key record
|
||||
Record first = readRecord(r, asList(RECORD_TYPE_MINOR_VERSION,
|
||||
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY));
|
||||
if (first.getRecordType() == RECORD_TYPE_MINOR_VERSION) {
|
||||
// The payload must be a single byte giving the remote peer's
|
||||
// protocol minor version, which must be non-zero
|
||||
byte[] payload = first.getPayload();
|
||||
checkLength(payload, 1);
|
||||
theirMinorVersion = payload[0];
|
||||
if (theirMinorVersion == 0) throw new FormatException();
|
||||
// The second record must be an ephemeral public key record
|
||||
Record second = readRecord(r,
|
||||
singletonList(RECORD_TYPE_EPHEMERAL_PUBLIC_KEY));
|
||||
theirEphemeralPublicKey = parsePublicKey(second);
|
||||
} else {
|
||||
// The remote peer did not send a minor version record, so the
|
||||
// remote peer's protocol minor version is assumed to be zero
|
||||
// TODO: Remove this branch after a reasonable migration period
|
||||
// (added 2023-03-10).
|
||||
theirMinorVersion = 0;
|
||||
theirEphemeralPublicKey = parsePublicKey(first);
|
||||
}
|
||||
return new Pair<>(theirMinorVersion, theirEphemeralPublicKey);
|
||||
}
|
||||
|
||||
private PublicKey parsePublicKey(Record rec) throws IOException {
|
||||
if (rec.getRecordType() != RECORD_TYPE_EPHEMERAL_PUBLIC_KEY) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
byte[] key = rec.getPayload();
|
||||
checkLength(key, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES);
|
||||
return new AgreementPublicKey(key);
|
||||
}
|
||||
|
||||
private void sendProof(RecordWriter w, byte[] proof) throws IOException {
|
||||
w.writeRecord(new Record(PROTOCOL_VERSION, PROOF_OF_OWNERSHIP, proof));
|
||||
w.writeRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_PROOF_OF_OWNERSHIP, proof));
|
||||
w.flush();
|
||||
}
|
||||
|
||||
private byte[] receiveProof(RecordReader r) throws IOException {
|
||||
byte[] proof = readRecord(r, PROOF_OF_OWNERSHIP).getPayload();
|
||||
Record rec = readRecord(r,
|
||||
singletonList(RECORD_TYPE_PROOF_OF_OWNERSHIP));
|
||||
byte[] proof = rec.getPayload();
|
||||
checkLength(proof, PROOF_BYTES, PROOF_BYTES);
|
||||
return proof;
|
||||
}
|
||||
|
||||
private Record readRecord(RecordReader r, byte expectedType)
|
||||
private void sendMinorVersion(RecordWriter w) throws IOException {
|
||||
w.writeRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_MINOR_VERSION,
|
||||
new byte[] {PROTOCOL_MINOR_VERSION}));
|
||||
w.flush();
|
||||
}
|
||||
|
||||
private Record readRecord(RecordReader r, List<Byte> expectedTypes)
|
||||
throws IOException {
|
||||
// Accept records with current protocol version, expected type only
|
||||
Predicate<Record> accept = rec ->
|
||||
rec.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
rec.getRecordType() == expectedType;
|
||||
// Accept records with current protocol version, expected types only
|
||||
RecordPredicate accept = rec ->
|
||||
rec.getProtocolVersion() == PROTOCOL_MAJOR_VERSION &&
|
||||
expectedTypes.contains(rec.getRecordType());
|
||||
Record rec = r.readRecord(accept, IGNORE);
|
||||
if (rec == null) throw new EOFException();
|
||||
return rec;
|
||||
|
||||
@@ -5,7 +5,9 @@ package org.briarproject.bramble.contact;
|
||||
*/
|
||||
interface HandshakeRecordTypes {
|
||||
|
||||
byte EPHEMERAL_PUBLIC_KEY = 0;
|
||||
byte RECORD_TYPE_EPHEMERAL_PUBLIC_KEY = 0;
|
||||
|
||||
byte PROOF_OF_OWNERSHIP = 1;
|
||||
byte RECORD_TYPE_PROOF_OF_OWNERSHIP = 1;
|
||||
|
||||
byte RECORD_TYPE_MINOR_VERSION = 2;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -222,7 +223,8 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey deriveSharedSecret(String label,
|
||||
@Deprecated
|
||||
public SecretKey deriveSharedSecretBadly(String label,
|
||||
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
|
||||
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
|
||||
boolean alice, byte[]... inputs) throws GeneralSecurityException {
|
||||
@@ -250,6 +252,35 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return new SecretKey(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey deriveSharedSecret(String label,
|
||||
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
|
||||
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
|
||||
boolean alice, byte[]... inputs) throws GeneralSecurityException {
|
||||
PrivateKey ourStaticPrivateKey = ourStaticKeyPair.getPrivate();
|
||||
PrivateKey ourEphemeralPrivateKey = ourEphemeralKeyPair.getPrivate();
|
||||
byte[][] hashInputs = new byte[inputs.length + 3][];
|
||||
// Alice ephemeral/Bob ephemeral
|
||||
hashInputs[0] = performRawKeyAgreement(ourEphemeralPrivateKey,
|
||||
theirEphemeralPublicKey);
|
||||
// Alice static/Bob ephemeral, Bob static/Alice ephemeral
|
||||
if (alice) {
|
||||
hashInputs[1] = performRawKeyAgreement(ourStaticPrivateKey,
|
||||
theirEphemeralPublicKey);
|
||||
hashInputs[2] = performRawKeyAgreement(ourEphemeralPrivateKey,
|
||||
theirStaticPublicKey);
|
||||
} else {
|
||||
hashInputs[1] = performRawKeyAgreement(ourEphemeralPrivateKey,
|
||||
theirStaticPublicKey);
|
||||
hashInputs[2] = performRawKeyAgreement(ourStaticPrivateKey,
|
||||
theirEphemeralPublicKey);
|
||||
}
|
||||
arraycopy(inputs, 0, hashInputs, 3, inputs.length);
|
||||
byte[] hash = hash(label, hashInputs);
|
||||
if (hash.length != SecretKey.LENGTH) throw new IllegalStateException();
|
||||
return new SecretKey(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] sign(String label, byte[] toSign, PrivateKey privateKey)
|
||||
throws GeneralSecurityException {
|
||||
@@ -470,7 +501,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
arraycopy(publicKey, 0, address, 0, publicKey.length);
|
||||
arraycopy(checksum, 0, address, publicKey.length, ONION_CHECKSUM_BYTES);
|
||||
address[address.length - 1] = ONION_HS_PROTOCOL_VERSION;
|
||||
return Base32.encode(address).toLowerCase();
|
||||
return Base32.encode(address).toLowerCase(Locale.US);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.briarproject.bramble.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.Predicate;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
@@ -34,12 +34,12 @@ class KeyAgreementTransport {
|
||||
Logger.getLogger(KeyAgreementTransport.class.getName());
|
||||
|
||||
// Accept records with current protocol version, known record type
|
||||
private static final Predicate<Record> ACCEPT = r ->
|
||||
private static final RecordPredicate ACCEPT = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
isKnownRecordType(r.getRecordType());
|
||||
|
||||
// Ignore records with current protocol version, unknown record type
|
||||
private static final Predicate<Record> IGNORE = r ->
|
||||
private static final RecordPredicate IGNORE = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
!isKnownRecordType(r.getRecordType());
|
||||
|
||||
|
||||
@@ -311,6 +311,9 @@ class TorPlugin implements DuplexPlugin, EventListener {
|
||||
tor.stop();
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while stopping Tor");
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,12 +628,23 @@ class TorPlugin implements DuplexPlugin, EventListener {
|
||||
}
|
||||
|
||||
private synchronized State getState(TorState torState) {
|
||||
if (torState == TorState.STARTING_STOPPING || !settingsChecked) {
|
||||
// Treat TorState.STARTED as State.STARTING_STOPPING because it's
|
||||
// only seen during startup, before TorWrapper#enableNetwork() is
|
||||
// called for the first time. TorState.NOT_STARTED and
|
||||
// TorState.STOPPED are mapped to State.STARTING_STOPPING because
|
||||
// that's the State before we've started and after we've stopped.
|
||||
if (torState == TorState.NOT_STARTED ||
|
||||
torState == TorState.STARTING ||
|
||||
torState == TorState.STARTED ||
|
||||
torState == TorState.STOPPING ||
|
||||
torState == TorState.STOPPED ||
|
||||
!settingsChecked) {
|
||||
return STARTING_STOPPING;
|
||||
}
|
||||
if (reasonsDisabled != 0) return DISABLED;
|
||||
if (torState == TorState.CONNECTING) return ENABLING;
|
||||
if (torState == TorState.CONNECTED) return ACTIVE;
|
||||
// The plugin is enabled in settings but the device is offline
|
||||
return INACTIVE;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble.record;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Predicate;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
@@ -45,7 +44,7 @@ class RecordReaderImpl implements RecordReader {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Record readRecord(Predicate<Record> accept, Predicate<Record> ignore)
|
||||
public Record readRecord(RecordPredicate accept, RecordPredicate ignore)
|
||||
throws IOException {
|
||||
while (true) {
|
||||
if (eof()) return null;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Predicate;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
@@ -41,12 +41,12 @@ import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||
class SyncRecordReaderImpl implements SyncRecordReader {
|
||||
|
||||
// Accept records with current protocol version, known record type
|
||||
private static final Predicate<Record> ACCEPT = r ->
|
||||
private static final RecordPredicate ACCEPT = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
isKnownRecordType(r.getRecordType());
|
||||
|
||||
// Ignore records with current protocol version, unknown record type
|
||||
private static final Predicate<Record> IGNORE = r ->
|
||||
private static final RecordPredicate IGNORE = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
!isKnownRecordType(r.getRecordType());
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ 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.MessageStatus;
|
||||
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.versioning.ClientMajorVersion;
|
||||
@@ -36,17 +37,20 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||
@@ -58,6 +62,9 @@ import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_
|
||||
class ClientVersioningManagerImpl implements ClientVersioningManager,
|
||||
Service, OpenDatabaseHook, ContactHook, IncomingMessageHook {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ClientVersioningManagerImpl.class.getName());
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
private final ContactGroupFactory contactGroupFactory;
|
||||
@@ -128,12 +135,68 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
||||
|
||||
@Override
|
||||
public void onDatabaseOpened(Transaction txn) throws DbException {
|
||||
LOG.info("onDatabaseOpened " + localGroup.getId());
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
try {
|
||||
// FIXME: DO NOT MERGE, this logs the contact name and alias
|
||||
LOG.info(String.format(Locale.US,
|
||||
"find latest updates for %d: %s (%s)",
|
||||
c.getId().getInt(),
|
||||
c.getAuthor().getName(),
|
||||
c.getAlias()));
|
||||
LatestUpdates latestUpdates = findLatestUpdates(txn, c.getId());
|
||||
if (latestUpdates == null) {
|
||||
LOG.info("none found");
|
||||
} else {
|
||||
if (latestUpdates.local != null) {
|
||||
MessageStatus status = db.getMessageStatus(txn,
|
||||
c.getId(), latestUpdates.local.messageId);
|
||||
LOG.info(String.format(Locale.US,
|
||||
"local: %s; sent: %b; seen: %b%n",
|
||||
latestUpdates.local.messageId,
|
||||
status.isSent(),
|
||||
status.isSeen()));
|
||||
Update update =
|
||||
loadUpdate(txn, latestUpdates.local.messageId);
|
||||
printUpdate(update);
|
||||
}
|
||||
if (latestUpdates.remote != null) {
|
||||
MessageStatus status = db.getMessageStatus(txn,
|
||||
c.getId(), latestUpdates.remote.messageId);
|
||||
LOG.info(String.format(Locale.US,
|
||||
"remote: %s; sent: %b; seen: %b%n",
|
||||
latestUpdates.remote.messageId,
|
||||
status.isSent(),
|
||||
status.isSeen()));
|
||||
Update update =
|
||||
loadUpdate(txn, latestUpdates.remote.messageId);
|
||||
printUpdate(update);
|
||||
}
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
private void printUpdate(Update update) {
|
||||
LOG.info(String.format(Locale.US, "update version: %d%n",
|
||||
update.updateVersion));
|
||||
for (ClientState state : update.states) {
|
||||
LOG.info(String.format(Locale.US,
|
||||
"id: %s, major: %d, minor: %d, active: %b, %n",
|
||||
state.clientVersion.getClientId().getString(),
|
||||
state.clientVersion.getClientMajorVersion()
|
||||
.getMajorVersion(),
|
||||
state.clientVersion.getMinorVersion(),
|
||||
state.active));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startService() throws ServiceException {
|
||||
List<ClientVersion> versions = new ArrayList<>(clients);
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.contact.HandshakeManager.HandshakeResult;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.briarproject.bramble.test.PredicateMatcher;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_BYTES;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MAJOR_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MINOR_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_EPHEMERAL_PUBLIC_KEY;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_MINOR_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_PROOF_OF_OWNERSHIP;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getPendingContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class HandshakeManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final TransactionManager db =
|
||||
context.mock(TransactionManager.class);
|
||||
private final IdentityManager identityManager =
|
||||
context.mock(IdentityManager.class);
|
||||
private final ContactManager contactManager =
|
||||
context.mock(ContactManager.class);
|
||||
private final TransportCrypto transportCrypto =
|
||||
context.mock(TransportCrypto.class);
|
||||
private final HandshakeCrypto handshakeCrypto =
|
||||
context.mock(HandshakeCrypto.class);
|
||||
private final RecordReaderFactory recordReaderFactory =
|
||||
context.mock(RecordReaderFactory.class);
|
||||
private final RecordWriterFactory recordWriterFactory =
|
||||
context.mock(RecordWriterFactory.class);
|
||||
private final RecordReader recordReader = context.mock(RecordReader.class);
|
||||
private final RecordWriter recordWriter = context.mock(RecordWriter.class);
|
||||
private final StreamWriter streamWriter = context.mock(StreamWriter.class);
|
||||
|
||||
private final PendingContact pendingContact = getPendingContact();
|
||||
private final PublicKey theirStaticPublicKey =
|
||||
pendingContact.getPublicKey();
|
||||
private final PublicKey ourStaticPublicKey = getAgreementPublicKey();
|
||||
private final PrivateKey ourStaticPrivateKey = getAgreementPrivateKey();
|
||||
private final KeyPair ourStaticKeyPair =
|
||||
new KeyPair(ourStaticPublicKey, ourStaticPrivateKey);
|
||||
private final PublicKey theirEphemeralPublicKey = getAgreementPublicKey();
|
||||
private final PublicKey ourEphemeralPublicKey = getAgreementPublicKey();
|
||||
private final PrivateKey ourEphemeralPrivateKey = getAgreementPrivateKey();
|
||||
private final KeyPair ourEphemeralKeyPair =
|
||||
new KeyPair(ourEphemeralPublicKey, ourEphemeralPrivateKey);
|
||||
private final SecretKey masterKey = getSecretKey();
|
||||
private final byte[] ourProof = getRandomBytes(PROOF_BYTES);
|
||||
private final byte[] theirProof = getRandomBytes(PROOF_BYTES);
|
||||
|
||||
private final InputStream in = new ByteArrayInputStream(new byte[0]);
|
||||
private final OutputStream out = new ByteArrayOutputStream(0);
|
||||
|
||||
private final HandshakeManagerImpl handshakeManager =
|
||||
new HandshakeManagerImpl(db, identityManager, contactManager,
|
||||
transportCrypto, handshakeCrypto, recordReaderFactory,
|
||||
recordWriterFactory);
|
||||
|
||||
@Test
|
||||
public void testHandshakeAsAliceWithPeerVersion_0_1() throws Exception {
|
||||
testHandshakeWithPeerVersion_0_1(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandshakeAsBobWithPeerVersion_0_1() throws Exception {
|
||||
testHandshakeWithPeerVersion_0_1(false);
|
||||
}
|
||||
|
||||
private void testHandshakeWithPeerVersion_0_1(boolean alice)
|
||||
throws Exception {
|
||||
expectPrepareForHandshake(alice);
|
||||
expectSendMinorVersion();
|
||||
expectSendKey();
|
||||
// Remote peer sends minor version, so use new key derivation
|
||||
expectReceiveMinorVersion();
|
||||
expectReceiveKey();
|
||||
expectDeriveMasterKey_0_1(alice);
|
||||
expectDeriveProof(alice);
|
||||
expectSendProof();
|
||||
expectReceiveProof();
|
||||
expectSendEof();
|
||||
expectReceiveEof();
|
||||
expectVerifyOwnership(alice, true);
|
||||
|
||||
HandshakeResult result = handshakeManager.handshake(
|
||||
pendingContact.getId(), in, streamWriter);
|
||||
|
||||
assertArrayEquals(masterKey.getBytes(),
|
||||
result.getMasterKey().getBytes());
|
||||
assertEquals(alice, result.isAlice());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandshakeAsAliceWithPeerVersion_0_0() throws Exception {
|
||||
testHandshakeWithPeerVersion_0_0(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandshakeAsBobWithPeerVersion_0_0() throws Exception {
|
||||
testHandshakeWithPeerVersion_0_0(false);
|
||||
}
|
||||
|
||||
private void testHandshakeWithPeerVersion_0_0(boolean alice)
|
||||
throws Exception {
|
||||
expectPrepareForHandshake(alice);
|
||||
expectSendMinorVersion();
|
||||
expectSendKey();
|
||||
// Remote peer does not send minor version, so use old key derivation
|
||||
expectReceiveKey();
|
||||
expectDeriveMasterKey_0_0(alice);
|
||||
expectDeriveProof(alice);
|
||||
expectSendProof();
|
||||
expectReceiveProof();
|
||||
expectSendEof();
|
||||
expectReceiveEof();
|
||||
expectVerifyOwnership(alice, true);
|
||||
|
||||
HandshakeResult result = handshakeManager.handshake(
|
||||
pendingContact.getId(), in, streamWriter);
|
||||
|
||||
assertArrayEquals(masterKey.getBytes(),
|
||||
result.getMasterKey().getBytes());
|
||||
assertEquals(alice, result.isAlice());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testProofOfOwnershipNotVerifiedAsAlice() throws Exception {
|
||||
testProofOfOwnershipNotVerified(true);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testProofOfOwnershipNotVerifiedAsBob() throws Exception {
|
||||
testProofOfOwnershipNotVerified(false);
|
||||
}
|
||||
|
||||
private void testProofOfOwnershipNotVerified(boolean alice)
|
||||
throws Exception {
|
||||
expectPrepareForHandshake(alice);
|
||||
expectSendMinorVersion();
|
||||
expectSendKey();
|
||||
expectReceiveMinorVersion();
|
||||
expectReceiveKey();
|
||||
expectDeriveMasterKey_0_1(alice);
|
||||
expectDeriveProof(alice);
|
||||
expectSendProof();
|
||||
expectReceiveProof();
|
||||
expectSendEof();
|
||||
expectReceiveEof();
|
||||
expectVerifyOwnership(alice, false);
|
||||
|
||||
handshakeManager.handshake(pendingContact.getId(), in, streamWriter);
|
||||
}
|
||||
|
||||
private void expectPrepareForHandshake(boolean alice) throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||
oneOf(contactManager).getPendingContact(txn,
|
||||
pendingContact.getId());
|
||||
will(returnValue(pendingContact));
|
||||
oneOf(identityManager).getHandshakeKeys(txn);
|
||||
will(returnValue(ourStaticKeyPair));
|
||||
oneOf(transportCrypto).isAlice(theirStaticPublicKey,
|
||||
ourStaticKeyPair);
|
||||
will(returnValue(alice));
|
||||
oneOf(recordReaderFactory).createRecordReader(in);
|
||||
will(returnValue(recordReader));
|
||||
oneOf(streamWriter).getOutputStream();
|
||||
will(returnValue(out));
|
||||
oneOf(recordWriterFactory).createRecordWriter(out);
|
||||
will(returnValue(recordWriter));
|
||||
oneOf(handshakeCrypto).generateEphemeralKeyPair();
|
||||
will(returnValue(ourEphemeralKeyPair));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectSendMinorVersion() throws Exception {
|
||||
expectWriteRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_MINOR_VERSION,
|
||||
new byte[] {PROTOCOL_MINOR_VERSION}));
|
||||
}
|
||||
|
||||
private void expectReceiveMinorVersion() throws Exception {
|
||||
expectReadRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_MINOR_VERSION,
|
||||
new byte[] {PROTOCOL_MINOR_VERSION}));
|
||||
}
|
||||
|
||||
private void expectSendKey() throws Exception {
|
||||
expectWriteRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY,
|
||||
ourEphemeralPublicKey.getEncoded()));
|
||||
}
|
||||
|
||||
private void expectReceiveKey() throws Exception {
|
||||
expectReadRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY,
|
||||
theirEphemeralPublicKey.getEncoded()));
|
||||
}
|
||||
|
||||
private void expectDeriveMasterKey_0_1(boolean alice) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(handshakeCrypto).deriveMasterKey_0_1(theirStaticPublicKey,
|
||||
theirEphemeralPublicKey, ourStaticKeyPair,
|
||||
ourEphemeralKeyPair, alice);
|
||||
will(returnValue(masterKey));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectDeriveMasterKey_0_0(boolean alice) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(handshakeCrypto).deriveMasterKey_0_0(theirStaticPublicKey,
|
||||
theirEphemeralPublicKey, ourStaticKeyPair,
|
||||
ourEphemeralKeyPair, alice);
|
||||
will(returnValue(masterKey));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectDeriveProof(boolean alice) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(handshakeCrypto).proveOwnership(masterKey, alice);
|
||||
will(returnValue(ourProof));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectSendProof() throws Exception {
|
||||
expectWriteRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_PROOF_OF_OWNERSHIP, ourProof));
|
||||
}
|
||||
|
||||
private void expectReceiveProof() throws Exception {
|
||||
expectReadRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_PROOF_OF_OWNERSHIP, theirProof));
|
||||
}
|
||||
|
||||
private void expectSendEof() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(streamWriter).sendEndOfStream();
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectReceiveEof() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord(with(any(RecordPredicate.class)),
|
||||
with(any(RecordPredicate.class)));
|
||||
will(returnValue(null));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectVerifyOwnership(boolean alice, boolean verified) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(handshakeCrypto).verifyOwnership(masterKey, !alice,
|
||||
theirProof);
|
||||
will(returnValue(verified));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectWriteRecord(Record record) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordWriter).writeRecord(with(new PredicateMatcher<>(
|
||||
Record.class, r -> recordEquals(record, r))));
|
||||
oneOf(recordWriter).flush();
|
||||
}});
|
||||
}
|
||||
|
||||
private boolean recordEquals(Record expected, Record actual) {
|
||||
return expected.getProtocolVersion() == actual.getProtocolVersion() &&
|
||||
expected.getRecordType() == actual.getRecordType() &&
|
||||
Arrays.equals(expected.getPayload(), actual.getPayload());
|
||||
}
|
||||
|
||||
private void expectReadRecord(Record record) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// Test that the `accept` predicate passed to the reader would
|
||||
// accept the expected record
|
||||
oneOf(recordReader).readRecord(with(new PredicateMatcher<>(
|
||||
RecordPredicate.class, rp -> rp.test(record))),
|
||||
with(any(RecordPredicate.class)));
|
||||
will(returnValue(record));
|
||||
}});
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Locale;
|
||||
|
||||
import static java.lang.System.arraycopy;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES;
|
||||
@@ -174,7 +175,7 @@ public class PendingContactFactoryImplTest extends BrambleMockTestCase {
|
||||
rawLink[0] = (byte) formatVersion;
|
||||
byte[] publicKeyBytes = publicKey.getEncoded();
|
||||
arraycopy(publicKeyBytes, 0, rawLink, 1, publicKeyBytes.length);
|
||||
String base32 = Base32.encode(rawLink).toLowerCase();
|
||||
String base32 = Base32.encode(rawLink).toLowerCase(Locale.US);
|
||||
assertEquals(BASE32_LINK_BYTES, base32.length());
|
||||
return base32;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,22 @@ public class KeyAgreementTest extends BrambleTestCase {
|
||||
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDerivesStaticEphemeralSharedSecretBadly() throws Exception {
|
||||
String label = getRandomString(123);
|
||||
KeyPair aStatic = crypto.generateAgreementKeyPair();
|
||||
KeyPair aEphemeral = crypto.generateAgreementKeyPair();
|
||||
KeyPair bStatic = crypto.generateAgreementKeyPair();
|
||||
KeyPair bEphemeral = crypto.generateAgreementKeyPair();
|
||||
SecretKey aShared = crypto.deriveSharedSecretBadly(label,
|
||||
bStatic.getPublic(), bEphemeral.getPublic(), aStatic,
|
||||
aEphemeral, true, inputs);
|
||||
SecretKey bShared = crypto.deriveSharedSecretBadly(label,
|
||||
aStatic.getPublic(), aEphemeral.getPublic(), bStatic,
|
||||
bEphemeral, false, inputs);
|
||||
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDerivesStaticEphemeralSharedSecret() throws Exception {
|
||||
String label = getRandomString(123);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class OnionEncodingTest extends BrambleTestCase {
|
||||
|
||||
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
|
||||
|
||||
private final CryptoComponent crypto =
|
||||
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
|
||||
private final SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
@Test
|
||||
public void testHostnameIsValid() {
|
||||
byte[] publicKey = new byte[32];
|
||||
for (int i = 0; i < 100; i++) {
|
||||
secureRandom.nextBytes(publicKey);
|
||||
String onion = crypto.encodeOnion(publicKey);
|
||||
assertTrue(onion, ONION_V3.matcher(onion).matches());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.bramble.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.Predicate;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
@@ -8,11 +7,13 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||
import org.briarproject.bramble.test.PredicateMatcher;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.imposters.ByteBuddyClassImposteriser;
|
||||
import org.junit.Test;
|
||||
@@ -21,8 +22,6 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT;
|
||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.CONFIRM;
|
||||
@@ -119,7 +118,7 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
|
||||
public void testReceiveKeyThrowsExceptionIfAtEndOfStream()
|
||||
throws Exception {
|
||||
setup();
|
||||
expectReadRecord(null);
|
||||
expectReadEof();
|
||||
|
||||
kat.receiveKey();
|
||||
}
|
||||
@@ -148,7 +147,7 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
|
||||
public void testReceiveConfirmThrowsExceptionIfAtEndOfStream()
|
||||
throws Exception {
|
||||
setup();
|
||||
expectReadRecord(null);
|
||||
expectReadEof();
|
||||
|
||||
kat.receiveConfirm();
|
||||
}
|
||||
@@ -209,12 +208,22 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
|
||||
assertArrayEquals(expectedPayload, actual.getPayload());
|
||||
}
|
||||
|
||||
private void expectReadRecord(@Nullable Record record) throws Exception {
|
||||
private void expectReadRecord(Record record) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
//noinspection unchecked
|
||||
oneOf(recordReader).readRecord(with(any(Predicate.class)),
|
||||
with(any(Predicate.class)));
|
||||
// Test that the `accept` predicate passed to the reader would
|
||||
// accept the expected record
|
||||
oneOf(recordReader).readRecord(with(new PredicateMatcher<>(
|
||||
RecordPredicate.class, rp -> rp.test(record))),
|
||||
with(any(RecordPredicate.class)));
|
||||
will(returnValue(record));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectReadEof() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord(with(any(RecordPredicate.class)),
|
||||
with(any(RecordPredicate.class)));
|
||||
will(returnValue(null));
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.briarproject.bramble.record;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Predicate;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
import org.junit.Test;
|
||||
@@ -128,12 +128,12 @@ public class RecordReaderImplTest extends BrambleTestCase {
|
||||
RecordReader reader = new RecordReaderImpl(in);
|
||||
|
||||
// Accept records with version 0, type 0 or 1
|
||||
Predicate<Record> accept = r -> {
|
||||
RecordPredicate accept = r -> {
|
||||
byte version = r.getProtocolVersion(), type = r.getRecordType();
|
||||
return version == 0 && (type == 0 || type == 1);
|
||||
};
|
||||
// Ignore records with version 0, any other type
|
||||
Predicate<Record> ignore = r -> {
|
||||
RecordPredicate ignore = r -> {
|
||||
byte version = r.getProtocolVersion(), type = r.getRecordType();
|
||||
return version == 0 && !(type == 0 || type == 1);
|
||||
};
|
||||
@@ -183,12 +183,12 @@ public class RecordReaderImplTest extends BrambleTestCase {
|
||||
RecordReader reader = new RecordReaderImpl(in);
|
||||
|
||||
// Accept records with version 0, type 0 or 1
|
||||
Predicate<Record> accept = r -> {
|
||||
RecordPredicate accept = r -> {
|
||||
byte version = r.getProtocolVersion(), type = r.getRecordType();
|
||||
return version == 0 && (type == 0 || type == 1);
|
||||
};
|
||||
// Ignore records with version 0, any other type
|
||||
Predicate<Record> ignore = r -> {
|
||||
RecordPredicate ignore = r -> {
|
||||
byte version = r.getProtocolVersion(), type = r.getRecordType();
|
||||
return version == 0 && !(type == 0 || type == 1);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Predicate;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
@@ -24,8 +24,6 @@ import org.junit.Test;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
|
||||
@@ -186,7 +184,7 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testEofReturnsTrueWhenAtEndOfStream() throws Exception {
|
||||
expectReadRecord(createAck());
|
||||
expectReadRecord(null);
|
||||
expectReadEof();
|
||||
|
||||
SyncRecordReader reader =
|
||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
||||
@@ -212,15 +210,25 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectReadRecord(@Nullable Record record) throws Exception {
|
||||
private void expectReadRecord(Record record) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
//noinspection unchecked
|
||||
oneOf(recordReader).readRecord(with(any(Predicate.class)),
|
||||
with(any(Predicate.class)));
|
||||
// Test that the `accept` predicate passed to the reader would
|
||||
// accept the expected record
|
||||
oneOf(recordReader).readRecord(with(new PredicateMatcher<>(
|
||||
RecordPredicate.class, rp -> rp.test(record))),
|
||||
with(any(RecordPredicate.class)));
|
||||
will(returnValue(record));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectReadEof() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord(with(any(RecordPredicate.class)),
|
||||
with(any(RecordPredicate.class)));
|
||||
will(returnValue(null));
|
||||
}});
|
||||
}
|
||||
|
||||
private Record createMessage(int payloadLength) {
|
||||
return new Record(PROTOCOL_VERSION, MESSAGE, new byte[payloadLength]);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ dependencyVerification {
|
||||
'org.bouncycastle:bcprov-jdk15to18:1.71:bcprov-jdk15to18-1.71.jar:143aaa4a40edd5fc2a18db7900059f6c16f4d931b94b94b20f7e2238e6662886',
|
||||
'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b',
|
||||
'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011',
|
||||
'org.briarproject:onionwrapper-core:0.0.2:onionwrapper-core-0.0.2.jar:7038e960c9e59803f0e2c19444dbb5214cd99e5a7463c0a01c45318e07a0eb80',
|
||||
'org.briarproject:onionwrapper-core:0.0.4:onionwrapper-core-0.0.4.jar:28a01a62e96aa763989a8afc325abd3bee54f8021269f91aa48b247a6e717870',
|
||||
'org.briarproject:socks-socket:0.1:socks-socket-0.1.jar:e5898822d10f5390363c5dddb945891648c92cf93ba50709e07f0d173ec0eb4b',
|
||||
'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a',
|
||||
'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb',
|
||||
|
||||
@@ -11,7 +11,7 @@ dependencies {
|
||||
implementation project(':bramble-core')
|
||||
|
||||
implementation fileTree(dir: 'libs', include: '*.jar')
|
||||
def jna_version = '4.5.2'
|
||||
def jna_version = '5.13.0'
|
||||
implementation "net.java.dev.jna:jna:$jna_version"
|
||||
implementation "net.java.dev.jna:jna-platform:$jna_version"
|
||||
implementation "org.briarproject:onionwrapper-java:$onionwrapper_version"
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.bramble.network.JavaNetworkModule;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface BrambleJavaEagerSingletons {
|
||||
|
||||
void inject(JavaNetworkModule.EagerSingletons init);
|
||||
|
||||
class Helper {
|
||||
|
||||
public static void injectEagerSingletons(BrambleJavaEagerSingletons c) {
|
||||
c.inject(new JavaNetworkModule.EagerSingletons());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,51 @@
|
||||
package org.briarproject.bramble.network;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.Service;
|
||||
import org.briarproject.bramble.api.network.NetworkManager;
|
||||
import org.briarproject.bramble.api.network.NetworkStatus;
|
||||
import org.briarproject.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Collections.list;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class JavaNetworkManager implements NetworkManager {
|
||||
@NotNullByDefault
|
||||
class JavaNetworkManager implements NetworkManager, Service {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(JavaNetworkManager.class.getName());
|
||||
|
||||
private final TaskScheduler scheduler;
|
||||
private final Executor ioExecutor;
|
||||
private final EventBus eventBus;
|
||||
private final AtomicReference<NetworkStatus> lastStatus =
|
||||
new AtomicReference<>();
|
||||
|
||||
@Inject
|
||||
JavaNetworkManager() {
|
||||
JavaNetworkManager(TaskScheduler scheduler,
|
||||
@IoExecutor Executor ioExecutor,
|
||||
EventBus eventBus) {
|
||||
this.scheduler = scheduler;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,7 +66,29 @@ class JavaNetworkManager implements NetworkManager {
|
||||
} catch (SocketException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Connected: " + connected
|
||||
+ ", has IPv4 address: " + hasIpv4
|
||||
+ ", has IPv6 unicast address: " + hasIpv6Unicast);
|
||||
}
|
||||
return new NetworkStatus(connected, false, !hasIpv4 && hasIpv6Unicast);
|
||||
}
|
||||
|
||||
private void broadcastNetworkStatusIfChanged() {
|
||||
NetworkStatus status = getNetworkStatus();
|
||||
NetworkStatus old = lastStatus.getAndSet(status);
|
||||
if (!status.equals(old)) {
|
||||
eventBus.broadcast(new NetworkStatusEvent(status));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startService() {
|
||||
scheduler.scheduleWithFixedDelay(this::broadcastNetworkStatusIfChanged,
|
||||
ioExecutor, 0, 10, SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopService() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.briarproject.bramble.network;
|
||||
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.network.NetworkManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
@@ -10,9 +12,16 @@ import dagger.Provides;
|
||||
@Module
|
||||
public class JavaNetworkModule {
|
||||
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
NetworkManager networkManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
NetworkManager provideNetworkManager(JavaNetworkManager networkManager) {
|
||||
NetworkManager provideNetworkManager(LifecycleManager lifecycleManager,
|
||||
JavaNetworkManager networkManager) {
|
||||
lifecycleManager.registerService(networkManager);
|
||||
return networkManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.network.NetworkManager;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.TorControlPort;
|
||||
import org.briarproject.bramble.api.plugin.TorDirectory;
|
||||
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.onionwrapper.CircumventionProvider;
|
||||
import org.briarproject.onionwrapper.LocationUtils;
|
||||
import org.briarproject.onionwrapper.MacTorWrapper;
|
||||
import org.briarproject.onionwrapper.TorWrapper;
|
||||
import org.briarproject.onionwrapper.UnixTorWrapper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static org.briarproject.bramble.util.OsUtils.isMac;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class MacTorPluginFactory extends TorPluginFactory {
|
||||
|
||||
@Inject
|
||||
MacTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@EventExecutor Executor eventExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
EventBus eventBus,
|
||||
SocketFactory torSocketFactory,
|
||||
BackoffFactory backoffFactory,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
Clock clock,
|
||||
CryptoComponent crypto,
|
||||
@TorDirectory File torDirectory,
|
||||
@TorSocksPort int torSocksPort,
|
||||
@TorControlPort int torControlPort) {
|
||||
super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
|
||||
locationUtils, eventBus, torSocketFactory, backoffFactory,
|
||||
circumventionProvider, batteryManager, clock, crypto,
|
||||
torDirectory, torSocksPort, torControlPort);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
String getArchitectureForTorBinary() {
|
||||
if (!isMac()) return null;
|
||||
String arch = System.getProperty("os.arch");
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("System's os.arch is " + arch);
|
||||
}
|
||||
if (arch.equals("x86_64")) return "x86_64";
|
||||
else if (arch.equals("aarch64")) return "aarch64";
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
TorPlugin createPluginInstance(Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
||||
String architecture) {
|
||||
TorWrapper tor = new MacTorWrapper(ioExecutor, eventExecutor,
|
||||
architecture, torDirectory, torSocksPort, torControlPort);
|
||||
return new TorPlugin(ioExecutor, wakefulIoExecutor, networkManager,
|
||||
locationUtils, torSocketFactory, circumventionProvider,
|
||||
batteryManager, backoff, torRendezvousCrypto, tor, callback,
|
||||
MAX_LATENCY, MAX_IDLE_TIME, true);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ public class DesktopSecureRandomModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
SecureRandomProvider provideSecureRandomProvider() {
|
||||
if (isLinux() || isMac()) return new UnixSecureRandomProvider();
|
||||
if (isLinux()) return new UnixSecureRandomProvider();
|
||||
return () -> null; // Use system default
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,15 +20,15 @@ dependencyVerification {
|
||||
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
|
||||
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
|
||||
'net.java.dev.jna:jna-platform:4.5.2:jna-platform-4.5.2.jar:f1d00c167d8921c6e23c626ef9f1c3ae0be473c95c68ffa012bc7ae55a87e2d6',
|
||||
'net.java.dev.jna:jna:4.5.2:jna-4.5.2.jar:0c8eb7acf67261656d79005191debaba3b6bf5dd60a43735a245429381dbecff',
|
||||
'net.java.dev.jna:jna-platform:5.13.0:jna-platform-5.13.0.jar:474d7b88f6e97009b6ec1d98c3024dd95c23187c65dabfbc35331bcac3d173dd',
|
||||
'net.java.dev.jna:jna:5.13.0:jna-5.13.0.jar:66d4f819a062a51a1d5627bffc23fac55d1677f0e0a1feba144aabdd670a64bb',
|
||||
'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:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b',
|
||||
'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011',
|
||||
'org.briarproject:onionwrapper-core:0.0.2:onionwrapper-core-0.0.2.jar:7038e960c9e59803f0e2c19444dbb5214cd99e5a7463c0a01c45318e07a0eb80',
|
||||
'org.briarproject:onionwrapper-java:0.0.2:onionwrapper-java-0.0.2.jar:87a3f4082174dbbd32c4f5f062b46af1d3fedd8cfa1ec84f6ce6ccb6e3674fb6',
|
||||
'org.briarproject:onionwrapper-core:0.0.4:onionwrapper-core-0.0.4.jar:28a01a62e96aa763989a8afc325abd3bee54f8021269f91aa48b247a6e717870',
|
||||
'org.briarproject:onionwrapper-java:0.0.4:onionwrapper-java-0.0.4.jar:7806ef878074498653b557e26eb70e6007df3450d6a910a2e9a322f7eb4df442',
|
||||
'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a',
|
||||
'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb',
|
||||
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
|
||||
|
||||
@@ -25,9 +25,9 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
versionCode 10502
|
||||
versionName "1.5.2"
|
||||
targetSdkVersion 33
|
||||
versionCode 10504
|
||||
versionName "1.5.4"
|
||||
applicationId "org.briarproject.briar.android"
|
||||
buildConfigField "String", "TorVersion", "\"$tor_version\""
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.NEARBY_WIFI_DEVICES"
|
||||
android:usesPermissionFlags="neverForLocation"
|
||||
tools:targetApi="31" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
@@ -30,8 +34,10 @@
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="18"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission-sdk-23 android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission-sdk-23 android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
@@ -7,17 +7,26 @@ import android.content.IntentFilter;
|
||||
import android.os.PowerManager;
|
||||
|
||||
import org.briarproject.bramble.api.lifecycle.Service;
|
||||
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||
import org.briarproject.briar.api.android.DozeWatchdog;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import static android.content.Context.POWER_SERVICE;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
|
||||
import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
|
||||
import static android.os.PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
|
||||
class DozeWatchdogImpl implements DozeWatchdog, Service {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(DozeWatchdogImpl.class.getName());
|
||||
|
||||
private final Context appContext;
|
||||
private final AtomicBoolean dozed = new AtomicBoolean(false);
|
||||
private final BroadcastReceiver receiver = new DozeBroadcastReceiver();
|
||||
@@ -32,14 +41,18 @@ class DozeWatchdogImpl implements DozeWatchdog, Service {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startService() throws ServiceException {
|
||||
public void startService() {
|
||||
if (SDK_INT < 23) return;
|
||||
IntentFilter filter = new IntentFilter(ACTION_DEVICE_IDLE_MODE_CHANGED);
|
||||
if (SDK_INT >= 33) {
|
||||
filter.addAction(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED);
|
||||
filter.addAction(ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
|
||||
}
|
||||
appContext.registerReceiver(receiver, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopService() throws ServiceException {
|
||||
public void stopService() {
|
||||
if (SDK_INT < 23) return;
|
||||
appContext.unregisterReceiver(receiver);
|
||||
}
|
||||
@@ -49,9 +62,33 @@ class DozeWatchdogImpl implements DozeWatchdog, Service {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (SDK_INT < 23) return;
|
||||
String action = intent.getAction();
|
||||
PowerManager pm =
|
||||
(PowerManager) appContext.getSystemService(POWER_SERVICE);
|
||||
if (pm.isDeviceIdleMode()) dozed.set(true);
|
||||
if (ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
|
||||
if (pm.isDeviceIdleMode()) dozed.set(true);
|
||||
} else if (SDK_INT >= 33) {
|
||||
onReceive33(action, pm);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(33)
|
||||
private void onReceive33(String action, PowerManager pm) {
|
||||
if (ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED.equals(action)) {
|
||||
if (pm.isLowPowerStandbyEnabled()) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("System is in low power standby mode");
|
||||
}
|
||||
dozed.set(true);
|
||||
}
|
||||
} else if (ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED.equals(action)) {
|
||||
if (pm.isDeviceLightIdleMode()) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("System is in light idle mode");
|
||||
}
|
||||
dozed.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.briar.android.account;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -8,6 +9,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.account.PowerView.OnCheckedChangedListener;
|
||||
@@ -18,6 +20,7 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static org.briarproject.android.dontkillmelib.DozeUtils.getDozeWhitelistingIntent;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
|
||||
@@ -113,7 +116,12 @@ public class DozeFragment extends SetupFragment
|
||||
private void askForDozeWhitelisting() {
|
||||
if (getContext() == null) return;
|
||||
Intent i = getDozeWhitelistingIntent(getContext());
|
||||
startActivityForResult(i, REQUEST_DOZE_WHITELISTING);
|
||||
try {
|
||||
startActivityForResult(i, REQUEST_DOZE_WHITELISTING);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(requireContext(),
|
||||
R.string.error_start_activity, LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.annotation.UiThread;
|
||||
|
||||
import static org.briarproject.android.dontkillmelib.HuaweiUtils.getHuaweiProtectedAppsIntent;
|
||||
import static org.briarproject.android.dontkillmelib.HuaweiUtils.protectedAppsNeedsToBeShown;
|
||||
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
@@ -49,7 +50,7 @@ class HuaweiProtectedAppsView extends PowerView {
|
||||
|
||||
@Override
|
||||
protected void onButtonClick() {
|
||||
getContext().startActivity(getHuaweiProtectedAppsIntent());
|
||||
tryToStartActivity(getContext(), getHuaweiProtectedAppsIntent());
|
||||
setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,17 @@ import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
|
||||
|
||||
import static android.Manifest.permission.POST_NOTIFICATIONS;
|
||||
import static android.content.Context.INPUT_METHOD_SERVICE;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
|
||||
import static androidx.core.content.ContextCompat.checkSelfPermission;
|
||||
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
|
||||
import static org.briarproject.briar.android.util.UiUtils.setError;
|
||||
|
||||
@@ -38,6 +45,10 @@ public class SetPasswordFragment extends SetupFragment {
|
||||
private StrengthMeter strengthMeter;
|
||||
private Button nextButton;
|
||||
|
||||
private final ActivityResultLauncher<String> requestPermissionLauncher =
|
||||
registerForActivityResult(new RequestPermission(), isGranted ->
|
||||
setPassword());
|
||||
|
||||
public static SetPasswordFragment newInstance() {
|
||||
return new SetPasswordFragment();
|
||||
}
|
||||
@@ -121,6 +132,18 @@ public class SetPasswordFragment extends SetupFragment {
|
||||
IBinder token = passwordEntry.getWindowToken();
|
||||
Object o = requireContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
|
||||
if (SDK_INT >= 33 &&
|
||||
checkSelfPermission(requireContext(), POST_NOTIFICATIONS) !=
|
||||
PERMISSION_GRANTED) {
|
||||
// this calls setPassword() when it returns
|
||||
requestPermissionLauncher.launch(POST_NOTIFICATIONS);
|
||||
} else {
|
||||
setPassword();
|
||||
}
|
||||
}
|
||||
|
||||
private void setPassword() {
|
||||
viewModel.setPassword(passwordEntry.getText().toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class SetupActivity extends BaseActivity
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
SetupViewModel viewModel;
|
||||
private SetupViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
@@ -71,16 +71,16 @@ public class SetupActivity extends BaseActivity
|
||||
}
|
||||
}
|
||||
|
||||
void showPasswordFragment() {
|
||||
private void showPasswordFragment() {
|
||||
showNextFragment(SetPasswordFragment.newInstance());
|
||||
}
|
||||
|
||||
@TargetApi(23)
|
||||
void showDozeFragment() {
|
||||
private void showDozeFragment() {
|
||||
showNextFragment(DozeFragment.newInstance());
|
||||
}
|
||||
|
||||
void showApp() {
|
||||
private void showApp() {
|
||||
Intent i = new Intent(this, ENTRY_ACTIVITY);
|
||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME |
|
||||
FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.briar.android.account;
|
||||
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.hardware.biometrics.BiometricPrompt;
|
||||
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
|
||||
@@ -28,6 +29,7 @@ import static android.hardware.biometrics.BiometricPrompt.BIOMETRIC_ERROR_CANCEL
|
||||
import static android.hardware.biometrics.BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_KEYGUARD_UNLOCK;
|
||||
import static org.briarproject.briar.android.util.UiUtils.hasKeyguardLock;
|
||||
import static org.briarproject.briar.android.util.UiUtils.hasUsableFingerprint;
|
||||
@@ -191,7 +193,12 @@ public class UnlockActivity extends BaseActivity {
|
||||
unlock();
|
||||
} else {
|
||||
keyguardShown = true;
|
||||
startActivityForResult(intent, REQUEST_KEYGUARD_UNLOCK);
|
||||
try {
|
||||
startActivityForResult(intent, REQUEST_KEYGUARD_UNLOCK);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(this, R.string.error_start_activity, LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
overridePendingTransition(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.briar.android.account;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.Toast;
|
||||
@@ -60,12 +61,12 @@ class XiaomiLockAppsView extends PowerView {
|
||||
getContext().startActivity(getXiaomiLockAppsIntent());
|
||||
setChecked(true);
|
||||
return;
|
||||
} catch (SecurityException e) {
|
||||
} catch (SecurityException | ActivityNotFoundException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
Toast.makeText(getContext(),
|
||||
R.string.dnkm_xiaomi_lock_apps_error_toast,
|
||||
LENGTH_LONG).show();
|
||||
}
|
||||
Toast.makeText(getContext(),
|
||||
R.string.dnkm_xiaomi_lock_apps_error_toast,
|
||||
LENGTH_LONG).show();
|
||||
// Let the user continue with setup
|
||||
setChecked(true);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package org.briarproject.briar.android.activity;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.transition.Transition;
|
||||
import android.view.Window;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
|
||||
import org.briarproject.bramble.api.system.Wakeful;
|
||||
@@ -34,9 +36,12 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.android.dontkillmelib.DozeUtils.getDozeWhitelistingIntent;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_UNLOCK;
|
||||
@@ -179,7 +184,13 @@ public abstract class BriarActivity extends BaseActivity {
|
||||
b.setPositiveButton(R.string.fix,
|
||||
(dialog, which) -> {
|
||||
Intent i = getDozeWhitelistingIntent(BriarActivity.this);
|
||||
startActivityForResult(i, REQUEST_DOZE_WHITELISTING);
|
||||
try {
|
||||
startActivityForResult(i, REQUEST_DOZE_WHITELISTING);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
Toast.makeText(this, R.string.error_start_activity,
|
||||
LENGTH_LONG).show();
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
b.setNegativeButton(R.string.cancel,
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||
import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails;
|
||||
import org.briarproject.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -32,6 +33,7 @@ import androidx.fragment.app.DialogFragment;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION;
|
||||
import static android.view.View.GONE;
|
||||
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -68,6 +70,7 @@ public class ScreenFilterDialogFragment extends DialogFragment {
|
||||
((BaseActivity) requireActivity()).getActivityComponent().inject(this);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
Activity activity = getActivity();
|
||||
@@ -98,7 +101,7 @@ public class ScreenFilterDialogFragment extends DialogFragment {
|
||||
builder.setNeutralButton(R.string.screen_filter_review_apps,
|
||||
(dialog, which) -> {
|
||||
Intent i = new Intent(ACTION_MANAGE_OVERLAY_PERMISSION);
|
||||
startActivity(i);
|
||||
tryToStartActivity(requireActivity(), i);
|
||||
});
|
||||
}
|
||||
builder.setPositiveButton(R.string.continue_button, (dialog, which) -> {
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
package org.briarproject.briar.android.hotspot;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.util.Consumer;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import static android.content.Context.WIFI_SERVICE;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
|
||||
/**
|
||||
* Abstract base class for the ConditionManagers that ensure that the conditions
|
||||
* to open a hotspot are fulfilled. There are different extensions of this for
|
||||
* API levels lower than 29 and 29+.
|
||||
* API levels lower than 29, 29+ and 33+.
|
||||
*/
|
||||
abstract class AbstractConditionManager {
|
||||
|
||||
@@ -28,6 +34,7 @@ abstract class AbstractConditionManager {
|
||||
final Consumer<Boolean> permissionUpdateCallback;
|
||||
protected FragmentActivity ctx;
|
||||
WifiManager wifiManager;
|
||||
private ActivityResultLauncher<Intent> wifiRequest;
|
||||
|
||||
AbstractConditionManager(Consumer<Boolean> permissionUpdateCallback) {
|
||||
this.permissionUpdateCallback = permissionUpdateCallback;
|
||||
@@ -38,8 +45,12 @@ abstract class AbstractConditionManager {
|
||||
*/
|
||||
void init(FragmentActivity ctx) {
|
||||
this.ctx = ctx;
|
||||
this.wifiManager = (WifiManager) ctx.getApplicationContext()
|
||||
wifiManager = (WifiManager) ctx.getApplicationContext()
|
||||
.getSystemService(WIFI_SERVICE);
|
||||
wifiRequest = ctx.registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> permissionUpdateCallback
|
||||
.accept(wifiManager.isWifiEnabled()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,6 +68,8 @@ abstract class AbstractConditionManager {
|
||||
*/
|
||||
abstract boolean checkAndRequestConditions();
|
||||
|
||||
abstract String getWifiSettingsAction();
|
||||
|
||||
void showRationale(Context ctx, @StringRes int title,
|
||||
@StringRes int body, Runnable onContinueClicked,
|
||||
Runnable onDismiss) {
|
||||
@@ -69,4 +82,13 @@ abstract class AbstractConditionManager {
|
||||
builder.show();
|
||||
}
|
||||
|
||||
void requestEnableWiFi() {
|
||||
try {
|
||||
wifiRequest.launch(new Intent(getWifiSettingsAction()));
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(ctx, R.string.error_start_activity, LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
package org.briarproject.briar.android.hotspot;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.provider.Settings;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import androidx.activity.result.ActivityResultCaller;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
@@ -22,20 +19,14 @@ import static java.util.logging.Logger.getLogger;
|
||||
* As soon as {@link #checkAndRequestConditions()} returns true,
|
||||
* all conditions are fulfilled.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
class ConditionManager extends AbstractConditionManager {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ConditionManager.class.getName());
|
||||
|
||||
private final ActivityResultLauncher<Intent> wifiRequest;
|
||||
|
||||
ConditionManager(ActivityResultCaller arc,
|
||||
Consumer<Boolean> permissionUpdateCallback) {
|
||||
super(permissionUpdateCallback);
|
||||
wifiRequest = arc.registerForActivityResult(
|
||||
new StartActivityForResult(),
|
||||
result -> permissionUpdateCallback
|
||||
.accept(wifiManager.isWifiEnabled()));
|
||||
ConditionManager(Consumer<Boolean> permissionUpdateCallback) {
|
||||
super( permissionUpdateCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,8 +67,9 @@ class ConditionManager extends AbstractConditionManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void requestEnableWiFi() {
|
||||
wifiRequest.launch(new Intent(Settings.ACTION_WIFI_SETTINGS));
|
||||
@Override
|
||||
String getWifiSettingsAction() {
|
||||
return Settings.ACTION_WIFI_SETTINGS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
package org.briarproject.briar.android.hotspot;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.provider.Settings;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.util.Permission;
|
||||
import org.briarproject.briar.android.util.PermissionUtils;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import androidx.activity.result.ActivityResultCaller;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.util.Consumer;
|
||||
@@ -28,12 +27,13 @@ import static org.briarproject.briar.android.util.PermissionUtils.showLocationDi
|
||||
|
||||
/**
|
||||
* This class ensures that the conditions to open a hotspot are fulfilled on
|
||||
* API levels >= 29.
|
||||
* API levels >= 29 and < 33.
|
||||
* <p>
|
||||
* As soon as {@link #checkAndRequestConditions()} returns true,
|
||||
* all conditions are fulfilled.
|
||||
*/
|
||||
@RequiresApi(29)
|
||||
@NotNullByDefault
|
||||
class ConditionManager29 extends AbstractConditionManager {
|
||||
|
||||
private static final Logger LOG =
|
||||
@@ -42,7 +42,6 @@ class ConditionManager29 extends AbstractConditionManager {
|
||||
private Permission locationPermission = Permission.UNKNOWN;
|
||||
|
||||
private final ActivityResultLauncher<String> locationRequest;
|
||||
private final ActivityResultLauncher<Intent> wifiRequest;
|
||||
|
||||
ConditionManager29(ActivityResultCaller arc,
|
||||
Consumer<Boolean> permissionUpdateCallback) {
|
||||
@@ -53,11 +52,6 @@ class ConditionManager29 extends AbstractConditionManager {
|
||||
onRequestPermissionResult(granted);
|
||||
permissionUpdateCallback.accept(TRUE.equals(granted));
|
||||
});
|
||||
wifiRequest = arc.registerForActivityResult(
|
||||
new StartActivityForResult(),
|
||||
result -> permissionUpdateCallback
|
||||
.accept(wifiManager.isWifiEnabled())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -131,6 +125,11 @@ class ConditionManager29 extends AbstractConditionManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
String getWifiSettingsAction() {
|
||||
return Settings.Panel.ACTION_WIFI;
|
||||
}
|
||||
|
||||
private void onRequestPermissionResult(@Nullable Boolean granted) {
|
||||
if (granted != null && granted) {
|
||||
locationPermission = Permission.GRANTED;
|
||||
@@ -146,8 +145,4 @@ class ConditionManager29 extends AbstractConditionManager {
|
||||
locationRequest.launch(ACCESS_FINE_LOCATION);
|
||||
}
|
||||
|
||||
private void requestEnableWiFi() {
|
||||
wifiRequest.launch(new Intent(Settings.Panel.ACTION_WIFI));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
package org.briarproject.briar.android.hotspot;
|
||||
|
||||
import android.provider.Settings;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.util.Permission;
|
||||
import org.briarproject.briar.android.util.PermissionUtils;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import androidx.activity.result.ActivityResultCaller;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
import static android.Manifest.permission.NEARBY_WIFI_DEVICES;
|
||||
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
|
||||
/**
|
||||
* This class ensures that the conditions to open a hotspot are fulfilled on
|
||||
* API levels >= 33.
|
||||
* <p>
|
||||
* As soon as {@link #checkAndRequestConditions()} returns true,
|
||||
* all conditions are fulfilled.
|
||||
*/
|
||||
@RequiresApi(33)
|
||||
@NotNullByDefault
|
||||
class ConditionManager33 extends AbstractConditionManager {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ConditionManager33.class.getName());
|
||||
|
||||
private Permission nearbyWifiPermission = Permission.UNKNOWN;
|
||||
|
||||
private final ActivityResultLauncher<String> nearbyWifiRequest;
|
||||
|
||||
ConditionManager33(ActivityResultCaller arc,
|
||||
Consumer<Boolean> permissionUpdateCallback) {
|
||||
super(permissionUpdateCallback);
|
||||
// permissionUpdateCallback receives false if permissions were denied
|
||||
nearbyWifiRequest = arc.registerForActivityResult(
|
||||
new RequestPermission(), granted -> {
|
||||
onRequestPermissionResult(granted);
|
||||
permissionUpdateCallback.accept(TRUE.equals(granted));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
void onStart() {
|
||||
nearbyWifiPermission = Permission.UNKNOWN;
|
||||
}
|
||||
|
||||
private boolean areEssentialPermissionsGranted() {
|
||||
boolean isWifiEnabled = wifiManager.isWifiEnabled();
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(String.format("areEssentialPermissionsGranted(): " +
|
||||
"nearbyWifiPermission? %s, " +
|
||||
"wifiManager.isWifiEnabled()? %b",
|
||||
nearbyWifiPermission, isWifiEnabled));
|
||||
}
|
||||
return nearbyWifiPermission == Permission.GRANTED && isWifiEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean checkAndRequestConditions() {
|
||||
if (areEssentialPermissionsGranted()) return true;
|
||||
|
||||
if (nearbyWifiPermission == Permission.UNKNOWN) {
|
||||
requestPermissions();
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the location permission has been permanently denied, ask the
|
||||
// user to change the setting
|
||||
if (nearbyWifiPermission == Permission.PERMANENTLY_DENIED) {
|
||||
PermissionUtils.showDenialDialog(ctx,
|
||||
R.string.permission_nearby_devices_title,
|
||||
R.string.permission_hotspot_nearby_wifi_denied_body,
|
||||
() -> permissionUpdateCallback.accept(false));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Should we show the rationale for location permission?
|
||||
if (nearbyWifiPermission == Permission.SHOW_RATIONALE) {
|
||||
showRationale(ctx,
|
||||
R.string.permission_location_title,
|
||||
R.string.permission_hotspot_nearby_wifi_request_body,
|
||||
this::requestPermissions,
|
||||
() -> permissionUpdateCallback.accept(false));
|
||||
return false;
|
||||
}
|
||||
|
||||
// If Wifi is not enabled, we show the rationale for enabling Wifi?
|
||||
if (!wifiManager.isWifiEnabled()) {
|
||||
showRationale(ctx, R.string.wifi_settings_title,
|
||||
R.string.wifi_settings_request_enable_body,
|
||||
this::requestEnableWiFi,
|
||||
() -> permissionUpdateCallback.accept(false));
|
||||
return false;
|
||||
}
|
||||
|
||||
// we shouldn't usually reach this point, but if we do, return false
|
||||
// anyway to force a recheck. Maybe some condition changed in the
|
||||
// meantime.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
String getWifiSettingsAction() {
|
||||
return Settings.Panel.ACTION_WIFI;
|
||||
}
|
||||
|
||||
private void onRequestPermissionResult(@Nullable Boolean granted) {
|
||||
if (granted != null && granted) {
|
||||
nearbyWifiPermission = Permission.GRANTED;
|
||||
} else if (shouldShowRequestPermissionRationale(ctx,
|
||||
NEARBY_WIFI_DEVICES)) {
|
||||
nearbyWifiPermission = Permission.SHOW_RATIONALE;
|
||||
} else {
|
||||
nearbyWifiPermission = Permission.PERMANENTLY_DENIED;
|
||||
}
|
||||
}
|
||||
|
||||
private void requestPermissions() {
|
||||
nearbyWifiRequest.launch(NEARBY_WIFI_DEVICES);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import static android.view.View.VISIBLE;
|
||||
import static androidx.transition.TransitionManager.beginDelayedTransition;
|
||||
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
|
||||
import static org.briarproject.briar.android.hotspot.HotspotViewModel.getApkFileName;
|
||||
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -102,7 +103,7 @@ public class FallbackFragment extends BaseFragment {
|
||||
i.putExtra(EXTRA_STREAM, uri);
|
||||
i.setType("*/*"); // gives us all sharing options
|
||||
i.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
|
||||
startActivity(Intent.createChooser(i, null));
|
||||
tryToStartActivity(requireActivity(), Intent.createChooser(i, null));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -49,8 +49,10 @@ public class HotspotIntroFragment extends Fragment {
|
||||
private TextView progressTextView;
|
||||
|
||||
private final AbstractConditionManager conditionManager = SDK_INT < 29 ?
|
||||
new ConditionManager(this, this::onPermissionUpdate) :
|
||||
new ConditionManager29(this, this::onPermissionUpdate);
|
||||
new ConditionManager(this::onPermissionUpdate) :
|
||||
SDK_INT >= 33 ?
|
||||
new ConditionManager33(this, this::onPermissionUpdate) :
|
||||
new ConditionManager29(this, this::onPermissionUpdate);
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
@@ -87,7 +89,6 @@ public class HotspotIntroFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void onButtonClick(View view) {
|
||||
startButton.setEnabled(false);
|
||||
startHotspotIfConditionsFulfilled();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,13 +22,19 @@ import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import static android.Manifest.permission.POST_NOTIFICATIONS;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
|
||||
import static androidx.core.content.ContextCompat.checkSelfPermission;
|
||||
import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHENER_ERROR;
|
||||
import static org.briarproject.bramble.api.crypto.DecryptionResult.SUCCESS;
|
||||
import static org.briarproject.briar.android.login.LoginUtils.createKeyStrengthenerErrorDialog;
|
||||
@@ -52,6 +58,10 @@ public class PasswordFragment extends BaseFragment implements TextWatcher {
|
||||
private TextInputLayout input;
|
||||
private TextInputEditText password;
|
||||
|
||||
private final ActivityResultLauncher<String> requestPermissionLauncher =
|
||||
registerForActivityResult(new RequestPermission(), isGranted ->
|
||||
validatePassword());
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
@@ -109,6 +119,17 @@ public class PasswordFragment extends BaseFragment implements TextWatcher {
|
||||
hideSoftKeyboard(password);
|
||||
signInButton.setVisibility(INVISIBLE);
|
||||
progress.setVisibility(VISIBLE);
|
||||
if (SDK_INT >= 33 &&
|
||||
checkSelfPermission(requireContext(), POST_NOTIFICATIONS) !=
|
||||
PERMISSION_GRANTED) {
|
||||
// this calls validatePassword() when it returns
|
||||
requestPermissionLauncher.launch(POST_NOTIFICATIONS);
|
||||
} else {
|
||||
validatePassword();
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePassword() {
|
||||
viewModel.validatePassword(password.getText().toString());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.briar.android.reporting;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@@ -33,13 +32,11 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.briar.android.util.UiUtils.onSingleLinkClick;
|
||||
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -180,13 +177,7 @@ public class ReportFormFragment extends BaseFragment {
|
||||
private void triggerPrivacyPolicy() {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse("https://briarproject.org/privacy-policy/\\"));
|
||||
try {
|
||||
startActivity(i);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
Toast.makeText(requireContext(),
|
||||
R.string.error_start_activity, LENGTH_LONG).show();
|
||||
}
|
||||
tryToStartActivity(requireActivity(), i);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.briar.android.settings;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@@ -8,7 +7,6 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.briar.BuildConfig;
|
||||
import org.briarproject.briar.R;
|
||||
@@ -21,10 +19,9 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static android.content.Intent.ACTION_VIEW;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -85,16 +82,9 @@ public class AboutFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void goToUrl(String url) {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
Intent i = new Intent(ACTION_VIEW);
|
||||
i.setData(Uri.parse(url));
|
||||
try {
|
||||
startActivity(i);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
Toast.makeText(requireContext(),
|
||||
R.string.error_start_activity, LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
tryToStartActivity(requireActivity(), i);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,11 +24,14 @@ import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceGroup;
|
||||
|
||||
import static android.content.Intent.ACTION_SEND;
|
||||
import static android.content.Intent.EXTRA_TEXT;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
|
||||
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||
import static org.briarproject.briar.android.util.UiUtils.launchActivityToOpenFile;
|
||||
import static org.briarproject.briar.android.util.UiUtils.triggerFeedback;
|
||||
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -37,11 +40,14 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
public static final String SETTINGS_NAMESPACE = "android-ui";
|
||||
|
||||
private static final String PREF_KEY_AVATAR = "pref_key_avatar";
|
||||
private static final String PREF_KEY_SHARE_LINK = "pref_key_share_app_link";
|
||||
private static final String PREF_KEY_FEEDBACK = "pref_key_send_feedback";
|
||||
private static final String PREF_KEY_DEV = "pref_key_dev";
|
||||
private static final String PREF_KEY_EXPLODE = "pref_key_explode";
|
||||
private static final String PREF_KEY_MAILBOX = "pref_key_mailbox";
|
||||
|
||||
private static final String DOWNLOAD_URL = "https://briarproject.org/download/";
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
@@ -86,6 +92,17 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
return true;
|
||||
});
|
||||
|
||||
Preference prefShareLink =
|
||||
requireNonNull(findPreference(PREF_KEY_SHARE_LINK));
|
||||
prefShareLink.setOnPreferenceClickListener(preference -> {
|
||||
String text = getString(R.string.share_app_link_text, DOWNLOAD_URL);
|
||||
Intent sendIntent = new Intent(ACTION_SEND);
|
||||
sendIntent.putExtra(EXTRA_TEXT, text);
|
||||
sendIntent.setType("text/plain");
|
||||
tryToStartActivity(requireActivity(),
|
||||
Intent.createChooser(sendIntent, null));
|
||||
return true;
|
||||
});
|
||||
Preference prefFeedback =
|
||||
requireNonNull(findPreference(PREF_KEY_FEEDBACK));
|
||||
prefFeedback.setOnPreferenceClickListener(preference -> {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package org.briarproject.briar.android.util;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.location.LocationManager;
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.nullsafety.MethodsNotNullByDefault;
|
||||
@@ -29,10 +27,10 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static androidx.core.content.ContextCompat.checkSelfPermission;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
|
||||
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -47,7 +45,7 @@ public class PermissionUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isPermissionGranted(Context ctx, String permission) {
|
||||
private static boolean isPermissionGranted(Context ctx, String permission) {
|
||||
return checkSelfPermission(ctx, permission) ==
|
||||
PERMISSION_GRANTED;
|
||||
}
|
||||
@@ -68,7 +66,7 @@ public class PermissionUtils {
|
||||
gotPermission(ctx, grantedMap, BLUETOOTH_SCAN);
|
||||
}
|
||||
|
||||
public static DialogInterface.OnClickListener getGoToSettingsListener(
|
||||
private static DialogInterface.OnClickListener getGoToSettingsListener(
|
||||
Context context) {
|
||||
return (dialog, which) -> {
|
||||
Intent i = new Intent();
|
||||
@@ -76,7 +74,7 @@ public class PermissionUtils {
|
||||
i.addCategory(CATEGORY_DEFAULT);
|
||||
i.setData(Uri.parse("package:" + APPLICATION_ID));
|
||||
i.addFlags(FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(i);
|
||||
tryToStartActivity(context, i);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -123,12 +121,7 @@ public class PermissionUtils {
|
||||
builder.setPositiveButton(R.string.permission_location_setting_button,
|
||||
(dialog, which) -> {
|
||||
Intent i = new Intent(ACTION_LOCATION_SOURCE_SETTINGS);
|
||||
try {
|
||||
ctx.startActivity(i);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(ctx, R.string.error_start_activity,
|
||||
LENGTH_LONG).show();
|
||||
}
|
||||
tryToStartActivity(ctx, i);
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
|
||||
@@ -157,6 +157,15 @@ public class UiUtils {
|
||||
ta.commit();
|
||||
}
|
||||
|
||||
public static void tryToStartActivity(Context ctx, Intent intent) {
|
||||
try {
|
||||
ctx.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(ctx, R.string.error_start_activity, LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getContactDisplayName(Author author,
|
||||
@Nullable String alias) {
|
||||
String name = author.getName();
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M21,12L14,5V9C7,10 4,15 3,20C5.5,16.5 9,14.9 14,14.9V19L21,12Z" />
|
||||
</vector>
|
||||
@@ -13,10 +13,10 @@
|
||||
android:layout_margin="@dimen/margin_medium"
|
||||
android:contentDescription="@string/info"
|
||||
android:drawablePadding="@dimen/margin_medium"
|
||||
android:drawableTint="?attr/colorControlNormal"
|
||||
android:gravity="center_vertical"
|
||||
app:drawableLeftCompat="@drawable/ic_info_dark"
|
||||
app:drawableStartCompat="@drawable/ic_info_dark"
|
||||
app:drawableTint="?attr/colorControlNormal"
|
||||
tools:text="Did you know that if you took all the veins out of your body and laid them out end to end, you would die?" />
|
||||
|
||||
</merge>
|
||||
@@ -696,7 +696,7 @@
|
||||
<string name="mailbox_error_wizard_info2">لطفا هنگامی که به دستگاه دسترسی دارید به این صفحه بازگردید.</string>
|
||||
<string name="mailbox_error_wizard_info3">لطفا با استفاده از دکمه زیر، پیوند Mailbox خود را لغو کنید.\n\nپس از لغو پیوند Mailbox قدیمی، میتوانید هر زمان که خواستید یک Mailbox جدید راهاندازی کنید.</string>
|
||||
<!--About-->
|
||||
<string name="about_title">دربارهی Psiphon</string>
|
||||
<string name="about_title">دربارهی Briar</string>
|
||||
<string name="briar_version">نسخه Briar: %s</string>
|
||||
<string name="tor_version">نسخه Tor: %s</string>
|
||||
<string name="links">لینک ها</string>
|
||||
|
||||
@@ -733,6 +733,8 @@
|
||||
|
||||
<!-- Settings Actions -->
|
||||
<string name="pref_category_actions">Actions</string>
|
||||
<string name="share_app_link">Share download link</string>
|
||||
<string name="share_app_link_text">Download Briar at %s</string>
|
||||
<string name="send_feedback">Send feedback</string>
|
||||
|
||||
<!-- Link Warning -->
|
||||
@@ -787,6 +789,7 @@
|
||||
<string name="permission_camera_title">Camera permission</string>
|
||||
<string name="permission_camera_request_body">To scan the QR code, Briar needs access to the camera.</string>
|
||||
<string name="permission_location_title">Location permission</string>
|
||||
<string name="permission_nearby_devices_title">Nearby devices permission</string>
|
||||
<string name="permission_location_request_body">To discover Bluetooth devices, Briar needs permission to access your location.\n\nBriar does not store your location or share it with anyone.</string>
|
||||
<string name="permission_camera_location_title">Camera and location</string>
|
||||
<string name="permission_camera_location_request_body">To scan the QR code, Briar needs access to the camera.\n\nTo discover Bluetooth devices, Briar needs permission to access your location.\n\nBriar does not store your location or share it with anyone.</string>
|
||||
@@ -831,6 +834,8 @@
|
||||
<string name="permission_hotspot_location_request_precise_body">To create a Wi-Fi hotspot, Briar needs permission to access your precise location.\n\nBriar does not store your location or share it with anyone.</string>
|
||||
<string name="permission_hotspot_location_denied_body">You have denied access to your location, but Briar needs this permission to create a Wi-Fi hotspot.\n\nPlease consider granting access.</string>
|
||||
<string name="permission_hotspot_location_denied_precise_body">You have denied access to your precise location, but Briar needs this permission to create a Wi-Fi hotspot.\n\nPlease consider granting access.</string>
|
||||
<string name="permission_hotspot_nearby_wifi_request_body">To create a Wi-Fi hotspot, Briar needs permission to access nearby devices.</string>
|
||||
<string name="permission_hotspot_nearby_wifi_denied_body">You have denied access to nearby devices, but Briar needs this permission to create a Wi-Fi hotspot.\n\nPlease consider granting access.</string>
|
||||
<string name="wifi_settings_title">Wi-Fi setting</string>
|
||||
<string name="wifi_settings_request_enable_body">To create a Wi-Fi hotspot, Briar needs to use Wi-Fi. Please enable it.</string>
|
||||
|
||||
|
||||
@@ -29,11 +29,6 @@
|
||||
android:title="@string/mailbox_settings_title"
|
||||
app:icon="@drawable/ic_mailbox" />
|
||||
|
||||
<Preference
|
||||
android:title="@string/about_title"
|
||||
app:fragment="org.briarproject.briar.android.settings.AboutFragment"
|
||||
app:icon="@drawable/ic_info_dark" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_key_actions"
|
||||
android:layout="@layout/preferences_category"
|
||||
@@ -47,6 +42,11 @@
|
||||
android:targetClass="org.briarproject.briar.android.hotspot.HotspotActivity"
|
||||
android:targetPackage="@string/app_package" />
|
||||
</Preference>
|
||||
<Preference
|
||||
android:key="pref_key_share_app_link"
|
||||
android:title="@string/share_app_link"
|
||||
app:icon="@drawable/ic_settings_share_link">
|
||||
</Preference>
|
||||
<Preference
|
||||
android:key="pref_key_send_feedback"
|
||||
android:title="@string/send_feedback"
|
||||
@@ -74,4 +74,10 @@
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<Preference
|
||||
android:title="@string/about_title"
|
||||
app:allowDividerAbove="true"
|
||||
app:fragment="org.briarproject.briar.android.settings.AboutFragment"
|
||||
app:icon="@drawable/ic_info_dark" />
|
||||
|
||||
</PreferenceScreen>
|
||||
|
||||
@@ -74,6 +74,13 @@ public interface BlogManager {
|
||||
@Nullable String comment, BlogPostHeader parentHeader)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Adds a comment to an existing blog post or reblogs it.
|
||||
*/
|
||||
void addLocalComment(Transaction txn, LocalAuthor author,
|
||||
GroupId groupId, @Nullable String comment,
|
||||
BlogPostHeader parentHeader) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the blog with the given ID.
|
||||
*/
|
||||
@@ -99,6 +106,11 @@ public interface BlogManager {
|
||||
*/
|
||||
Collection<Blog> getBlogs() throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all blogs to which the user subscribes.
|
||||
*/
|
||||
Collection<Blog> getBlogs(Transaction txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the group IDs of all blogs to which the user subscribes.
|
||||
*/
|
||||
@@ -136,6 +148,11 @@ public interface BlogManager {
|
||||
*/
|
||||
void setReadFlag(MessageId m, boolean read) throws DbException;
|
||||
|
||||
/**
|
||||
* Marks a blog post as read or unread.
|
||||
*/
|
||||
void setReadFlag(Transaction txn, MessageId m, boolean read) throws DbException;
|
||||
|
||||
/**
|
||||
* Registers a hook to be called whenever a blog is removed.
|
||||
*/
|
||||
|
||||
@@ -257,12 +257,19 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
||||
public void addLocalComment(LocalAuthor author, GroupId groupId,
|
||||
@Nullable String comment, BlogPostHeader parentHeader)
|
||||
throws DbException {
|
||||
db.transaction(false, txn -> {
|
||||
addLocalComment(txn, author, groupId, comment, parentHeader);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLocalComment(Transaction txn, LocalAuthor author,
|
||||
GroupId groupId, @Nullable String comment,
|
||||
BlogPostHeader parentHeader) throws DbException {
|
||||
MessageType type = parentHeader.getType();
|
||||
if (type != POST && type != COMMENT)
|
||||
throw new IllegalArgumentException("Comment on unknown type!");
|
||||
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
// Wrap post that we are commenting on
|
||||
MessageId parentOriginalId =
|
||||
@@ -281,6 +288,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
||||
meta.put(KEY_ORIGINAL_PARENT_MSG_ID, parentOriginalId);
|
||||
meta.put(KEY_PARENT_MSG_ID, parentCurrentId);
|
||||
meta.put(KEY_AUTHOR, clientHelper.toList(author));
|
||||
meta.put(KEY_READ, true);
|
||||
|
||||
// Send comment
|
||||
clientHelper.addLocalMessage(txn, message, meta, true, false);
|
||||
@@ -290,13 +298,10 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
||||
message.getId(), meta);
|
||||
BlogPostAddedEvent event = new BlogPostAddedEvent(groupId, h, true);
|
||||
txn.attach(event);
|
||||
db.commitTransaction(txn);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IllegalArgumentException("Invalid key of author", e);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,18 +434,17 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
||||
|
||||
@Override
|
||||
public Collection<Blog> getBlogs() throws DbException {
|
||||
return db.transactionWithResult(true, this::getBlogs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Blog> getBlogs(Transaction txn) throws DbException {
|
||||
try {
|
||||
List<Blog> blogs = new ArrayList<>();
|
||||
Collection<Group> groups;
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
groups = db.getGroups(txn, CLIENT_ID, MAJOR_VERSION);
|
||||
for (Group g : groups) {
|
||||
blogs.add(blogFactory.parseBlog(g));
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
Collection<Group> groups =
|
||||
db.getGroups(txn, CLIENT_ID, MAJOR_VERSION);
|
||||
for (Group g : groups) {
|
||||
blogs.add(blogFactory.parseBlog(g));
|
||||
}
|
||||
return blogs;
|
||||
} catch (FormatException e) {
|
||||
@@ -555,10 +559,18 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
||||
|
||||
@Override
|
||||
public void setReadFlag(MessageId m, boolean read) throws DbException {
|
||||
db.transaction(true, txn -> {
|
||||
setReadFlag(txn, m, read);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadFlag(Transaction txn, MessageId m, boolean read)
|
||||
throws DbException {
|
||||
try {
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(KEY_READ, read);
|
||||
clientHelper.mergeMessageMetadata(m, meta);
|
||||
clientHelper.mergeMessageMetadata(txn, m, meta);
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.briarproject.briar.api.blog.Blog;
|
||||
import org.briarproject.briar.api.blog.BlogCommentHeader;
|
||||
import org.briarproject.briar.api.blog.BlogFactory;
|
||||
@@ -396,12 +397,12 @@ public class BlogManagerImplTest extends BrambleMockTestCase {
|
||||
new BdfEntry(KEY_ORIGINAL_MSG_ID, commentId),
|
||||
new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, messageId),
|
||||
new BdfEntry(KEY_PARENT_MSG_ID, messageId),
|
||||
new BdfEntry(KEY_AUTHOR, authorList1)
|
||||
new BdfEntry(KEY_AUTHOR, authorList1),
|
||||
new BdfEntry(KEY_READ, true)
|
||||
);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||
// Create the comment
|
||||
oneOf(blogPostFactory).createBlogComment(blog1.getId(),
|
||||
localAuthor1, comment, messageId, messageId);
|
||||
@@ -422,8 +423,6 @@ public class BlogManagerImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(localAuthor1));
|
||||
oneOf(authorManager).getAuthorInfo(txn, localAuthor1.getId());
|
||||
will(returnValue(ourselvesInfo));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
BlogPostHeader postHeader = new BlogPostHeader(POST, blog1.getId(),
|
||||
@@ -492,12 +491,12 @@ public class BlogManagerImplTest extends BrambleMockTestCase {
|
||||
new BdfEntry(KEY_ORIGINAL_MSG_ID, commentId),
|
||||
new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, messageId),
|
||||
new BdfEntry(KEY_PARENT_MSG_ID, wrappedPostId),
|
||||
new BdfEntry(KEY_AUTHOR, authorList2)
|
||||
new BdfEntry(KEY_AUTHOR, authorList2),
|
||||
new BdfEntry(KEY_READ, true)
|
||||
);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||
// Wrap the original post for blog 2
|
||||
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
||||
will(returnValue(originalPostBody));
|
||||
@@ -533,8 +532,6 @@ public class BlogManagerImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(localAuthor1));
|
||||
oneOf(authorManager).getAuthorInfo(txn, localAuthor1.getId());
|
||||
will(returnValue(verifiedInfo));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
BlogPostHeader originalPostHeader = new BlogPostHeader(POST,
|
||||
@@ -603,12 +600,12 @@ public class BlogManagerImplTest extends BrambleMockTestCase {
|
||||
new BdfEntry(KEY_ORIGINAL_MSG_ID, commentId),
|
||||
new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, rssMessageId),
|
||||
new BdfEntry(KEY_PARENT_MSG_ID, wrappedPostId),
|
||||
new BdfEntry(KEY_AUTHOR, authorList1)
|
||||
new BdfEntry(KEY_AUTHOR, authorList1),
|
||||
new BdfEntry(KEY_READ, true)
|
||||
);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||
// Wrap the original post for blog 1
|
||||
oneOf(clientHelper).getMessageAsList(txn, rssMessageId);
|
||||
will(returnValue(originalPostBody));
|
||||
@@ -642,8 +639,6 @@ public class BlogManagerImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(wrappedPostMeta));
|
||||
oneOf(clientHelper).parseAndValidateAuthor(rssAuthorList);
|
||||
will(returnValue(rssLocalAuthor));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
BlogPostHeader originalPostHeader = new BlogPostHeader(POST,
|
||||
@@ -728,12 +723,12 @@ public class BlogManagerImplTest extends BrambleMockTestCase {
|
||||
new BdfEntry(KEY_ORIGINAL_MSG_ID, localCommentId),
|
||||
new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, originalCommentId),
|
||||
new BdfEntry(KEY_PARENT_MSG_ID, wrappedCommentId),
|
||||
new BdfEntry(KEY_AUTHOR, authorList2)
|
||||
new BdfEntry(KEY_AUTHOR, authorList2),
|
||||
new BdfEntry(KEY_READ, true)
|
||||
);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||
// Rewrap the wrapped post for blog 2
|
||||
oneOf(clientHelper).getMessageAsList(txn, wrappedPostId);
|
||||
will(returnValue(wrappedPostBody));
|
||||
@@ -790,8 +785,6 @@ public class BlogManagerImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(rewrappedPostMeta));
|
||||
oneOf(clientHelper).parseAndValidateAuthor(rssAuthorList);
|
||||
will(returnValue(rssLocalAuthor));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
BlogPostHeader wrappedPostHeader = new BlogPostHeader(WRAPPED_POST,
|
||||
|
||||
@@ -11,6 +11,8 @@ import org.briarproject.bramble.util.Base32;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static java.lang.System.arraycopy;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.FORMAT_VERSION;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.RAW_LINK_BYTES;
|
||||
@@ -52,7 +54,7 @@ public class BriarTestUtils {
|
||||
byte[] publicKey = keyPair.getPublic().getEncoded();
|
||||
linkBytes[0] = FORMAT_VERSION;
|
||||
arraycopy(publicKey, 0, linkBytes, 1, RAW_LINK_BYTES - 1);
|
||||
return ("briar://" + Base32.encode(linkBytes)).toLowerCase();
|
||||
return ("briar://" + Base32.encode(linkBytes)).toLowerCase(Locale.US);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ or to develop your own user interface for it.
|
||||
|
||||
The REST API peer comes as a `jar` file
|
||||
and needs a Java Runtime Environment (JRE) that supports at least Java 8.
|
||||
It currently works only on GNU/Linux operating systems and on Windows.
|
||||
It currently works on GNU/Linux operating systems, on Windows and on macOS.
|
||||
|
||||
To build the `jar` file, you need to specify the combination of architecture and platform:
|
||||
|
||||
@@ -17,6 +17,8 @@ To build the `jar` file, you need to specify the combination of architecture and
|
||||
$ ./gradlew --configure-on-demand briar-headless:aarch64LinuxJar
|
||||
$ ./gradlew --configure-on-demand briar-headless:armhfLinuxJar
|
||||
$ ./gradlew --configure-on-demand briar-headless:windowsJar
|
||||
$ ./gradlew --configure-on-demand briar-headless:x86MacOsJar
|
||||
$ ./gradlew --configure-on-demand briar-headless:aarch64MacOsJar
|
||||
|
||||
You can start the peer (and its API server) like this:
|
||||
|
||||
@@ -51,6 +53,11 @@ The answer is an empty JSON array, because you don't have any contacts.
|
||||
Note that the HTTP request sets an `Authorization` header with the bearer token.
|
||||
A missing or wrong token will result in a `401` response.
|
||||
|
||||
To run on macOS you will need to sign the native tor binaries included in the
|
||||
jar file. To do so, extract the files in `aarch64` or `x86_64` depending on your
|
||||
system architecture, sign them using `codesign` and replace the original files
|
||||
in the jar files with the signed ones.
|
||||
|
||||
## REST API
|
||||
|
||||
### Listing all contacts
|
||||
|
||||
@@ -20,6 +20,9 @@ configurations {
|
||||
linux {
|
||||
extendsFrom runtimeClasspath
|
||||
}
|
||||
macos {
|
||||
extendsFrom runtimeClasspath
|
||||
}
|
||||
}
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
@@ -38,6 +41,10 @@ dependencies {
|
||||
windows "org.briarproject:obfs4proxy-windows:$obfs4proxy_version"
|
||||
windows "org.briarproject:snowflake-windows:$snowflake_version"
|
||||
|
||||
macos "org.briarproject:tor-macos:$tor_version"
|
||||
macos "org.briarproject:obfs4proxy-macos:$obfs4proxy_version"
|
||||
macos "org.briarproject:snowflake-macos:$snowflake_version"
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation 'io.javalin:javalin:3.5.0'
|
||||
implementation 'org.slf4j:slf4j-simple:1.7.30'
|
||||
@@ -89,7 +96,7 @@ void jarFactory(Jar jarTask, os, architecture, configuration) {
|
||||
}
|
||||
{
|
||||
it.duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
|
||||
if (os == "linux") {
|
||||
if (os == "linux" || os == "macos") {
|
||||
String[] architectures = [
|
||||
"aarch64",
|
||||
"armhf",
|
||||
@@ -100,6 +107,7 @@ void jarFactory(Jar jarTask, os, architecture, configuration) {
|
||||
exclude arch + "/obfs4proxy"
|
||||
exclude arch + "/tor"
|
||||
exclude arch + "/snowflake"
|
||||
exclude arch + "/libevent-*.dylib"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,10 +161,22 @@ task windowsJar(type: Jar) {
|
||||
jarFactory(it, 'windows', 'x86_64', configurations.windows)
|
||||
}
|
||||
|
||||
task aarch64MacOsJar(type: Jar) {
|
||||
jarFactory(it, 'macos', 'aarch64', configurations.macos)
|
||||
}
|
||||
|
||||
task x86MacOsJar(type: Jar) {
|
||||
jarFactory(it, 'macos', 'x86_64', configurations.macos)
|
||||
}
|
||||
|
||||
task linuxJars {
|
||||
dependsOn(aarch64LinuxJar, armhfLinuxJar, x86LinuxJar)
|
||||
}
|
||||
|
||||
task macosJars {
|
||||
dependsOn(aarch64MacOsJar, x86MacOsJar)
|
||||
}
|
||||
|
||||
// At the moment for non-Android projects we need to explicitly mark the code generated by kapt
|
||||
// as 'generated source code' for correct highlighting and resolve in IDE.
|
||||
idea {
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.identity
|
||||
import org.briarproject.bramble.api.identity.Author
|
||||
import org.briarproject.briar.api.identity.AuthorInfo
|
||||
import org.briarproject.briar.headless.json.JsonDict
|
||||
import java.util.Locale
|
||||
|
||||
fun Author.output() = JsonDict(
|
||||
"formatVersion" to formatVersion,
|
||||
@@ -11,4 +12,4 @@ fun Author.output() = JsonDict(
|
||||
"publicKey" to publicKey.encoded
|
||||
)
|
||||
|
||||
fun AuthorInfo.Status.output() = name.toLowerCase()
|
||||
fun AuthorInfo.Status.output() = name.lowercase(Locale.US)
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.headless
|
||||
import dagger.Component
|
||||
import org.briarproject.bramble.BrambleCoreEagerSingletons
|
||||
import org.briarproject.bramble.BrambleCoreModule
|
||||
import org.briarproject.bramble.BrambleJavaEagerSingletons
|
||||
import org.briarproject.bramble.BrambleJavaModule
|
||||
import org.briarproject.briar.BriarCoreEagerSingletons
|
||||
import org.briarproject.briar.BriarCoreModule
|
||||
@@ -19,7 +20,7 @@ import javax.inject.Singleton
|
||||
)
|
||||
@Singleton
|
||||
internal interface BriarHeadlessApp : BrambleCoreEagerSingletons, BriarCoreEagerSingletons,
|
||||
HeadlessEagerSingletons {
|
||||
BrambleJavaEagerSingletons, HeadlessEagerSingletons {
|
||||
|
||||
fun getRouter(): Router
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory
|
||||
import org.briarproject.bramble.battery.DefaultBatteryManagerModule
|
||||
import org.briarproject.bramble.event.DefaultEventExecutorModule
|
||||
import org.briarproject.bramble.plugin.tor.MacTorPluginFactory
|
||||
import org.briarproject.bramble.plugin.tor.UnixTorPluginFactory
|
||||
import org.briarproject.bramble.plugin.tor.WindowsTorPluginFactory
|
||||
import org.briarproject.bramble.system.ClockModule
|
||||
@@ -92,10 +93,12 @@ internal class HeadlessModule(private val appDir: File) {
|
||||
@Singleton
|
||||
internal fun providePluginConfig(
|
||||
unixTor: UnixTorPluginFactory,
|
||||
macTor: MacTorPluginFactory,
|
||||
winTor: WindowsTorPluginFactory
|
||||
): PluginConfig {
|
||||
val duplex: List<DuplexPluginFactory> = when {
|
||||
isLinux() || isMac() -> listOf(unixTor)
|
||||
isLinux() -> listOf(unixTor)
|
||||
isMac() -> listOf(macTor)
|
||||
isWindows() -> listOf(winTor)
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.github.ajalt.clikt.parameters.options.option
|
||||
import com.github.ajalt.clikt.parameters.types.int
|
||||
import org.bouncycastle.util.encoders.Base64.toBase64String
|
||||
import org.briarproject.bramble.BrambleCoreEagerSingletons
|
||||
import org.briarproject.bramble.BrambleJavaEagerSingletons
|
||||
import org.briarproject.bramble.util.OsUtils.isLinux
|
||||
import org.briarproject.bramble.util.OsUtils.isMac
|
||||
import org.briarproject.briar.BriarCoreEagerSingletons
|
||||
@@ -28,7 +29,8 @@ import java.util.logging.Level.INFO
|
||||
import java.util.logging.Level.WARNING
|
||||
import java.util.logging.LogManager
|
||||
|
||||
private const val DEFAULT_PORT = 7000
|
||||
// On macOS, port 7000 is used by ControlCenter (probably AirPlay), so use a different port
|
||||
private val DEFAULT_PORT = if (isMac()) 7001 else 7000
|
||||
private val DEFAULT_DATA_DIR = getProperty("user.home") + separator + ".briar"
|
||||
|
||||
private class Main : CliktCommand(
|
||||
@@ -77,6 +79,7 @@ private class Main : CliktCommand(
|
||||
// We need to load the eager singletons directly after making the
|
||||
// dependency graphs
|
||||
BrambleCoreEagerSingletons.Helper.injectEagerSingletons(app)
|
||||
BrambleJavaEagerSingletons.Helper.injectEagerSingletons(app)
|
||||
BriarCoreEagerSingletons.Helper.injectEagerSingletons(app)
|
||||
HeadlessEagerSingletons.Helper.injectEagerSingletons(app)
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.briarproject.bramble.identity.output
|
||||
import org.briarproject.briar.api.blog.BlogPostHeader
|
||||
import org.briarproject.briar.api.blog.MessageType
|
||||
import org.briarproject.briar.headless.json.JsonDict
|
||||
import java.util.Locale
|
||||
|
||||
internal fun BlogPostHeader.output(text: String) = JsonDict(
|
||||
"text" to text,
|
||||
@@ -18,4 +19,4 @@ internal fun BlogPostHeader.output(text: String) = JsonDict(
|
||||
"timestampReceived" to timeReceived
|
||||
)
|
||||
|
||||
internal fun MessageType.output() = name.toLowerCase()
|
||||
internal fun MessageType.output() = name.lowercase(Locale.US)
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.headless
|
||||
import dagger.Component
|
||||
import org.briarproject.bramble.BrambleCoreEagerSingletons
|
||||
import org.briarproject.bramble.BrambleCoreModule
|
||||
import org.briarproject.bramble.BrambleJavaEagerSingletons
|
||||
import org.briarproject.bramble.BrambleJavaModule
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent
|
||||
import org.briarproject.briar.BriarCoreEagerSingletons
|
||||
@@ -20,7 +21,7 @@ import javax.inject.Singleton
|
||||
)
|
||||
@Singleton
|
||||
internal interface BriarHeadlessTestApp : BrambleCoreEagerSingletons, BriarCoreEagerSingletons,
|
||||
HeadlessEagerSingletons {
|
||||
BrambleJavaEagerSingletons, HeadlessEagerSingletons {
|
||||
|
||||
fun getRouter(): Router
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import org.briarproject.bramble.BrambleCoreEagerSingletons
|
||||
import org.briarproject.bramble.BrambleJavaEagerSingletons
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent
|
||||
import org.briarproject.briar.BriarCoreEagerSingletons
|
||||
import org.briarproject.briar.api.test.TestDataCreator
|
||||
@@ -38,6 +39,7 @@ abstract class IntegrationTest {
|
||||
.headlessTestModule(HeadlessTestModule(dataDir))
|
||||
.build()
|
||||
BrambleCoreEagerSingletons.Helper.injectEagerSingletons(app)
|
||||
BrambleJavaEagerSingletons.Helper.injectEagerSingletons(app)
|
||||
BriarCoreEagerSingletons.Helper.injectEagerSingletons(app)
|
||||
HeadlessEagerSingletons.Helper.injectEagerSingletons(app)
|
||||
router = app.getRouter()
|
||||
|
||||
@@ -35,8 +35,8 @@ dependencyVerification {
|
||||
'javax.servlet:javax.servlet-api:3.1.0:javax.servlet-api-3.1.0.jar:af456b2dd41c4e82cf54f3e743bc678973d9fe35bd4d3071fa05c7e5333b8482',
|
||||
'net.bytebuddy:byte-buddy-agent:1.12.6:byte-buddy-agent-1.12.6.jar:9b29421fe4650b75fc3ed53590f914c54f932e334b3506cc00296dff73024183',
|
||||
'net.bytebuddy:byte-buddy:1.12.6:byte-buddy-1.12.6.jar:211918dc24f0fdef4335ce8af40ef5616e15e818b962a21146397c7701eb75a7',
|
||||
'net.java.dev.jna:jna-platform:4.5.2:jna-platform-4.5.2.jar:f1d00c167d8921c6e23c626ef9f1c3ae0be473c95c68ffa012bc7ae55a87e2d6',
|
||||
'net.java.dev.jna:jna:4.5.2:jna-4.5.2.jar:0c8eb7acf67261656d79005191debaba3b6bf5dd60a43735a245429381dbecff',
|
||||
'net.java.dev.jna:jna-platform:5.13.0:jna-platform-5.13.0.jar:474d7b88f6e97009b6ec1d98c3024dd95c23187c65dabfbc35331bcac3d173dd',
|
||||
'net.java.dev.jna:jna:5.13.0:jna-5.13.0.jar:66d4f819a062a51a1d5627bffc23fac55d1677f0e0a1feba144aabdd670a64bb',
|
||||
'net.java.dev.jna:jna:5.6.0:jna-5.6.0.jar:5557e235a8aa2f9766d5dc609d67948f2a8832c2d796cea9ef1d6cbe0b3b7eaf',
|
||||
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
||||
'org.apiguardian:apiguardian-api:1.1.0:apiguardian-api-1.1.0.jar:a9aae9ff8ae3e17a2a18f79175e82b16267c246fbbd3ca9dfbbb290b08dcfdd4',
|
||||
@@ -44,12 +44,15 @@ dependencyVerification {
|
||||
'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b',
|
||||
'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011',
|
||||
'org.briarproject:obfs4proxy-linux:0.0.14-tor2:obfs4proxy-linux-0.0.14-tor2.jar:bb2431092b5ad998ad620b0223e725c0f7e43f1b02af2f097a2544edc1fd9738',
|
||||
'org.briarproject:obfs4proxy-macos:0.0.14-tor2:obfs4proxy-macos-0.0.14-tor2.jar:4a688d3a14d2510dd312213488c8f39ee08e609e47a7300aa12e31ceacb16ce2',
|
||||
'org.briarproject:obfs4proxy-windows:0.0.14-tor2:obfs4proxy-windows-0.0.14-tor2.jar:b5fbd00a8c35ccf095b265370752390e4cd46055331049c4dfcc236dc9c650ac',
|
||||
'org.briarproject:onionwrapper-core:0.0.2:onionwrapper-core-0.0.2.jar:7038e960c9e59803f0e2c19444dbb5214cd99e5a7463c0a01c45318e07a0eb80',
|
||||
'org.briarproject:onionwrapper-java:0.0.2:onionwrapper-java-0.0.2.jar:87a3f4082174dbbd32c4f5f062b46af1d3fedd8cfa1ec84f6ce6ccb6e3674fb6',
|
||||
'org.briarproject:onionwrapper-core:0.0.4:onionwrapper-core-0.0.4.jar:28a01a62e96aa763989a8afc325abd3bee54f8021269f91aa48b247a6e717870',
|
||||
'org.briarproject:onionwrapper-java:0.0.4:onionwrapper-java-0.0.4.jar:7806ef878074498653b557e26eb70e6007df3450d6a910a2e9a322f7eb4df442',
|
||||
'org.briarproject:snowflake-linux:2.5.1:snowflake-linux-2.5.1.jar:edc807dcb7758365970d95525e4749349a27f462d0e2df6505ad1ca65fb296d2',
|
||||
'org.briarproject:snowflake-macos:2.5.1:snowflake-macos-2.5.1.jar:f6d59471d476860950bb639ac318920caa460c4d6d023cbd6547c742949c84f0',
|
||||
'org.briarproject:snowflake-windows:2.5.1:snowflake-windows-2.5.1.jar:700ec9c68dc033f544daa4ca3547c89e523aed66500cf4b3ac51fe017c51e7be',
|
||||
'org.briarproject:tor-linux:0.4.7.13-2:tor-linux-0.4.7.13-2.jar:1e4ca9e0f724e1f17fcce570832704942cc3be26c4c2eccbe5aae29f35afa307',
|
||||
'org.briarproject:tor-macos:0.4.7.13-2:tor-macos-0.4.7.13-2.jar:3d84fbe667584d24275c6a4cb197bafcb0ead890e4d46acac3317debf0cd3351',
|
||||
'org.briarproject:tor-windows:0.4.7.13-2:tor-windows-0.4.7.13-2.jar:3a0aa01ed3103cac0c22a91a6f227ab99f7d32ea970ea41558eece484ab49e88',
|
||||
'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a',
|
||||
'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb',
|
||||
|
||||
Submodule briar-mailbox updated: 64147fd1d0...d887c49ab3
@@ -37,7 +37,7 @@ buildscript {
|
||||
junit_version = "4.13.2"
|
||||
jmock_version = '2.12.0'
|
||||
mockwebserver_version = '4.10.0'
|
||||
onionwrapper_version = '0.0.2'
|
||||
onionwrapper_version = '0.0.4'
|
||||
}
|
||||
dependencies {
|
||||
// upgrading this let's us run into https://github.com/gradle/gradle/issues/20330
|
||||
|
||||
Reference in New Issue
Block a user