Compare commits

..

53 Commits

Author SHA1 Message Date
akwizgran
72136cc627 DO NOT MERGE: Log contact name and alias.
Use the logger. Log the contact name, alias and message status.
2023-07-24 16:53:43 +01:00
Sebastian Kürten
411ace13aa Add some logging to ClientVersioningManagerImpl 2023-07-24 16:09:24 +02:00
akwizgran
952ee42ad1 Merge branch 'blog-txns' into 'master'
Add transactional versions of BlogManager methods and a bug fix

See merge request briar/briar!1802
2023-07-13 20:30:21 +00:00
Torsten Grote
f61b09d5a9 Fix BlogManager tests after last commits 2023-07-13 14:50:05 -03:00
Torsten Grote
8f735d176e Add transactional versions of BlogManager methods 2023-07-13 13:01:30 -03:00
Torsten Grote
c47253fc5f Mark our own reblogs as read automatically 2023-07-13 12:54:17 -03:00
akwizgran
7a0fb74c09 Merge branch '2266-target-sdk-33' into 'master'
Target SDK 33

Closes #2266

See merge request briar/briar!1800
2023-07-03 10:55:46 +00:00
akwizgran
882f536b8d Don't try to get Bluetooth address from settings. 2023-06-30 18:14:12 +01:00
Torsten Grote
74f8e84a9b React to device light idle mode in DozeWatchdog as well 2023-06-29 10:58:32 -03:00
Torsten Grote
23df2a41c2 Add @NotNullByDefault annotation to ConditionManagers 2023-06-29 10:58:32 -03:00
Torsten Grote
c77eaf16d9 Log more mode changes in AndroidBatteryManager 2023-06-29 10:58:31 -03:00
Torsten Grote
9a6bb4b203 Set dozed to true when we are in LowPowerStandby 2023-06-29 10:58:31 -03:00
Torsten Grote
3d237a9104 Introduce tryToStartActivity() helper method 2023-06-29 10:58:31 -03:00
Torsten Grote
fa216ffc6f Move requestEnableWiFi() into AbstractConditionManager 2023-06-29 10:58:31 -03:00
Torsten Grote
a34631d36c Catch ActivityNotFoundException in places where we launch external intents 2023-06-29 10:58:31 -03:00
Torsten Grote
45cda191e5 Log changes to DeviceLightIdleMode in AndroidBatteryManager 2023-06-29 10:58:31 -03:00
Torsten Grote
2495b6f5c0 Add LowPowerStandby stub to DozeWatchdogImpl 2023-06-29 10:58:31 -03:00
Torsten Grote
03fc504f7d Log changes to LowPowerStandby in AndroidBatteryManager 2023-06-29 10:58:30 -03:00
Torsten Grote
d19062e319 Don't disable hotspot start button after click
to avoid issues when coming back to the screen after granting permissions.
2023-06-29 10:58:30 -03:00
Torsten Grote
fdb429ab7a Ask for NEARBY_WIFI_DEVICES permission on SDK 33 and up 2023-06-29 10:58:30 -03:00
Torsten Grote
d0c59a6d75 Target SDK 33 and ask for notification permission
when creating account and when signing in
2023-06-29 10:58:30 -03:00
akwizgran
3bb39c2aa3 Merge branch 'fix-macos-x86-issue' into 'master'
Fix architecture detection for macOS Intel CPUs

See merge request briar/briar!1799
2023-06-28 11:07:26 +00:00
Sebastian Kürten
917fc5e5b6 Fix architecture detection for macOS Intel CPUs 2023-06-28 12:57:20 +02:00
akwizgran
caa078585b Merge branch 'macos3' into 'master'
macOS support

See merge request briar/briar!1790
2023-06-22 17:04:09 +00:00
akwizgran
e68c0c7f4b Merge branch 'onionwrapper-0.0.4' into 'master'
Upgrade onionwrapper to 0.0.4

See merge request briar/briar!1798
2023-06-22 12:26:21 +00:00
akwizgran
a6b3749fb6 Extend comment explaining TorState -> State mapping. 2023-06-22 13:12:58 +01:00
Torsten Grote
a8f6e8e4bd Merge branch 'check-network-status-periodically' into 'master'
Check network status periodically

See merge request briar/briar!1797
2023-06-21 13:32:39 +00:00
akwizgran
4d884601f0 Check more often, only broadcast status if changed. 2023-06-20 17:01:45 +01:00
akwizgran
b71198d9b1 Check network status periodically on JavaSE. 2023-06-20 16:34:45 +01:00
Sebastian Kürten
079c6e0475 Add comment why we choose a differnt port for headless on macOS 2023-06-20 12:21:11 +02:00
Sebastian Kürten
3a0f8ed85c Document building of macOS headless jars and fix included native binaries on macOS 2023-06-15 18:07:18 +02:00
Sebastian Kürten
57f7501780 macOS support 2023-06-15 12:48:01 +02:00
akwizgran
3cc5699fe0 Upgrade onionwrapper to 0.0.4. 2023-06-14 17:06:10 +01:00
akwizgran
7d761710e6 Bump version numbers for 1.5.4 release. 2023-06-02 13:53:20 +01:00
Torsten Grote
7461d3c943 Merge branch '2434-use-us-locale-for-lowercasing-onion-hostname' into 'master'
Use US locale for lowercasing onion hostname

Closes #2434

See merge request briar/briar!1796
2023-05-31 16:27:24 +00:00
akwizgran
9291613175 Fix some other uses of toLowerCase() without a locale. 2023-05-30 22:06:18 +01:00
akwizgran
ce6739a9fd Use US locale for lowercasing onion hostname. 2023-05-30 22:00:41 +01:00
akwizgran
1f1a97f62d Bump version numbers for 1.5.3 release. 2023-05-24 11:33:49 +01:00
akwizgran
7a33d26533 Merge branch 'new-handshake' 2023-05-24 11:22:23 +01:00
akwizgran
f2c85f37be Merge branch '2391-share-link' into 'master'
Share a link to the Briar download page via another app

Closes #2391

See merge request briar/briar!1795
2023-05-22 15:35:43 +00:00
Torsten Grote
8e3fa872fd Move About settings item to the bottom 2023-05-17 08:53:24 -03:00
akwizgran
0d1e81ebdb Merge branch 'use-default-secure-random-provider-on-macos' into 'master'
Use system default secure random provider on macOS

Closes briar-desktop#132

See merge request briar/briar!1794
2023-05-17 09:25:42 +00:00
Sebastian Kürten
bded4e7bc8 Use system default secure random provider on macOS 2023-05-17 11:13:43 +02:00
Torsten Grote
bf1a5cf218 Allow sharing download link for Briar from settings actions 2023-05-16 16:55:19 -03:00
akwizgran
dd7a638984 Merge branch 'fa-string-fix' into 'master'
Fix translation

See merge request briar/briar!1793
2023-05-16 10:38:47 +00:00
paul
942222131e Fix translation. 2023-05-15 21:48:02 +00:00
akwizgran
643757e407 Bump version numbers for 1.5.2 release. 2023-05-15 15:48:50 +01:00
akwizgran
7c530ad7a3 Update translations. 2023-05-15 15:48:00 +01:00
Torsten Grote
23b2dfa4a8 Merge branch 'conversationmanager-txn' into 'master'
Add transactional versions to delete message functions

See merge request briar/briar!1792
2023-05-15 12:03:53 +00:00
ialokim
ce10e6770f add transactional versions to delete message functions 2023-05-12 23:58:59 +02:00
akwizgran
4a4b04bec3 Rename version constant. 2023-04-26 17:10:23 +01:00
akwizgran
462f57c966 Upgrade handshake protocol to new key agreement method. 2023-03-10 16:05:59 +00:00
akwizgran
8d20c5d8b8 Reify RecordPredicate for easier testing. 2023-03-10 15:15:29 +00:00
100 changed files with 1504 additions and 358 deletions

View File

@@ -12,9 +12,9 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 33
versionCode 10501 versionCode 10504
versionName "1.5.1" versionName "1.5.4"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -5,6 +5,7 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.PowerManager;
import org.briarproject.bramble.api.battery.BatteryManager; import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.battery.event.BatteryEvent; import org.briarproject.bramble.api.battery.event.BatteryEvent;
@@ -16,10 +17,17 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.RequiresApi;
import static android.content.Intent.ACTION_BATTERY_CHANGED; import static android.content.Intent.ACTION_BATTERY_CHANGED;
import static android.content.Intent.ACTION_POWER_CONNECTED; import static android.content.Intent.ACTION_POWER_CONNECTED;
import static android.content.Intent.ACTION_POWER_DISCONNECTED; import static android.content.Intent.ACTION_POWER_DISCONNECTED;
import static android.os.BatteryManager.EXTRA_PLUGGED; 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.Level.INFO;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
@@ -57,6 +65,12 @@ class AndroidBatteryManager implements BatteryManager, Service {
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_POWER_CONNECTED); filter.addAction(ACTION_POWER_CONNECTED);
filter.addAction(ACTION_POWER_DISCONNECTED); 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); appContext.registerReceiver(batteryReceiver, filter);
} }
@@ -76,6 +90,33 @@ class AndroidBatteryManager implements BatteryManager, Service {
eventBus.broadcast(new BatteryEvent(true)); eventBus.broadcast(new BatteryEvent(true));
else if (ACTION_POWER_DISCONNECTED.equals(action)) else if (ACTION_POWER_DISCONNECTED.equals(action))
eventBus.broadcast(new BatteryEvent(false)); 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);
}
} }

View File

@@ -63,10 +63,12 @@ public class AndroidUtils {
return new Pair<>(address, "adapter"); return new Pair<>(address, "adapter");
} }
// Return the address from settings if it's valid and not fake // Return the address from settings if it's valid and not fake
address = Settings.Secure.getString(ctx.getContentResolver(), if (SDK_INT < 33) {
"bluetooth_address"); address = Settings.Secure.getString(ctx.getContentResolver(),
if (isValidBluetoothAddress(address)) { "bluetooth_address");
return new Pair<>(address, "settings"); if (isValidBluetoothAddress(address)) {
return new Pair<>(address, "settings");
}
} }
// Try to get the address via reflection // Try to get the address via reflection
address = getBluetoothAddressByReflection(adapter); address = getBluetoothAddressByReflection(adapter);

View File

@@ -29,8 +29,8 @@ dependencyVerification {
'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b', 'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b',
'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011', '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:obfs4proxy-android:0.0.14-tor2:obfs4proxy-android-0.0.14-tor2.jar:a0a93770d6760ce57d9dbd31cc7177687374e00c3361dac22ab75e3b6e0f289e',
'org.briarproject:onionwrapper-android:0.0.3:onionwrapper-android-0.0.3.aar:d761854dac454616b3e0ca099b2cd17060365ce4316afe495cc7ae86b6c81d15', 'org.briarproject:onionwrapper-android:0.0.4:onionwrapper-android-0.0.4.aar:d761854dac454616b3e0ca099b2cd17060365ce4316afe495cc7ae86b6c81d15',
'org.briarproject:onionwrapper-core:0.0.3:onionwrapper-core-0.0.3.jar:2645dd7e4f2e73fc2ba30696aeac4c960ef8b8f405d85e529fdf8b105ef374f1', '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: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.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', 'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a',

View File

@@ -54,6 +54,38 @@ public interface CryptoComponent {
KeyPair ourKeyPair, byte[]... inputs) KeyPair ourKeyPair, byte[]... inputs)
throws GeneralSecurityException; 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. * Derives a shared secret from two static and two ephemeral key pairs.
* *

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.api.network;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@@ -27,4 +28,20 @@ public class NetworkStatus {
public boolean isIpv6Only() { public boolean isIpv6Only() {
return ipv6Only; 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;
}
} }

View File

@@ -32,8 +32,15 @@ public interface RecordReader {
* 'accept' or 'ignore' predicates * 'accept' or 'ignore' predicates
*/ */
@Nullable @Nullable
Record readRecord(Predicate<Record> accept, Predicate<Record> ignore) Record readRecord(RecordPredicate accept, RecordPredicate ignore)
throws IOException; throws IOException;
void close() throws IOException; void close() throws IOException;
/**
* Interface that reifies the generic interface {@code Predicate<Record>}
* for easier testing.
*/
interface RecordPredicate extends Predicate<Record> {
}
} }

View File

@@ -6,6 +6,7 @@ import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.Locale;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -51,7 +52,7 @@ public class PrivacyUtils {
} }
private static String scrubIpv6Address(byte[] ipv6) { 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); return hex.substring(0, 2) + "[scrubbed]" + hex.substring(30);
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.contact; package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactExchangeManager; 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.properties.TransportPropertyManager;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; 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.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriter; import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory; import org.briarproject.bramble.api.record.RecordWriterFactory;
@@ -61,12 +61,12 @@ class ContactExchangeManagerImpl implements ContactExchangeManager {
getLogger(ContactExchangeManagerImpl.class.getName()); getLogger(ContactExchangeManagerImpl.class.getName());
// Accept records with current protocol version, known record type // 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 && r.getProtocolVersion() == PROTOCOL_VERSION &&
isKnownRecordType(r.getRecordType()); isKnownRecordType(r.getRecordType());
// Ignore records with current protocol version, unknown record type // 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 && r.getProtocolVersion() == PROTOCOL_VERSION &&
!isKnownRecordType(r.getRecordType()); !isKnownRecordType(r.getRecordType());

View File

@@ -5,14 +5,31 @@ import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
interface HandshakeConstants { 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. * Label for deriving Alice's proof of ownership from the master key.

View File

@@ -13,11 +13,26 @@ interface HandshakeCrypto {
KeyPair generateEphemeralKeyPair(); 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 * @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, PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
KeyPair ourEphemeralKeyPair, boolean alice) KeyPair ourEphemeralKeyPair, boolean alice)
throws GeneralSecurityException; throws GeneralSecurityException;

View File

@@ -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.ALICE_PROOF_LABEL;
import static org.briarproject.bramble.contact.HandshakeConstants.BOB_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 @Immutable
@NotNullByDefault @NotNullByDefault
@@ -32,7 +33,8 @@ class HandshakeCryptoImpl implements HandshakeCrypto {
} }
@Override @Override
public SecretKey deriveMasterKey(PublicKey theirStaticPublicKey, @Deprecated
public SecretKey deriveMasterKey_0_0(PublicKey theirStaticPublicKey,
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair, PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
KeyPair ourEphemeralKeyPair, boolean alice) throws KeyPair ourEphemeralKeyPair, boolean alice) throws
GeneralSecurityException { GeneralSecurityException {
@@ -46,9 +48,29 @@ class HandshakeCryptoImpl implements HandshakeCrypto {
alice ? ourEphemeral : theirEphemeral, alice ? ourEphemeral : theirEphemeral,
alice ? theirEphemeral : ourEphemeral alice ? theirEphemeral : ourEphemeral
}; };
return crypto.deriveSharedSecret(MASTER_KEY_LABEL, theirStaticPublicKey, return crypto.deriveSharedSecretBadly(MASTER_KEY_LABEL_0_0,
theirEphemeralPublicKey, ourStaticKeyPair, ourEphemeralKeyPair, theirStaticPublicKey, theirEphemeralPublicKey,
alice, inputs); 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 @Override

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair; 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.ContactManager;
import org.briarproject.bramble.api.contact.HandshakeManager; import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.contact.PendingContact; 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.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.TransportCrypto; 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.DbException;
import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; 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.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriter; import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory; import org.briarproject.bramble.api.record.RecordWriterFactory;
@@ -28,15 +27,20 @@ import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.List;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; 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.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_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.HandshakeConstants.PROTOCOL_MAJOR_VERSION;
import static org.briarproject.bramble.contact.HandshakeRecordTypes.EPHEMERAL_PUBLIC_KEY; import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MINOR_VERSION;
import static org.briarproject.bramble.contact.HandshakeRecordTypes.PROOF_OF_OWNERSHIP; 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; import static org.briarproject.bramble.util.ValidationUtils.checkLength;
@Immutable @Immutable
@@ -44,12 +48,14 @@ import static org.briarproject.bramble.util.ValidationUtils.checkLength;
class HandshakeManagerImpl implements HandshakeManager { class HandshakeManagerImpl implements HandshakeManager {
// Ignore records with current protocol version, unknown record type // 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 && r.getProtocolVersion() == PROTOCOL_MAJOR_VERSION &&
!isKnownRecordType(r.getRecordType()); !isKnownRecordType(r.getRecordType());
private static boolean isKnownRecordType(byte type) { 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; private final TransactionManager db;
@@ -61,7 +67,7 @@ class HandshakeManagerImpl implements HandshakeManager {
private final RecordWriterFactory recordWriterFactory; private final RecordWriterFactory recordWriterFactory;
@Inject @Inject
HandshakeManagerImpl(DatabaseComponent db, HandshakeManagerImpl(TransactionManager db,
IdentityManager identityManager, IdentityManager identityManager,
ContactManager contactManager, ContactManager contactManager,
TransportCrypto transportCrypto, TransportCrypto transportCrypto,
@@ -95,19 +101,31 @@ class HandshakeManagerImpl implements HandshakeManager {
.createRecordWriter(out.getOutputStream()); .createRecordWriter(out.getOutputStream());
KeyPair ourEphemeralKeyPair = KeyPair ourEphemeralKeyPair =
handshakeCrypto.generateEphemeralKeyPair(); handshakeCrypto.generateEphemeralKeyPair();
PublicKey theirEphemeralPublicKey; Pair<Byte, PublicKey> theirMinorVersionAndKey;
if (alice) { if (alice) {
sendMinorVersion(recordWriter);
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic()); sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
theirEphemeralPublicKey = receivePublicKey(recordReader); theirMinorVersionAndKey = receiveMinorVersionAndKey(recordReader);
} else { } else {
theirEphemeralPublicKey = receivePublicKey(recordReader); theirMinorVersionAndKey = receiveMinorVersionAndKey(recordReader);
sendMinorVersion(recordWriter);
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic()); sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
} }
byte theirMinorVersion = theirMinorVersionAndKey.getFirst();
PublicKey theirEphemeralPublicKey = theirMinorVersionAndKey.getSecond();
SecretKey masterKey; SecretKey masterKey;
try { try {
masterKey = handshakeCrypto.deriveMasterKey(theirStaticPublicKey, if (theirMinorVersion > 0) {
theirEphemeralPublicKey, ourStaticKeyPair, masterKey = handshakeCrypto.deriveMasterKey_0_1(
ourEphemeralKeyPair, alice); 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) { } catch (GeneralSecurityException e) {
throw new FormatException(); throw new FormatException();
} }
@@ -128,34 +146,91 @@ class HandshakeManagerImpl implements HandshakeManager {
} }
private void sendPublicKey(RecordWriter w, PublicKey k) throws IOException { private void sendPublicKey(RecordWriter w, PublicKey k) throws IOException {
w.writeRecord(new Record(PROTOCOL_VERSION, EPHEMERAL_PUBLIC_KEY, w.writeRecord(new Record(PROTOCOL_MAJOR_VERSION,
k.getEncoded())); RECORD_TYPE_EPHEMERAL_PUBLIC_KEY, k.getEncoded()));
w.flush(); 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); checkLength(key, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES);
return new AgreementPublicKey(key); return new AgreementPublicKey(key);
} }
private void sendProof(RecordWriter w, byte[] proof) throws IOException { 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(); w.flush();
} }
private byte[] receiveProof(RecordReader r) throws IOException { 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); checkLength(proof, PROOF_BYTES, PROOF_BYTES);
return proof; 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 { throws IOException {
// Accept records with current protocol version, expected type only // Accept records with current protocol version, expected types only
Predicate<Record> accept = rec -> RecordPredicate accept = rec ->
rec.getProtocolVersion() == PROTOCOL_VERSION && rec.getProtocolVersion() == PROTOCOL_MAJOR_VERSION &&
rec.getRecordType() == expectedType; expectedTypes.contains(rec.getRecordType());
Record rec = r.readRecord(accept, IGNORE); Record rec = r.readRecord(accept, IGNORE);
if (rec == null) throw new EOFException(); if (rec == null) throw new EOFException();
return rec; return rec;

View File

@@ -5,7 +5,9 @@ package org.briarproject.bramble.contact;
*/ */
interface HandshakeRecordTypes { 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;
} }

View File

@@ -34,6 +34,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.Provider; import java.security.Provider;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.Security; import java.security.Security;
import java.util.Locale;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -222,7 +223,8 @@ class CryptoComponentImpl implements CryptoComponent {
} }
@Override @Override
public SecretKey deriveSharedSecret(String label, @Deprecated
public SecretKey deriveSharedSecretBadly(String label,
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey, PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair, KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
boolean alice, byte[]... inputs) throws GeneralSecurityException { boolean alice, byte[]... inputs) throws GeneralSecurityException {
@@ -250,6 +252,35 @@ class CryptoComponentImpl implements CryptoComponent {
return new SecretKey(hash); 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 @Override
public byte[] sign(String label, byte[] toSign, PrivateKey privateKey) public byte[] sign(String label, byte[] toSign, PrivateKey privateKey)
throws GeneralSecurityException { throws GeneralSecurityException {
@@ -470,7 +501,7 @@ class CryptoComponentImpl implements CryptoComponent {
arraycopy(publicKey, 0, address, 0, publicKey.length); arraycopy(publicKey, 0, address, 0, publicKey.length);
arraycopy(checksum, 0, address, publicKey.length, ONION_CHECKSUM_BYTES); arraycopy(checksum, 0, address, publicKey.length, ONION_CHECKSUM_BYTES);
address[address.length - 1] = ONION_HS_PROTOCOL_VERSION; address[address.length - 1] = ONION_HS_PROTOCOL_VERSION;
return Base32.encode(address).toLowerCase(); return Base32.encode(address).toLowerCase(Locale.US);
} }
} }

View File

@@ -1,11 +1,11 @@
package org.briarproject.bramble.keyagreement; package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection; import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; 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.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriter; import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory; import org.briarproject.bramble.api.record.RecordWriterFactory;
@@ -34,12 +34,12 @@ class KeyAgreementTransport {
Logger.getLogger(KeyAgreementTransport.class.getName()); Logger.getLogger(KeyAgreementTransport.class.getName());
// Accept records with current protocol version, known record type // 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 && r.getProtocolVersion() == PROTOCOL_VERSION &&
isKnownRecordType(r.getRecordType()); isKnownRecordType(r.getRecordType());
// Ignore records with current protocol version, unknown record type // 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 && r.getProtocolVersion() == PROTOCOL_VERSION &&
!isKnownRecordType(r.getRecordType()); !isKnownRecordType(r.getRecordType());

View File

@@ -630,7 +630,9 @@ class TorPlugin implements DuplexPlugin, EventListener {
private synchronized State getState(TorState torState) { private synchronized State getState(TorState torState) {
// Treat TorState.STARTED as State.STARTING_STOPPING because it's // Treat TorState.STARTED as State.STARTING_STOPPING because it's
// only seen during startup, before TorWrapper#enableNetwork() is // only seen during startup, before TorWrapper#enableNetwork() is
// called for the first time // 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 || if (torState == TorState.NOT_STARTED ||
torState == TorState.STARTING || torState == TorState.STARTING ||
torState == TorState.STARTED || torState == TorState.STARTED ||

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.record; package org.briarproject.bramble.record;
import org.briarproject.bramble.api.FormatException; 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.Record;
import org.briarproject.bramble.api.record.RecordReader; import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.util.ByteUtils; import org.briarproject.bramble.util.ByteUtils;
@@ -45,7 +44,7 @@ class RecordReaderImpl implements RecordReader {
@Nullable @Nullable
@Override @Override
public Record readRecord(Predicate<Record> accept, Predicate<Record> ignore) public Record readRecord(RecordPredicate accept, RecordPredicate ignore)
throws IOException { throws IOException {
while (true) { while (true) {
if (eof()) return null; if (eof()) return null;

View File

@@ -1,10 +1,10 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; 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.Ack;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageFactory; 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 { class SyncRecordReaderImpl implements SyncRecordReader {
// Accept records with current protocol version, known record type // 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 && r.getProtocolVersion() == PROTOCOL_VERSION &&
isKnownRecordType(r.getRecordType()); isKnownRecordType(r.getRecordType());
// Ignore records with current protocol version, unknown record type // 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 && r.getProtocolVersion() == PROTOCOL_VERSION &&
!isKnownRecordType(r.getRecordType()); !isKnownRecordType(r.getRecordType());

View File

@@ -22,6 +22,7 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.InvalidMessageException; import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook; import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientMajorVersion; import org.briarproject.bramble.api.versioning.ClientMajorVersion;
@@ -36,17 +37,20 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.emptyList; 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.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; 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, class ClientVersioningManagerImpl implements ClientVersioningManager,
Service, OpenDatabaseHook, ContactHook, IncomingMessageHook { Service, OpenDatabaseHook, ContactHook, IncomingMessageHook {
private static final Logger LOG =
getLogger(ClientVersioningManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final ClientHelper clientHelper; private final ClientHelper clientHelper;
private final ContactGroupFactory contactGroupFactory; private final ContactGroupFactory contactGroupFactory;
@@ -128,12 +135,68 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
@Override @Override
public void onDatabaseOpened(Transaction txn) throws DbException { 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; if (db.containsGroup(txn, localGroup.getId())) return;
db.addGroup(txn, localGroup); db.addGroup(txn, localGroup);
// Set things up for any pre-existing contacts // Set things up for any pre-existing contacts
for (Contact c : db.getContacts(txn)) addingContact(txn, c); 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 @Override
public void startService() throws ServiceException { public void startService() throws ServiceException {
List<ClientVersion> versions = new ArrayList<>(clients); List<ClientVersion> versions = new ArrayList<>(clients);

View File

@@ -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));
}});
}
}

View File

@@ -13,6 +13,7 @@ import org.jmock.Expectations;
import org.junit.Test; import org.junit.Test;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Locale;
import static java.lang.System.arraycopy; import static java.lang.System.arraycopy;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES; import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES;
@@ -174,7 +175,7 @@ public class PendingContactFactoryImplTest extends BrambleMockTestCase {
rawLink[0] = (byte) formatVersion; rawLink[0] = (byte) formatVersion;
byte[] publicKeyBytes = publicKey.getEncoded(); byte[] publicKeyBytes = publicKey.getEncoded();
arraycopy(publicKeyBytes, 0, rawLink, 1, publicKeyBytes.length); 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()); assertEquals(BASE32_LINK_BYTES, base32.length());
return base32; return base32;
} }

View File

@@ -60,6 +60,22 @@ public class KeyAgreementTest extends BrambleTestCase {
assertArrayEquals(aShared.getBytes(), bShared.getBytes()); 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 @Test
public void testDerivesStaticEphemeralSharedSecret() throws Exception { public void testDerivesStaticEphemeralSharedSecret() throws Exception {
String label = getRandomString(123); String label = getRandomString(123);

View File

@@ -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());
}
}
}

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.keyagreement; package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection; import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.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.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; 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.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriter; import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory; import org.briarproject.bramble.api.record.RecordWriterFactory;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction; import org.briarproject.bramble.test.CaptureArgumentAction;
import org.briarproject.bramble.test.PredicateMatcher;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.jmock.imposters.ByteBuddyClassImposteriser; import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.junit.Test; import org.junit.Test;
@@ -21,8 +22,6 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicReference; 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.KeyAgreementConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT; import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT;
import static org.briarproject.bramble.api.keyagreement.RecordTypes.CONFIRM; import static org.briarproject.bramble.api.keyagreement.RecordTypes.CONFIRM;
@@ -119,7 +118,7 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
public void testReceiveKeyThrowsExceptionIfAtEndOfStream() public void testReceiveKeyThrowsExceptionIfAtEndOfStream()
throws Exception { throws Exception {
setup(); setup();
expectReadRecord(null); expectReadEof();
kat.receiveKey(); kat.receiveKey();
} }
@@ -148,7 +147,7 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
public void testReceiveConfirmThrowsExceptionIfAtEndOfStream() public void testReceiveConfirmThrowsExceptionIfAtEndOfStream()
throws Exception { throws Exception {
setup(); setup();
expectReadRecord(null); expectReadEof();
kat.receiveConfirm(); kat.receiveConfirm();
} }
@@ -209,12 +208,22 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
assertArrayEquals(expectedPayload, actual.getPayload()); assertArrayEquals(expectedPayload, actual.getPayload());
} }
private void expectReadRecord(@Nullable Record record) throws Exception { private void expectReadRecord(Record record) throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
//noinspection unchecked // Test that the `accept` predicate passed to the reader would
oneOf(recordReader).readRecord(with(any(Predicate.class)), // accept the expected record
with(any(Predicate.class))); oneOf(recordReader).readRecord(with(new PredicateMatcher<>(
RecordPredicate.class, rp -> rp.test(record))),
with(any(RecordPredicate.class)));
will(returnValue(record)); 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));
}});
}
} }

View File

@@ -1,9 +1,9 @@
package org.briarproject.bramble.record; package org.briarproject.bramble.record;
import org.briarproject.bramble.api.FormatException; 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.Record;
import org.briarproject.bramble.api.record.RecordReader; 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.test.BrambleTestCase;
import org.briarproject.bramble.util.ByteUtils; import org.briarproject.bramble.util.ByteUtils;
import org.junit.Test; import org.junit.Test;
@@ -128,12 +128,12 @@ public class RecordReaderImplTest extends BrambleTestCase {
RecordReader reader = new RecordReaderImpl(in); RecordReader reader = new RecordReaderImpl(in);
// Accept records with version 0, type 0 or 1 // Accept records with version 0, type 0 or 1
Predicate<Record> accept = r -> { RecordPredicate accept = r -> {
byte version = r.getProtocolVersion(), type = r.getRecordType(); byte version = r.getProtocolVersion(), type = r.getRecordType();
return version == 0 && (type == 0 || type == 1); return version == 0 && (type == 0 || type == 1);
}; };
// Ignore records with version 0, any other type // Ignore records with version 0, any other type
Predicate<Record> ignore = r -> { RecordPredicate ignore = r -> {
byte version = r.getProtocolVersion(), type = r.getRecordType(); byte version = r.getProtocolVersion(), type = r.getRecordType();
return version == 0 && !(type == 0 || type == 1); return version == 0 && !(type == 0 || type == 1);
}; };
@@ -183,12 +183,12 @@ public class RecordReaderImplTest extends BrambleTestCase {
RecordReader reader = new RecordReaderImpl(in); RecordReader reader = new RecordReaderImpl(in);
// Accept records with version 0, type 0 or 1 // Accept records with version 0, type 0 or 1
Predicate<Record> accept = r -> { RecordPredicate accept = r -> {
byte version = r.getProtocolVersion(), type = r.getRecordType(); byte version = r.getProtocolVersion(), type = r.getRecordType();
return version == 0 && (type == 0 || type == 1); return version == 0 && (type == 0 || type == 1);
}; };
// Ignore records with version 0, any other type // Ignore records with version 0, any other type
Predicate<Record> ignore = r -> { RecordPredicate ignore = r -> {
byte version = r.getProtocolVersion(), type = r.getRecordType(); byte version = r.getProtocolVersion(), type = r.getRecordType();
return version == 0 && !(type == 0 || type == 1); return version == 0 && !(type == 0 || type == 1);
}; };

View File

@@ -1,10 +1,10 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; 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.Ack;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
@@ -24,8 +24,6 @@ import org.junit.Test;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.util.List; 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.record.Record.MAX_RECORD_PAYLOAD_BYTES;
import static org.briarproject.bramble.api.sync.RecordTypes.ACK; import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE; import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
@@ -186,7 +184,7 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
@Test @Test
public void testEofReturnsTrueWhenAtEndOfStream() throws Exception { public void testEofReturnsTrueWhenAtEndOfStream() throws Exception {
expectReadRecord(createAck()); expectReadRecord(createAck());
expectReadRecord(null); expectReadEof();
SyncRecordReader reader = SyncRecordReader reader =
new SyncRecordReaderImpl(messageFactory, recordReader); 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() {{ context.checking(new Expectations() {{
//noinspection unchecked // Test that the `accept` predicate passed to the reader would
oneOf(recordReader).readRecord(with(any(Predicate.class)), // accept the expected record
with(any(Predicate.class))); oneOf(recordReader).readRecord(with(new PredicateMatcher<>(
RecordPredicate.class, rp -> rp.test(record))),
with(any(RecordPredicate.class)));
will(returnValue(record)); 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) { private Record createMessage(int payloadLength) {
return new Record(PROTOCOL_VERSION, MESSAGE, new byte[payloadLength]); return new Record(PROTOCOL_VERSION, MESSAGE, new byte[payloadLength]);
} }

View File

@@ -36,7 +36,7 @@ dependencyVerification {
'org.bouncycastle:bcprov-jdk15to18:1.71:bcprov-jdk15to18-1.71.jar:143aaa4a40edd5fc2a18db7900059f6c16f4d931b94b94b20f7e2238e6662886', 'org.bouncycastle:bcprov-jdk15to18:1.71:bcprov-jdk15to18-1.71.jar:143aaa4a40edd5fc2a18db7900059f6c16f4d931b94b94b20f7e2238e6662886',
'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b', 'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b',
'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011', 'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011',
'org.briarproject:onionwrapper-core:0.0.3:onionwrapper-core-0.0.3.jar:2645dd7e4f2e73fc2ba30696aeac4c960ef8b8f405d85e529fdf8b105ef374f1', '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.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-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.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb',

View File

@@ -11,7 +11,7 @@ dependencies {
implementation project(':bramble-core') implementation project(':bramble-core')
implementation fileTree(dir: 'libs', include: '*.jar') 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:$jna_version"
implementation "net.java.dev.jna:jna-platform:$jna_version" implementation "net.java.dev.jna:jna-platform:$jna_version"
implementation "org.briarproject:onionwrapper-java:$onionwrapper_version" implementation "org.briarproject:onionwrapper-java:$onionwrapper_version"

View File

@@ -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());
}
}
}

View File

@@ -1,33 +1,51 @@
package org.briarproject.bramble.network; 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.NetworkManager;
import org.briarproject.bramble.api.network.NetworkStatus; import org.briarproject.bramble.api.network.NetworkStatus;
import org.briarproject.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.nullsafety.NotNullByDefault;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.list; 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.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces; import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
@MethodsNotNullByDefault @NotNullByDefault
@ParametersNotNullByDefault class JavaNetworkManager implements NetworkManager, Service {
class JavaNetworkManager implements NetworkManager {
private static final Logger LOG = private static final Logger LOG =
getLogger(JavaNetworkManager.class.getName()); getLogger(JavaNetworkManager.class.getName());
private final TaskScheduler scheduler;
private final Executor ioExecutor;
private final EventBus eventBus;
private final AtomicReference<NetworkStatus> lastStatus =
new AtomicReference<>();
@Inject @Inject
JavaNetworkManager() { JavaNetworkManager(TaskScheduler scheduler,
@IoExecutor Executor ioExecutor,
EventBus eventBus) {
this.scheduler = scheduler;
this.ioExecutor = ioExecutor;
this.eventBus = eventBus;
} }
@Override @Override
@@ -48,7 +66,29 @@ class JavaNetworkManager implements NetworkManager {
} catch (SocketException e) { } catch (SocketException e) {
logException(LOG, WARNING, 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); 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() {
}
} }

View File

@@ -1,7 +1,9 @@
package org.briarproject.bramble.network; package org.briarproject.bramble.network;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
@@ -10,9 +12,16 @@ import dagger.Provides;
@Module @Module
public class JavaNetworkModule { public class JavaNetworkModule {
public static class EagerSingletons {
@Inject
NetworkManager networkManager;
}
@Provides @Provides
@Singleton @Singleton
NetworkManager provideNetworkManager(JavaNetworkManager networkManager) { NetworkManager provideNetworkManager(LifecycleManager lifecycleManager,
JavaNetworkManager networkManager) {
lifecycleManager.registerService(networkManager);
return networkManager; return networkManager;
} }
} }

View File

@@ -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);
}
}

View File

@@ -16,7 +16,7 @@ public class DesktopSecureRandomModule {
@Provides @Provides
@Singleton @Singleton
SecureRandomProvider provideSecureRandomProvider() { SecureRandomProvider provideSecureRandomProvider() {
if (isLinux() || isMac()) return new UnixSecureRandomProvider(); if (isLinux()) return new UnixSecureRandomProvider();
return () -> null; // Use system default return () -> null; // Use system default
} }
} }

View File

@@ -20,15 +20,15 @@ dependencyVerification {
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3', '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.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-platform:5.13.0:jna-platform-5.13.0.jar:474d7b88f6e97009b6ec1d98c3024dd95c23187c65dabfbc35331bcac3d173dd',
'net.java.dev.jna:jna:4.5.2:jna-4.5.2.jar:0c8eb7acf67261656d79005191debaba3b6bf5dd60a43735a245429381dbecff', '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.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd', '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.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b', 'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b',
'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011', 'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011',
'org.briarproject:onionwrapper-core:0.0.3:onionwrapper-core-0.0.3.jar:2645dd7e4f2e73fc2ba30696aeac4c960ef8b8f405d85e529fdf8b105ef374f1', 'org.briarproject:onionwrapper-core:0.0.4:onionwrapper-core-0.0.4.jar:28a01a62e96aa763989a8afc325abd3bee54f8021269f91aa48b247a6e717870',
'org.briarproject:onionwrapper-java:0.0.3:onionwrapper-java-0.0.3.jar:25e3a28010e054d7976ff05b8bfed0f25fb60e748a38aa3236451fc4339955e8', '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-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.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', 'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',

View File

@@ -25,9 +25,9 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 33
versionCode 10501 versionCode 10504
versionName "1.5.1" versionName "1.5.4"
applicationId "org.briarproject.briar.android" applicationId "org.briarproject.briar.android"
buildConfigField "String", "TorVersion", "\"$tor_version\"" buildConfigField "String", "TorVersion", "\"$tor_version\""

View File

@@ -19,6 +19,10 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_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.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.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
@@ -30,8 +34,10 @@
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" android:maxSdkVersion="18"
tools:ignore="ScopedStorage" /> 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.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission-sdk-23 android:name="android.permission.USE_BIOMETRIC" /> <uses-permission-sdk-23 android:name="android.permission.USE_BIOMETRIC" />
<uses-permission-sdk-23 android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission-sdk-23 android:name="android.permission.FOREGROUND_SERVICE" />

View File

@@ -7,17 +7,26 @@ import android.content.IntentFilter;
import android.os.PowerManager; import android.os.PowerManager;
import org.briarproject.bramble.api.lifecycle.Service; import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.briar.api.android.DozeWatchdog; import org.briarproject.briar.api.android.DozeWatchdog;
import java.util.concurrent.atomic.AtomicBoolean; 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.content.Context.POWER_SERVICE;
import static android.os.Build.VERSION.SDK_INT; 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_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 { class DozeWatchdogImpl implements DozeWatchdog, Service {
private static final Logger LOG =
getLogger(DozeWatchdogImpl.class.getName());
private final Context appContext; private final Context appContext;
private final AtomicBoolean dozed = new AtomicBoolean(false); private final AtomicBoolean dozed = new AtomicBoolean(false);
private final BroadcastReceiver receiver = new DozeBroadcastReceiver(); private final BroadcastReceiver receiver = new DozeBroadcastReceiver();
@@ -32,14 +41,18 @@ class DozeWatchdogImpl implements DozeWatchdog, Service {
} }
@Override @Override
public void startService() throws ServiceException { public void startService() {
if (SDK_INT < 23) return; if (SDK_INT < 23) return;
IntentFilter filter = new IntentFilter(ACTION_DEVICE_IDLE_MODE_CHANGED); 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); appContext.registerReceiver(receiver, filter);
} }
@Override @Override
public void stopService() throws ServiceException { public void stopService() {
if (SDK_INT < 23) return; if (SDK_INT < 23) return;
appContext.unregisterReceiver(receiver); appContext.unregisterReceiver(receiver);
} }
@@ -49,9 +62,33 @@ class DozeWatchdogImpl implements DozeWatchdog, Service {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (SDK_INT < 23) return; if (SDK_INT < 23) return;
String action = intent.getAction();
PowerManager pm = PowerManager pm =
(PowerManager) appContext.getSystemService(POWER_SERVICE); (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);
}
}
} }
} }
} }

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.android.account; package org.briarproject.briar.android.account;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -8,6 +9,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.Toast;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.account.PowerView.OnCheckedChangedListener; 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.INVISIBLE;
import static android.view.View.VISIBLE; 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.android.dontkillmelib.DozeUtils.getDozeWhitelistingIntent;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog; import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
@@ -113,7 +116,12 @@ public class DozeFragment extends SetupFragment
private void askForDozeWhitelisting() { private void askForDozeWhitelisting() {
if (getContext() == null) return; if (getContext() == null) return;
Intent i = getDozeWhitelistingIntent(getContext()); 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 @Override

View File

@@ -14,6 +14,7 @@ import androidx.annotation.UiThread;
import static org.briarproject.android.dontkillmelib.HuaweiUtils.getHuaweiProtectedAppsIntent; import static org.briarproject.android.dontkillmelib.HuaweiUtils.getHuaweiProtectedAppsIntent;
import static org.briarproject.android.dontkillmelib.HuaweiUtils.protectedAppsNeedsToBeShown; import static org.briarproject.android.dontkillmelib.HuaweiUtils.protectedAppsNeedsToBeShown;
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
@@ -49,7 +50,7 @@ class HuaweiProtectedAppsView extends PowerView {
@Override @Override
protected void onButtonClick() { protected void onButtonClick() {
getContext().startActivity(getHuaweiProtectedAppsIntent()); tryToStartActivity(getContext(), getHuaweiProtectedAppsIntent());
setChecked(true); setChecked(true);
} }
} }

View File

@@ -19,10 +19,17 @@ import org.briarproject.nullsafety.ParametersNotNullByDefault;
import javax.annotation.Nullable; 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.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.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; 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.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
import static org.briarproject.briar.android.util.UiUtils.setError; import static org.briarproject.briar.android.util.UiUtils.setError;
@@ -38,6 +45,10 @@ public class SetPasswordFragment extends SetupFragment {
private StrengthMeter strengthMeter; private StrengthMeter strengthMeter;
private Button nextButton; private Button nextButton;
private final ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new RequestPermission(), isGranted ->
setPassword());
public static SetPasswordFragment newInstance() { public static SetPasswordFragment newInstance() {
return new SetPasswordFragment(); return new SetPasswordFragment();
} }
@@ -121,6 +132,18 @@ public class SetPasswordFragment extends SetupFragment {
IBinder token = passwordEntry.getWindowToken(); IBinder token = passwordEntry.getWindowToken();
Object o = requireContext().getSystemService(INPUT_METHOD_SERVICE); Object o = requireContext().getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).hideSoftInputFromWindow(token, 0); ((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()); viewModel.setPassword(passwordEntry.getText().toString());
} }
} }

View File

@@ -36,7 +36,7 @@ public class SetupActivity extends BaseActivity
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
SetupViewModel viewModel; private SetupViewModel viewModel;
@Override @Override
public void injectActivity(ActivityComponent component) { public void injectActivity(ActivityComponent component) {
@@ -71,16 +71,16 @@ public class SetupActivity extends BaseActivity
} }
} }
void showPasswordFragment() { private void showPasswordFragment() {
showNextFragment(SetPasswordFragment.newInstance()); showNextFragment(SetPasswordFragment.newInstance());
} }
@TargetApi(23) @TargetApi(23)
void showDozeFragment() { private void showDozeFragment() {
showNextFragment(DozeFragment.newInstance()); showNextFragment(DozeFragment.newInstance());
} }
void showApp() { private void showApp() {
Intent i = new Intent(this, ENTRY_ACTIVITY); Intent i = new Intent(this, ENTRY_ACTIVITY);
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME | i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME |
FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_CLEAR_TOP); FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_CLEAR_TOP);

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.android.account; package org.briarproject.briar.android.account;
import android.app.KeyguardManager; import android.app.KeyguardManager;
import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback; 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.hardware.biometrics.BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.view.View.INVISIBLE; 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.activity.RequestCodes.REQUEST_KEYGUARD_UNLOCK;
import static org.briarproject.briar.android.util.UiUtils.hasKeyguardLock; import static org.briarproject.briar.android.util.UiUtils.hasKeyguardLock;
import static org.briarproject.briar.android.util.UiUtils.hasUsableFingerprint; import static org.briarproject.briar.android.util.UiUtils.hasUsableFingerprint;
@@ -191,7 +193,12 @@ public class UnlockActivity extends BaseActivity {
unlock(); unlock();
} else { } else {
keyguardShown = true; 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); overridePendingTransition(0, 0);
} }
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.android.account; package org.briarproject.briar.android.account;
import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.Toast; import android.widget.Toast;
@@ -60,12 +61,12 @@ class XiaomiLockAppsView extends PowerView {
getContext().startActivity(getXiaomiLockAppsIntent()); getContext().startActivity(getXiaomiLockAppsIntent());
setChecked(true); setChecked(true);
return; return;
} catch (SecurityException e) { } catch (SecurityException | ActivityNotFoundException e) {
logException(LOG, WARNING, 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 // Let the user continue with setup
setChecked(true); setChecked(true);
} }

View File

@@ -1,9 +1,11 @@
package org.briarproject.briar.android.activity; package org.briarproject.briar.android.activity;
import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.transition.Transition; import android.transition.Transition;
import android.view.Window; import android.view.Window;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.Toast;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager; import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Wakeful; 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_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION; import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
import static android.os.Build.VERSION.SDK_INT; 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.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.android.dontkillmelib.DozeUtils.getDozeWhitelistingIntent; 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_DOZE_WHITELISTING;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_UNLOCK; 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, b.setPositiveButton(R.string.fix,
(dialog, which) -> { (dialog, which) -> {
Intent i = getDozeWhitelistingIntent(BriarActivity.this); 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(); dialog.dismiss();
}); });
b.setNegativeButton(R.string.cancel, b.setNegativeButton(R.string.cancel,

View File

@@ -19,6 +19,7 @@ import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails; import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails;
import org.briarproject.nullsafety.MethodsNotNullByDefault; import org.briarproject.nullsafety.MethodsNotNullByDefault;
import org.briarproject.nullsafety.ParametersNotNullByDefault; import org.briarproject.nullsafety.ParametersNotNullByDefault;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -32,6 +33,7 @@ import androidx.fragment.app.DialogFragment;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION; import static android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION;
import static android.view.View.GONE; import static android.view.View.GONE;
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -68,6 +70,7 @@ public class ScreenFilterDialogFragment extends DialogFragment {
((BaseActivity) requireActivity()).getActivityComponent().inject(this); ((BaseActivity) requireActivity()).getActivityComponent().inject(this);
} }
@NotNull
@Override @Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Activity activity = getActivity(); Activity activity = getActivity();
@@ -98,7 +101,7 @@ public class ScreenFilterDialogFragment extends DialogFragment {
builder.setNeutralButton(R.string.screen_filter_review_apps, builder.setNeutralButton(R.string.screen_filter_review_apps,
(dialog, which) -> { (dialog, which) -> {
Intent i = new Intent(ACTION_MANAGE_OVERLAY_PERMISSION); Intent i = new Intent(ACTION_MANAGE_OVERLAY_PERMISSION);
startActivity(i); tryToStartActivity(requireActivity(), i);
}); });
} }
builder.setPositiveButton(R.string.continue_button, (dialog, which) -> { builder.setPositiveButton(R.string.continue_button, (dialog, which) -> {

View File

@@ -1,21 +1,27 @@
package org.briarproject.briar.android.hotspot; package org.briarproject.briar.android.hotspot;
import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.widget.Toast;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.core.util.Consumer; import androidx.core.util.Consumer;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import static android.content.Context.WIFI_SERVICE; 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 * Abstract base class for the ConditionManagers that ensure that the conditions
* to open a hotspot are fulfilled. There are different extensions of this for * 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 { abstract class AbstractConditionManager {
@@ -28,6 +34,7 @@ abstract class AbstractConditionManager {
final Consumer<Boolean> permissionUpdateCallback; final Consumer<Boolean> permissionUpdateCallback;
protected FragmentActivity ctx; protected FragmentActivity ctx;
WifiManager wifiManager; WifiManager wifiManager;
private ActivityResultLauncher<Intent> wifiRequest;
AbstractConditionManager(Consumer<Boolean> permissionUpdateCallback) { AbstractConditionManager(Consumer<Boolean> permissionUpdateCallback) {
this.permissionUpdateCallback = permissionUpdateCallback; this.permissionUpdateCallback = permissionUpdateCallback;
@@ -38,8 +45,12 @@ abstract class AbstractConditionManager {
*/ */
void init(FragmentActivity ctx) { void init(FragmentActivity ctx) {
this.ctx = ctx; this.ctx = ctx;
this.wifiManager = (WifiManager) ctx.getApplicationContext() wifiManager = (WifiManager) ctx.getApplicationContext()
.getSystemService(WIFI_SERVICE); .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 boolean checkAndRequestConditions();
abstract String getWifiSettingsAction();
void showRationale(Context ctx, @StringRes int title, void showRationale(Context ctx, @StringRes int title,
@StringRes int body, Runnable onContinueClicked, @StringRes int body, Runnable onContinueClicked,
Runnable onDismiss) { Runnable onDismiss) {
@@ -69,4 +82,13 @@ abstract class AbstractConditionManager {
builder.show(); builder.show();
} }
void requestEnableWiFi() {
try {
wifiRequest.launch(new Intent(getWifiSettingsAction()));
} catch (ActivityNotFoundException e) {
Toast.makeText(ctx, R.string.error_start_activity, LENGTH_LONG)
.show();
}
}
} }

View File

@@ -1,15 +1,12 @@
package org.briarproject.briar.android.hotspot; package org.briarproject.briar.android.hotspot;
import android.content.Intent;
import android.provider.Settings; import android.provider.Settings;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.logging.Logger; 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 androidx.core.util.Consumer;
import static java.util.logging.Level.INFO; 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, * As soon as {@link #checkAndRequestConditions()} returns true,
* all conditions are fulfilled. * all conditions are fulfilled.
*/ */
@NotNullByDefault
class ConditionManager extends AbstractConditionManager { class ConditionManager extends AbstractConditionManager {
private static final Logger LOG = private static final Logger LOG =
getLogger(ConditionManager.class.getName()); getLogger(ConditionManager.class.getName());
private final ActivityResultLauncher<Intent> wifiRequest; ConditionManager(Consumer<Boolean> permissionUpdateCallback) {
super( permissionUpdateCallback);
ConditionManager(ActivityResultCaller arc,
Consumer<Boolean> permissionUpdateCallback) {
super(permissionUpdateCallback);
wifiRequest = arc.registerForActivityResult(
new StartActivityForResult(),
result -> permissionUpdateCallback
.accept(wifiManager.isWifiEnabled()));
} }
@Override @Override
@@ -76,8 +67,9 @@ class ConditionManager extends AbstractConditionManager {
return false; return false;
} }
private void requestEnableWiFi() { @Override
wifiRequest.launch(new Intent(Settings.ACTION_WIFI_SETTINGS)); String getWifiSettingsAction() {
return Settings.ACTION_WIFI_SETTINGS;
} }
} }

View File

@@ -1,18 +1,17 @@
package org.briarproject.briar.android.hotspot; package org.briarproject.briar.android.hotspot;
import android.content.Intent;
import android.provider.Settings; import android.provider.Settings;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.util.Permission; import org.briarproject.briar.android.util.Permission;
import org.briarproject.briar.android.util.PermissionUtils; import org.briarproject.briar.android.util.PermissionUtils;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.logging.Logger; import java.util.logging.Logger;
import androidx.activity.result.ActivityResultCaller; import androidx.activity.result.ActivityResultCaller;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission; import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.core.util.Consumer; 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 * This class ensures that the conditions to open a hotspot are fulfilled on
* API levels >= 29. * API levels >= 29 and < 33.
* <p> * <p>
* As soon as {@link #checkAndRequestConditions()} returns true, * As soon as {@link #checkAndRequestConditions()} returns true,
* all conditions are fulfilled. * all conditions are fulfilled.
*/ */
@RequiresApi(29) @RequiresApi(29)
@NotNullByDefault
class ConditionManager29 extends AbstractConditionManager { class ConditionManager29 extends AbstractConditionManager {
private static final Logger LOG = private static final Logger LOG =
@@ -42,7 +42,6 @@ class ConditionManager29 extends AbstractConditionManager {
private Permission locationPermission = Permission.UNKNOWN; private Permission locationPermission = Permission.UNKNOWN;
private final ActivityResultLauncher<String> locationRequest; private final ActivityResultLauncher<String> locationRequest;
private final ActivityResultLauncher<Intent> wifiRequest;
ConditionManager29(ActivityResultCaller arc, ConditionManager29(ActivityResultCaller arc,
Consumer<Boolean> permissionUpdateCallback) { Consumer<Boolean> permissionUpdateCallback) {
@@ -53,11 +52,6 @@ class ConditionManager29 extends AbstractConditionManager {
onRequestPermissionResult(granted); onRequestPermissionResult(granted);
permissionUpdateCallback.accept(TRUE.equals(granted)); permissionUpdateCallback.accept(TRUE.equals(granted));
}); });
wifiRequest = arc.registerForActivityResult(
new StartActivityForResult(),
result -> permissionUpdateCallback
.accept(wifiManager.isWifiEnabled())
);
} }
@Override @Override
@@ -131,6 +125,11 @@ class ConditionManager29 extends AbstractConditionManager {
return false; return false;
} }
@Override
String getWifiSettingsAction() {
return Settings.Panel.ACTION_WIFI;
}
private void onRequestPermissionResult(@Nullable Boolean granted) { private void onRequestPermissionResult(@Nullable Boolean granted) {
if (granted != null && granted) { if (granted != null && granted) {
locationPermission = Permission.GRANTED; locationPermission = Permission.GRANTED;
@@ -146,8 +145,4 @@ class ConditionManager29 extends AbstractConditionManager {
locationRequest.launch(ACCESS_FINE_LOCATION); locationRequest.launch(ACCESS_FINE_LOCATION);
} }
private void requestEnableWiFi() {
wifiRequest.launch(new Intent(Settings.Panel.ACTION_WIFI));
}
} }

View File

@@ -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);
}
}

View File

@@ -31,6 +31,7 @@ import static android.view.View.VISIBLE;
import static androidx.transition.TransitionManager.beginDelayedTransition; import static androidx.transition.TransitionManager.beginDelayedTransition;
import static org.briarproject.briar.android.AppModule.getAndroidComponent; import static org.briarproject.briar.android.AppModule.getAndroidComponent;
import static org.briarproject.briar.android.hotspot.HotspotViewModel.getApkFileName; import static org.briarproject.briar.android.hotspot.HotspotViewModel.getApkFileName;
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -102,7 +103,7 @@ public class FallbackFragment extends BaseFragment {
i.putExtra(EXTRA_STREAM, uri); i.putExtra(EXTRA_STREAM, uri);
i.setType("*/*"); // gives us all sharing options i.setType("*/*"); // gives us all sharing options
i.addFlags(FLAG_GRANT_READ_URI_PERMISSION); i.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(i, null)); tryToStartActivity(requireActivity(), Intent.createChooser(i, null));
} }
} }

View File

@@ -49,8 +49,10 @@ public class HotspotIntroFragment extends Fragment {
private TextView progressTextView; private TextView progressTextView;
private final AbstractConditionManager conditionManager = SDK_INT < 29 ? private final AbstractConditionManager conditionManager = SDK_INT < 29 ?
new ConditionManager(this, this::onPermissionUpdate) : new ConditionManager(this::onPermissionUpdate) :
new ConditionManager29(this, this::onPermissionUpdate); SDK_INT >= 33 ?
new ConditionManager33(this, this::onPermissionUpdate) :
new ConditionManager29(this, this::onPermissionUpdate);
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
@@ -87,7 +89,6 @@ public class HotspotIntroFragment extends Fragment {
} }
private void onButtonClick(View view) { private void onButtonClick(View view) {
startButton.setEnabled(false);
startHotspotIfConditionsFulfilled(); startHotspotIfConditionsFulfilled();
} }

View File

@@ -22,13 +22,19 @@ import org.briarproject.nullsafety.ParametersNotNullByDefault;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider; 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.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; 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.KEY_STRENGTHENER_ERROR;
import static org.briarproject.bramble.api.crypto.DecryptionResult.SUCCESS; import static org.briarproject.bramble.api.crypto.DecryptionResult.SUCCESS;
import static org.briarproject.briar.android.login.LoginUtils.createKeyStrengthenerErrorDialog; import static org.briarproject.briar.android.login.LoginUtils.createKeyStrengthenerErrorDialog;
@@ -52,6 +58,10 @@ public class PasswordFragment extends BaseFragment implements TextWatcher {
private TextInputLayout input; private TextInputLayout input;
private TextInputEditText password; private TextInputEditText password;
private final ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new RequestPermission(), isGranted ->
validatePassword());
@Override @Override
public void injectFragment(ActivityComponent component) { public void injectFragment(ActivityComponent component) {
component.inject(this); component.inject(this);
@@ -109,6 +119,17 @@ public class PasswordFragment extends BaseFragment implements TextWatcher {
hideSoftKeyboard(password); hideSoftKeyboard(password);
signInButton.setVisibility(INVISIBLE); signInButton.setVisibility(INVISIBLE);
progress.setVisibility(VISIBLE); 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()); viewModel.validatePassword(password.getText().toString());
} }

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.android.reporting; package org.briarproject.briar.android.reporting;
import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@@ -33,13 +32,11 @@ import androidx.recyclerview.widget.RecyclerView;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.util.UiUtils.onSingleLinkClick; import static org.briarproject.briar.android.util.UiUtils.onSingleLinkClick;
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -180,13 +177,7 @@ public class ReportFormFragment extends BaseFragment {
private void triggerPrivacyPolicy() { private void triggerPrivacyPolicy() {
Intent i = new Intent(Intent.ACTION_VIEW); Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://briarproject.org/privacy-policy/\\")); i.setData(Uri.parse("https://briarproject.org/privacy-policy/\\"));
try { tryToStartActivity(requireActivity(), i);
startActivity(i);
} catch (ActivityNotFoundException e) {
logException(LOG, WARNING, e);
Toast.makeText(requireContext(),
R.string.error_start_activity, LENGTH_LONG).show();
}
} }
} }

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.android.settings; package org.briarproject.briar.android.settings;
import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@@ -8,7 +7,6 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.briar.BuildConfig; import org.briarproject.briar.BuildConfig;
import org.briarproject.briar.R; import org.briarproject.briar.R;
@@ -21,10 +19,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import static android.widget.Toast.LENGTH_LONG; import static android.content.Intent.ACTION_VIEW;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -85,16 +82,9 @@ public class AboutFragment extends Fragment {
} }
private void goToUrl(String url) { private void goToUrl(String url) {
Intent i = new Intent(Intent.ACTION_VIEW); Intent i = new Intent(ACTION_VIEW);
i.setData(Uri.parse(url)); i.setData(Uri.parse(url));
try { tryToStartActivity(requireActivity(), i);
startActivity(i);
} catch (ActivityNotFoundException e) {
logException(LOG, WARNING, e);
Toast.makeText(requireContext(),
R.string.error_start_activity, LENGTH_LONG).show();
}
} }
} }

View File

@@ -24,11 +24,14 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceGroup; 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 java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.AppModule.getAndroidComponent; import static org.briarproject.briar.android.AppModule.getAndroidComponent;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; 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.launchActivityToOpenFile;
import static org.briarproject.briar.android.util.UiUtils.triggerFeedback; import static org.briarproject.briar.android.util.UiUtils.triggerFeedback;
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -37,11 +40,14 @@ public class SettingsFragment extends PreferenceFragmentCompat {
public static final String SETTINGS_NAMESPACE = "android-ui"; public static final String SETTINGS_NAMESPACE = "android-ui";
private static final String PREF_KEY_AVATAR = "pref_key_avatar"; 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_FEEDBACK = "pref_key_send_feedback";
private static final String PREF_KEY_DEV = "pref_key_dev"; 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_EXPLODE = "pref_key_explode";
private static final String PREF_KEY_MAILBOX = "pref_key_mailbox"; private static final String PREF_KEY_MAILBOX = "pref_key_mailbox";
private static final String DOWNLOAD_URL = "https://briarproject.org/download/";
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
@@ -86,6 +92,17 @@ public class SettingsFragment extends PreferenceFragmentCompat {
return true; 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 = Preference prefFeedback =
requireNonNull(findPreference(PREF_KEY_FEEDBACK)); requireNonNull(findPreference(PREF_KEY_FEEDBACK));
prefFeedback.setOnPreferenceClickListener(preference -> { prefFeedback.setOnPreferenceClickListener(preference -> {

View File

@@ -1,12 +1,10 @@
package org.briarproject.briar.android.util; package org.briarproject.briar.android.util;
import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.location.LocationManager; import android.location.LocationManager;
import android.net.Uri; import android.net.Uri;
import android.widget.Toast;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.nullsafety.MethodsNotNullByDefault; 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.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS; 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 androidx.core.content.ContextCompat.checkSelfPermission;
import static java.lang.Boolean.TRUE; import static java.lang.Boolean.TRUE;
import static org.briarproject.briar.BuildConfig.APPLICATION_ID; import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @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) == return checkSelfPermission(ctx, permission) ==
PERMISSION_GRANTED; PERMISSION_GRANTED;
} }
@@ -68,7 +66,7 @@ public class PermissionUtils {
gotPermission(ctx, grantedMap, BLUETOOTH_SCAN); gotPermission(ctx, grantedMap, BLUETOOTH_SCAN);
} }
public static DialogInterface.OnClickListener getGoToSettingsListener( private static DialogInterface.OnClickListener getGoToSettingsListener(
Context context) { Context context) {
return (dialog, which) -> { return (dialog, which) -> {
Intent i = new Intent(); Intent i = new Intent();
@@ -76,7 +74,7 @@ public class PermissionUtils {
i.addCategory(CATEGORY_DEFAULT); i.addCategory(CATEGORY_DEFAULT);
i.setData(Uri.parse("package:" + APPLICATION_ID)); i.setData(Uri.parse("package:" + APPLICATION_ID));
i.addFlags(FLAG_ACTIVITY_NEW_TASK); 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, builder.setPositiveButton(R.string.permission_location_setting_button,
(dialog, which) -> { (dialog, which) -> {
Intent i = new Intent(ACTION_LOCATION_SOURCE_SETTINGS); Intent i = new Intent(ACTION_LOCATION_SOURCE_SETTINGS);
try { tryToStartActivity(ctx, i);
ctx.startActivity(i);
} catch (ActivityNotFoundException e) {
Toast.makeText(ctx, R.string.error_start_activity,
LENGTH_LONG).show();
}
}); });
builder.show(); builder.show();
} }

View File

@@ -157,6 +157,15 @@ public class UiUtils {
ta.commit(); 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, public static String getContactDisplayName(Author author,
@Nullable String alias) { @Nullable String alias) {
String name = author.getName(); String name = author.getName();

View File

@@ -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>

View File

@@ -13,10 +13,10 @@
android:layout_margin="@dimen/margin_medium" android:layout_margin="@dimen/margin_medium"
android:contentDescription="@string/info" android:contentDescription="@string/info"
android:drawablePadding="@dimen/margin_medium" android:drawablePadding="@dimen/margin_medium"
android:drawableTint="?attr/colorControlNormal"
android:gravity="center_vertical" android:gravity="center_vertical"
app:drawableLeftCompat="@drawable/ic_info_dark" app:drawableLeftCompat="@drawable/ic_info_dark"
app:drawableStartCompat="@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?" /> 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> </merge>

View File

@@ -27,6 +27,8 @@
<string name="dnkm_xiaomi_help">Ако Briar не е заключен в списъка с последно използваните приложения, няма да работи на заден план.</string> <string name="dnkm_xiaomi_help">Ако Briar не е заключен в списъка с последно използваните приложения, няма да работи на заден план.</string>
<string name="dnkm_xiaomi_dialog_body_old">1. Отворете списъка с отворени приложения (списък за превключване на приложения)\n\n2. Плъзнете надолу върху изображението на Briar докато се покаже икона на катинар\n\n3. Ако катинарът е отключен го докоснете, за да го заключите</string> <string name="dnkm_xiaomi_dialog_body_old">1. Отворете списъка с отворени приложения (списък за превключване на приложения)\n\n2. Плъзнете надолу върху изображението на Briar докато се покаже икона на катинар\n\n3. Ако катинарът е отключен го докоснете, за да го заключите</string>
<string name="dnkm_xiaomi_dialog_body_new">1. Отворете списъка с последните приложения\n\n2. Ако до името на Briar има значка на катинарче, не е необходимо да правите нищо\n\n3. Ако няма натиснете и задръжте изображението на Briar, докато се появи бутон за катинарче, след което го докоснете</string> <string name="dnkm_xiaomi_dialog_body_new">1. Отворете списъка с последните приложения\n\n2. Ако до името на Briar има значка на катинарче, не е необходимо да правите нищо\n\n3. Ако няма натиснете и задръжте изображението на Briar, докато се появи бутон за катинарче, след което го докоснете</string>
<string name="dnkm_xiaomi_lock_apps_text">Моля, докоснете бутона по-долу, за да отворите настройките за сигурност. Натиснете бутона \"Ускори\", след това натиснете \"Заключени приложения\" и се уверете, че Briar е \"Заключен\"</string>
<string name="dnkm_xiaomi_lock_apps_help">Ако Briar не е \"Заключен\" в менюто \"Заключени приложения\", няма да може да работи на заден план.</string>
<string name="dnkm_warning_dozed_1">Briar не може да работи във фонов режим</string> <string name="dnkm_warning_dozed_1">Briar не може да работи във фонов режим</string>
<!--Login--> <!--Login-->
<string name="enter_password">Парола</string> <string name="enter_password">Парола</string>
@@ -47,10 +49,6 @@
<item quantity="one">Това е тестова версия на Briar. Вашият акаунт ще бъде изтече след %d ден и не може да бъде подновена.</item> <item quantity="one">Това е тестова версия на Briar. Вашият акаунт ще бъде изтече след %d ден и не може да бъде подновена.</item>
<item quantity="other">Това е изпитателно издание на Briar. Валидността на профила ще изтече след %d дена и не може да бъде подновена.</item> <item quantity="other">Това е изпитателно издание на Briar. Валидността на профила ще изтече след %d дена и не може да бъде подновена.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Android 4 не се поддържа. Briar ще спре да работи на %s (след %d ден). Инсталирайте Briar на друго устройство и създайте нов профил.</item>
<item quantity="other">Android 4 не се поддържа. Briar ще спре да работи на %s (след %d дена). Инсталирайте Briar на друго устройство и създайте нов профил.</item>
</plurals>
<string name="expiry_date_reached">Софтуерът е с изтекъл срок.\nБлагодарим за изпитването!</string> <string name="expiry_date_reached">Софтуерът е с изтекъл срок.\nБлагодарим за изпитването!</string>
<string name="download_briar">За да продължите да използвате Briar изтеглете последното издание.</string> <string name="download_briar">За да продължите да използвате Briar изтеглете последното издание.</string>
<string name="create_new_account">Ще трябва да създадете нов профил, но ще можете да използвате същия прякор.</string> <string name="create_new_account">Ще трябва да създадете нов профил, но ще можете да използвате същия прякор.</string>

View File

@@ -49,10 +49,6 @@
<item quantity="one">Aquesta és una versió de prova de Briar. El vostre compte expira en %d dia i no es pot renovar.</item> <item quantity="one">Aquesta és una versió de prova de Briar. El vostre compte expira en %d dia i no es pot renovar.</item>
<item quantity="other">Aquesta és una versió de prova de Briar. El vostre compte caducarà en %d dies i no es podrà renovar.</item> <item quantity="other">Aquesta és una versió de prova de Briar. El vostre compte caducarà en %d dies i no es podrà renovar.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">L\'Android 4 ja no és compatible. El Briar deixarà de funcionar a %s (d\'aquí a %d dia). Instal·leu el Briar en un dispositiu més nou i creeu un compte nou.</item>
<item quantity="other">L\'Android 4 ja no és compatible. El Briar deixarà de funcionar a %s (d\'aquí a %d dies). Instal·leu el Briar en un dispositiu més nou i creeu un compte nou.</item>
</plurals>
<string name="expiry_date_reached">Aquesta versió de Briar ha caducat.\nGràcies per haver-lo provat!</string> <string name="expiry_date_reached">Aquesta versió de Briar ha caducat.\nGràcies per haver-lo provat!</string>
<string name="download_briar">Per continuar utilitzant Briar, baixeu la darrera versió.</string> <string name="download_briar">Per continuar utilitzant Briar, baixeu la darrera versió.</string>
<string name="create_new_account">Haureu de crear un compte nou, però podeu utilitzar el mateix sobrenom.</string> <string name="create_new_account">Haureu de crear un compte nou, però podeu utilitzar el mateix sobrenom.</string>

View File

@@ -40,7 +40,7 @@
<string name="dialog_title_lost_password">Passwort vergessen</string> <string name="dialog_title_lost_password">Passwort vergessen</string>
<string name="dialog_message_lost_password">Dein Briar-Konto ist verschlüsselt auf deinem Gerät und nicht in der Cloud gespeichert, deshalb kannst du dein Passwort nicht zurücksetzen. Willst du dein Konto löschen und neu beginnen?\n\nAchtung: Deine bestehenden Identitäten, Kontakte und Nachrichten gehen dann für immer verloren.</string> <string name="dialog_message_lost_password">Dein Briar-Konto ist verschlüsselt auf deinem Gerät und nicht in der Cloud gespeichert, deshalb kannst du dein Passwort nicht zurücksetzen. Willst du dein Konto löschen und neu beginnen?\n\nAchtung: Deine bestehenden Identitäten, Kontakte und Nachrichten gehen dann für immer verloren.</string>
<string name="startup_failed_activity_title">Fehler beim Starten von Briar</string> <string name="startup_failed_activity_title">Fehler beim Starten von Briar</string>
<string name="startup_failed_clock_error">Briar konnte nicht gestartet werden, weil die Uhr deines Geräts falsch eingestellt ist.\n\Bitte stelle die Uhr deines Geräts auf die richtige Zeit ein und versuche es erneut.</string> <string name="startup_failed_clock_error">Briar konnte nicht gestartet werden, weil die Uhr deines Geräts falsch eingestellt ist.\n\nBitte stelle die Uhr deines Geräts auf die richtige Zeit ein und versuche es erneut.</string>
<string name="startup_failed_db_error">Briar konnte die Datenbank mit deinem Konto, deinen Kontakten und deinen Nachrichten nicht öffnen.\n\nBitte aktualisiere auf die neueste Version der App und versuche es erneut, oder richte ein neues Konto ein, indem du bei der Passwortabfrage \"Ich habe mein Passwort vergessen\" wählst.</string> <string name="startup_failed_db_error">Briar konnte die Datenbank mit deinem Konto, deinen Kontakten und deinen Nachrichten nicht öffnen.\n\nBitte aktualisiere auf die neueste Version der App und versuche es erneut, oder richte ein neues Konto ein, indem du bei der Passwortabfrage \"Ich habe mein Passwort vergessen\" wählst.</string>
<string name="startup_failed_data_too_old_error">Dein Konto wurde mit einer alten Version dieser App erstellt und kann mit dieser Version nicht geöffnet werden.\n\nDu musst entweder die alte Version neu installieren oder ein neues Konto einrichten, indem du bei der Passwortabfrage \"Ich habe mein Passwort vergessen\" wählst.</string> <string name="startup_failed_data_too_old_error">Dein Konto wurde mit einer alten Version dieser App erstellt und kann mit dieser Version nicht geöffnet werden.\n\nDu musst entweder die alte Version neu installieren oder ein neues Konto einrichten, indem du bei der Passwortabfrage \"Ich habe mein Passwort vergessen\" wählst.</string>
<string name="startup_failed_data_too_new_error">Dein Konto wurde mit einer neueren Version dieser App erstellt und kann mit dieser Version nicht geöffnet werden.\n\nBitte aktualisiere auf die neueste Version und versuche es erneut.</string> <string name="startup_failed_data_too_new_error">Dein Konto wurde mit einer neueren Version dieser App erstellt und kann mit dieser Version nicht geöffnet werden.\n\nBitte aktualisiere auf die neueste Version und versuche es erneut.</string>
@@ -49,10 +49,6 @@
<item quantity="one">Dies ist eine Testversion von Briar. Dein Konto läuft in %d Tag ab und kann nicht verlängert werden.</item> <item quantity="one">Dies ist eine Testversion von Briar. Dein Konto läuft in %d Tag ab und kann nicht verlängert werden.</item>
<item quantity="other">Dies ist eine Testversion von Briar. Dein Konto läuft in %d Tagen ab und kann nicht verlängert werden.</item> <item quantity="other">Dies ist eine Testversion von Briar. Dein Konto läuft in %d Tagen ab und kann nicht verlängert werden.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Android 4 wird nicht mehr unterstützt. Briar wird nicht mehr auf %s funktionieren (in %d Tag). Bitte installiere es auf einem neueren Gerät und erstelle ein neues Konto.</item>
<item quantity="other">Android 4 wird nicht mehr unterstützt. Briar wird nicht mehr auf %s funktionieren (in %dTagen). Bitte installiere es auf einem neueren Gerät und erstelle ein neues Konto.</item>
</plurals>
<string name="expiry_date_reached">Diese Software ist abgelaufen.\nDanke, dass du Briar getestet hast!</string> <string name="expiry_date_reached">Diese Software ist abgelaufen.\nDanke, dass du Briar getestet hast!</string>
<string name="download_briar">Bitte lade die aktuelle Version von Briar herunter, um es weiter zu benutzen.</string> <string name="download_briar">Bitte lade die aktuelle Version von Briar herunter, um es weiter zu benutzen.</string>
<string name="create_new_account">Du wirst ein neues Konto erstellen müssen, wobei du jedoch wieder denselben Spitznamen verwenden kannst.</string> <string name="create_new_account">Du wirst ein neues Konto erstellen müssen, wobei du jedoch wieder denselben Spitznamen verwenden kannst.</string>

View File

@@ -50,11 +50,6 @@
<item quantity="many">Esta es una versión de prueba de Briar. Tu cuenta expirará en %d días y no podrá ser renovada.</item> <item quantity="many">Esta es una versión de prueba de Briar. Tu cuenta expirará en %d días y no podrá ser renovada.</item>
<item quantity="other">Esta es una versión de prueba de Briar. Tu cuenta expirará en %d días y no podrá ser renovada.</item> <item quantity="other">Esta es una versión de prueba de Briar. Tu cuenta expirará en %d días y no podrá ser renovada.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Android 4 ya no es soportado. Briar dejará de funcionar sobre %s (en %d días). Por favor, instala Briar en un dispositivo más nuevo y crea una cuenta nueva.</item>
<item quantity="many">Android 4 ya no es soportado. Briar dejará de funcionar sobre %s (en %d días). Por favor, instala Briar en un dispositivo más nuevo y crea una cuenta nueva.</item>
<item quantity="other">Android 4 ya no es soportado. Briar dejará de funcionar sobre %s (en %d días). Por favor, instala Briar en un dispositivo más nuevo y crea una cuenta nueva.</item>
</plurals>
<string name="expiry_date_reached">Este programa ha caducado.\n¡Gracias por probarlo!</string> <string name="expiry_date_reached">Este programa ha caducado.\n¡Gracias por probarlo!</string>
<string name="download_briar">Para continuar usando Briar, por favor descarga la versión mas reciente.</string> <string name="download_briar">Para continuar usando Briar, por favor descarga la versión mas reciente.</string>
<string name="create_new_account">Necesitarás crear una nueva cuenta, pero puedes usar el mismo nombre de usuario.</string> <string name="create_new_account">Necesitarás crear una nueva cuenta, pero puedes usar el mismo nombre de usuario.</string>

View File

@@ -53,10 +53,6 @@
<item quantity="one">این یک نسخه آزمایشی از Briar (برایر) می باشد. حساب کاربری شما در %d روز آینده به پایان می رسد و امکان تمدید آن وجود نخواهد داشت.</item> <item quantity="one">این یک نسخه آزمایشی از Briar (برایر) می باشد. حساب کاربری شما در %d روز آینده به پایان می رسد و امکان تمدید آن وجود نخواهد داشت.</item>
<item quantity="other">این یک نسخه آزمایشی از Briar (برایر) می باشد. حساب کاربری شما در %d روز آینده به پایان می رسد و امکان تمدید آن وجود نخواهد داشت.</item> <item quantity="other">این یک نسخه آزمایشی از Briar (برایر) می باشد. حساب کاربری شما در %d روز آینده به پایان می رسد و امکان تمدید آن وجود نخواهد داشت.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">اندروید 4 دیگر پشتیبانی نمی‌شود. Briar (در عرض %d روز) کار بر روی %s را متوقف خواهد کرد. لطفا Briar را در دستگاه جدیدتر نصب کنید و یک حساب کاربری جدید ایجاد کنید.</item>
<item quantity="other">اندروید 4 دیگر پشتیبانی نمی‌شود. Briar (در عرض %d روز) کار بر روی %s را متوقف خواهد کرد. لطفا Briar را در دستگاه جدیدتر نصب کنید و یک حساب کاربری جدید ایجاد کنید.</item>
</plurals>
<string name="expiry_date_reached">این نرم افزار منقضی شده است. <string name="expiry_date_reached">این نرم افزار منقضی شده است.
بابت تست از شما سپاسگزاریم.</string> بابت تست از شما سپاسگزاریم.</string>
@@ -700,7 +696,7 @@
<string name="mailbox_error_wizard_info2">لطفا هنگامی که به دستگاه دسترسی دارید به این صفحه بازگردید.</string> <string name="mailbox_error_wizard_info2">لطفا هنگامی که به دستگاه دسترسی دارید به این صفحه بازگردید.</string>
<string name="mailbox_error_wizard_info3">لطفا با استفاده از دکمه زیر، پیوند Mailbox خود را لغو کنید.\n\nپس از لغو پیوند Mailbox قدیمی، می‌توانید هر زمان که خواستید یک Mailbox جدید راه‌اندازی کنید.</string> <string name="mailbox_error_wizard_info3">لطفا با استفاده از دکمه زیر، پیوند Mailbox خود را لغو کنید.\n\nپس از لغو پیوند Mailbox قدیمی، می‌توانید هر زمان که خواستید یک Mailbox جدید راه‌اندازی کنید.</string>
<!--About--> <!--About-->
<string name="about_title">درباره‌ی Psiphon</string> <string name="about_title">درباره‌ی Briar</string>
<string name="briar_version">نسخه Briar: %s</string> <string name="briar_version">نسخه Briar: %s</string>
<string name="tor_version">نسخه Tor: %s</string> <string name="tor_version">نسخه Tor: %s</string>
<string name="links">لینک ها</string> <string name="links">لینک ها</string>

View File

@@ -46,11 +46,6 @@
<item quantity="many">Ceci est une version dessai de Briar. Votre compte arrivera à expiration dans %d jours et ne pourra pas être renouvelé.</item> <item quantity="many">Ceci est une version dessai de Briar. Votre compte arrivera à expiration dans %d jours et ne pourra pas être renouvelé.</item>
<item quantity="other">Ceci est une version dessai de Briar. Votre compte arrivera à expiration dans %d jours et ne pourra pas être renouvelé.</item> <item quantity="other">Ceci est une version dessai de Briar. Votre compte arrivera à expiration dans %d jours et ne pourra pas être renouvelé.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Android 4 nest plus pris en charge. Briar cessera de fonctionner le %s (dans %d jour). Veuillez installer Briar sur un appareil plus récent et créer un nouveau compte.</item>
<item quantity="many">Android 4 nest plus pris en charge. Briar cessera de fonctionner le %s (dans %d jours). Veuillez installer Briar sur un appareil plus récent et créer un nouveau compte.</item>
<item quantity="other">Android 4 nest plus pris en charge. Briar cessera de fonctionner le %s (dans %d jours). Veuillez installer Briar sur un appareil plus récent et créer un nouveau compte.</item>
</plurals>
<string name="expiry_date_reached">Ce logiciel est arrivé à expiration.\nMerci de lavoir testé!</string> <string name="expiry_date_reached">Ce logiciel est arrivé à expiration.\nMerci de lavoir testé!</string>
<string name="download_briar">Pour continuer à utiliser Briar, veuillez télécharger la dernière version.</string> <string name="download_briar">Pour continuer à utiliser Briar, veuillez télécharger la dernière version.</string>
<string name="create_new_account">Vous devrez créer un nouveau compte, mais vous pouvez utiliser le même pseudonyme.</string> <string name="create_new_account">Vous devrez créer un nouveau compte, mais vous pouvez utiliser le même pseudonyme.</string>

View File

@@ -45,10 +45,6 @@
<item quantity="one">Þetta er prufuútgáfa af Briar. Notandaaðgangurinn þinn mun renna út eftir %d dag og er ekki hægt að endurnýja hann.</item> <item quantity="one">Þetta er prufuútgáfa af Briar. Notandaaðgangurinn þinn mun renna út eftir %d dag og er ekki hægt að endurnýja hann.</item>
<item quantity="other">Þetta er prufuútgáfa af Briar. Notandaaðgangurinn þinn mun renna út eftir %d daga og er ekki hægt að endurnýja hann.</item> <item quantity="other">Þetta er prufuútgáfa af Briar. Notandaaðgangurinn þinn mun renna út eftir %d daga og er ekki hægt að endurnýja hann.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Android 4 er ekki lengur stutt. Briar mun hætta að virka á %s (eftir %d dag). Settu Briar upp á nýrra tæki og útbúðu nýjan aðgang.</item>
<item quantity="other">Android 4 er ekki lengur stutt. Briar mun hætta að virka á %s (eftir %d daga). Settu Briar upp á nýrra tæki og útbúðu nýjan aðgang.</item>
</plurals>
<string name="expiry_date_reached">Þessi hugbúnaður er úreltur.\nTakk fyrir að hafa tekið þátt í prófunum!</string> <string name="expiry_date_reached">Þessi hugbúnaður er úreltur.\nTakk fyrir að hafa tekið þátt í prófunum!</string>
<string name="download_briar">Til að halda áfram að nota Briar, ættirðu að sækja nýjustu útgáfuna.</string> <string name="download_briar">Til að halda áfram að nota Briar, ættirðu að sækja nýjustu útgáfuna.</string>
<string name="create_new_account">Þú þarft að búa til nýjan notandaaðgang, en þú getur notað áfram sama stuttnefni.</string> <string name="create_new_account">Þú þarft að búa til nýjan notandaaðgang, en þú getur notað áfram sama stuttnefni.</string>

View File

@@ -49,10 +49,6 @@
<item quantity="one">ესაა საცდელი ვერსიის Briar. თქვენს ანგარიშს %d დღეში გაუვა ვადა და ვეღარ გახანგრძლივდება.</item> <item quantity="one">ესაა საცდელი ვერსიის Briar. თქვენს ანგარიშს %d დღეში გაუვა ვადა და ვეღარ გახანგრძლივდება.</item>
<item quantity="other">ეს არის Briar-ის საცდელი ვერსია, რომლის ვადა %d დღეში ამოიწურება და მისი განახლება შეუძლებელია.</item> <item quantity="other">ეს არის Briar-ის საცდელი ვერსია, რომლის ვადა %d დღეში ამოიწურება და მისი განახლება შეუძლებელია.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Android 4 აღარაა მხარდაჭერილი. Briar-ის გაუქმების ვადაა %s (%d დღეში). გთხოვთ, Briar გამოიყენოთ უფრო ახალ მოწყობილობაზე და ახალი ანგარიში შექმნათ.</item>
<item quantity="other">Android 4 აღარაა მხარდაჭერილი. Briar შეწყვეტს მუშაობას %s (%d დღეში). გთხოვთ, დააინსტალიროთ Briar უფრო ახალ მოწყობილობაზე და ახალი ანგარიში შექმნათ.</item>
</plurals>
<string name="expiry_date_reached">პროგრამული უზრუნველყოფის ვადა ამოიწურა.\nგმადლობთ ტესტირებისთვის!</string> <string name="expiry_date_reached">პროგრამული უზრუნველყოფის ვადა ამოიწურა.\nგმადლობთ ტესტირებისთვის!</string>
<string name="download_briar">თუ გსურთ, კვლავ გამოიყენოთ Briar, ჩამოტვირთეთ ბოლო გამოშვება.</string> <string name="download_briar">თუ გსურთ, კვლავ გამოიყენოთ Briar, ჩამოტვირთეთ ბოლო გამოშვება.</string>
<string name="create_new_account">დაგჭირდებათ ახალი ანგარიშის შექმნა, თუმცა იმავე მეტსახელის გამოყენება შეგეძლებათ.</string> <string name="create_new_account">დაგჭირდებათ ახალი ანგარიშის შექმნა, თუმცა იმავე მეტსახელის გამოყენება შეგეძლებათ.</string>

View File

@@ -47,12 +47,6 @@
<item quantity="many">Tai yra bandomoji Briar versija. Jūsų paskyros galiojimas pasibaigs po %d dienų ir negalės būti pratęstas.</item> <item quantity="many">Tai yra bandomoji Briar versija. Jūsų paskyros galiojimas pasibaigs po %d dienų ir negalės būti pratęstas.</item>
<item quantity="other">Tai yra bandomoji Briar versija. Jūsų paskyros galiojimas pasibaigs po %d dienos ir negalės būti pratęstas.</item> <item quantity="other">Tai yra bandomoji Briar versija. Jūsų paskyros galiojimas pasibaigs po %d dienos ir negalės būti pratęstas.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">„Android“ 4 yra daugiau nebepalaikoma. Briar nustos veikti ties %s (po %d dienos). Įsidiekite Briar naujesniame įrenginyje ir susikurkite naują paskyrą.</item>
<item quantity="few">„Android“ 4 yra daugiau nebepalaikoma. Briar nustos veikti ties %s (po %d dienų). Įsidiekite Briar naujesniame įrenginyje ir susikurkite naują paskyrą.</item>
<item quantity="many">„Android“ 4 yra daugiau nebepalaikoma. Briar nustos veikti ties %s (po %d dienų). Įsidiekite Briar naujesniame įrenginyje ir susikurkite naują paskyrą.</item>
<item quantity="other">„Android“ 4 yra daugiau nebepalaikoma. Briar nustos veikti ties %s (po %d dienos). Įsidiekite Briar naujesniame įrenginyje ir susikurkite naują paskyrą.</item>
</plurals>
<string name="expiry_date_reached">Programinės įrangos galiojimas pasibaigė.\nDėkojame, kad išbandėte!</string> <string name="expiry_date_reached">Programinės įrangos galiojimas pasibaigė.\nDėkojame, kad išbandėte!</string>
<string name="download_briar">Norėdami ir toliau naudotis Briar, atsisiųskite naujausią programos laidą.</string> <string name="download_briar">Norėdami ir toliau naudotis Briar, atsisiųskite naujausią programos laidą.</string>
<string name="create_new_account">Jūs turėsite susikurti paskyrą, tačiau galėsite naudoti tą patį slapyvardį.</string> <string name="create_new_account">Jūs turėsite susikurti paskyrą, tačiau galėsite naudoti tą patį slapyvardį.</string>
@@ -780,6 +774,7 @@
<string name="hotspot_qr_site">Jūsų telefonas teikia belaidį (Wi-Fi) prieigos tašką. Žmonės, prisijungę prie prieigos taško, gali atsisiųsti Briar, skenuodami šį QR kodą.</string> <string name="hotspot_qr_site">Jūsų telefonas teikia belaidį (Wi-Fi) prieigos tašką. Žmonės, prisijungę prie prieigos taško, gali atsisiųsti Briar, skenuodami šį QR kodą.</string>
<!--e.g. Download Briar 1.2.20--> <!--e.g. Download Briar 1.2.20-->
<string name="website_download_title_1">Atsisiųsti Briar %s</string> <string name="website_download_title_1">Atsisiųsti Briar %s</string>
<string name="website_download_intro_1">Kažkas iš šalia esančių pradėjo bendrinti su jumis Briar.</string>
<string name="website_download_button">Atsisiųsti Briar</string> <string name="website_download_button">Atsisiųsti Briar</string>
<string name="website_download_outro">Kai atsisiuntimas pasibaigs, atverkite atsisiųstą failą ir jį įdiekite.</string> <string name="website_download_outro">Kai atsisiuntimas pasibaigs, atverkite atsisiųstą failą ir jį įdiekite.</string>
<string name="website_troubleshooting_title">Nesklandumų šalinimas</string> <string name="website_troubleshooting_title">Nesklandumų šalinimas</string>

View File

@@ -44,9 +44,6 @@
<plurals name="expiry_warning"> <plurals name="expiry_warning">
<item quantity="other">ဤအရာသည် Briar ၏ စမ်းသပ်ဆဲဗားရှင်းဖြစ်ပါသည်။ သင့်အကောင့်သည် %d ရက်ကြာလျှင် သက်တမ်းကုန်ဆုံးမည်ဖြစ်ပြီး သက်တမ်းတိုး၍မရနိုင်ပါ။</item> <item quantity="other">ဤအရာသည် Briar ၏ စမ်းသပ်ဆဲဗားရှင်းဖြစ်ပါသည်။ သင့်အကောင့်သည် %d ရက်ကြာလျှင် သက်တမ်းကုန်ဆုံးမည်ဖြစ်ပြီး သက်တမ်းတိုး၍မရနိုင်ပါ။</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="other">အန်းဒရိုက် ၄ ကို မပံ့ပိုးတော့ပါ။ Briar သည် %s(ရက်%dတွင်) အလုပ် မလုပ်တော့ပါ။ ကျေးဇူးပြု၍ Briar ကို စက်ပစ္စည်းအသစ်တွင် ထည့်သွင်းပြီး အကောင့်အသစ်တစ်ခု ဖန်တီးပါ။</item>
</plurals>
<string name="expiry_date_reached">ဤဆော့ဖ်ဝဲသည် သက်တမ်းကုန်သွားပါပြီ။ \nစမ်းသပ်အသုံးပြုခြင်းအတွက် ကျေးဇူးတင်ပါသည်။</string> <string name="expiry_date_reached">ဤဆော့ဖ်ဝဲသည် သက်တမ်းကုန်သွားပါပြီ။ \nစမ်းသပ်အသုံးပြုခြင်းအတွက် ကျေးဇူးတင်ပါသည်။</string>
<string name="download_briar">Briar ကို ဆက်လက်အသုံးပြုရန် နောက်ဆုံးထွက်ထားသည်ကို ဒေါင်းလုဒ်လုပ်ပါ။</string> <string name="download_briar">Briar ကို ဆက်လက်အသုံးပြုရန် နောက်ဆုံးထွက်ထားသည်ကို ဒေါင်းလုဒ်လုပ်ပါ။</string>
<string name="create_new_account">အကောင့်အသစ်ဖွင့်ရန် လိုအပ်သော်လည်း သုံးလက်စနာမည်ပြောင်ကို ဆက်သုံးနိုင်ပါသည်။</string> <string name="create_new_account">အကောင့်အသစ်ဖွင့်ရန် လိုအပ်သော်လည်း သုံးလက်စနာမည်ပြောင်ကို ဆက်သုံးနိုင်ပါသည်။</string>

View File

@@ -49,10 +49,6 @@
<item quantity="one">Dette er en test-versjon av Briar. Din konto vil utløpe om %d dag, og kan ikke fornyes.</item> <item quantity="one">Dette er en test-versjon av Briar. Din konto vil utløpe om %d dag, og kan ikke fornyes.</item>
<item quantity="other">Dette er en test-versjon av Briar. Din konto vil utløpe om %d dager og kan ikke fornyes.</item> <item quantity="other">Dette er en test-versjon av Briar. Din konto vil utløpe om %d dager og kan ikke fornyes.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Android 4 er ikke lenger støttet. Briar vil slutte å virke den %s (om %d dag). Vennligst installer Briar på en nyere enhet og lage en ny konto.</item>
<item quantity="other">Android 4 er ikke lenger støttet. Briar vil slutte å virke den %s (om %d dager). Vennligst installer Briar på en nyere enhet og lage en ny konto.</item>
</plurals>
<string name="expiry_date_reached">Denne programvaren har utløpt.\nTakk for at du testet den.</string> <string name="expiry_date_reached">Denne programvaren har utløpt.\nTakk for at du testet den.</string>
<string name="download_briar">For å fortsette å bruke Briar, vennligst laste ned nyeste utgave.</string> <string name="download_briar">For å fortsette å bruke Briar, vennligst laste ned nyeste utgave.</string>
<string name="create_new_account">Du trenger å lage en ny konto, men du kan benytte samme kallenavn.</string> <string name="create_new_account">Du trenger å lage en ny konto, men du kan benytte samme kallenavn.</string>

View File

@@ -47,11 +47,6 @@
<item quantity="many">Esta é uma versão de teste do Briar. Sua conta irá expirar em %d dias e não poderá ser renovada.</item> <item quantity="many">Esta é uma versão de teste do Briar. Sua conta irá expirar em %d dias e não poderá ser renovada.</item>
<item quantity="other">Esta é uma versão de teste do Briar. Sua conta irá expirar em %d dias e não poderá ser renovada.</item> <item quantity="other">Esta é uma versão de teste do Briar. Sua conta irá expirar em %d dias e não poderá ser renovada.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">A versão 4 do Android já não é mais compatível. Briar deixará de funcionar em %s (dentro de %d dias). Por favor, instale Briar em um novo aparelho e crie uma nova conta.</item>
<item quantity="many">A versão 4 do Android já não é mais compatível. Briar deixará de funcionar em %s (dentro de %d dias). Por favor, instale Briar em um novo dispositivo e crie uma nova conta.</item>
<item quantity="other">A versão 4 do Android já não é mais compatível. Briar deixará de funcionar em %s (dentro de %d dias). Por favor, instale Briar em um novo dispositivo e crie uma nova conta.</item>
</plurals>
<string name="expiry_date_reached">Este software expirou.\nObrigado por testar!</string> <string name="expiry_date_reached">Este software expirou.\nObrigado por testar!</string>
<string name="download_briar">Para continuar usando o Briar, por favor baixe a versão mais recente.</string> <string name="download_briar">Para continuar usando o Briar, por favor baixe a versão mais recente.</string>
<string name="create_new_account">Você precisará criar uma nova conta, mas poderá usar o mesmo apelido.</string> <string name="create_new_account">Você precisará criar uma nova conta, mas poderá usar o mesmo apelido.</string>

View File

@@ -50,11 +50,6 @@
<item quantity="few">Aceasta este o versiune de test Briar. Contul dvs. va expira în %d zile și nu poate fi reînnoit.</item> <item quantity="few">Aceasta este o versiune de test Briar. Contul dvs. va expira în %d zile și nu poate fi reînnoit.</item>
<item quantity="other">Aceasta este o versiune de test Briar. Contul dvs. va expira în %d zile și nu poate fi reînnoit.</item> <item quantity="other">Aceasta este o versiune de test Briar. Contul dvs. va expira în %d zile și nu poate fi reînnoit.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Versiunea Android 4 nu mai este acceptată. Briar nu va mai funcționa pe %s (în decurs de %d zi). Instalați Briar pe un dispozitiv mai nou și creați un cont nou.</item>
<item quantity="few">Versiunea Android 4 nu mai este acceptată. Briar nu va mai funcționa pe %s (în %d zile). Instalați Briar pe un dispozitiv mai nou și creați un cont nou.</item>
<item quantity="other">Versiunea Android 4 nu mai este acceptată. Briar nu va mai funcționa pe %s (în %d zile). Instalați Briar pe un dispozitiv mai nou și creați un cont nou.</item>
</plurals>
<string name="expiry_date_reached">Acest program software a expirat.\nVă mulțumim că l-ați testat!</string> <string name="expiry_date_reached">Acest program software a expirat.\nVă mulțumim că l-ați testat!</string>
<string name="download_briar">Pentru a putea să utilizați Briar în continuare, descărcați cea mai recentă versiune.</string> <string name="download_briar">Pentru a putea să utilizați Briar în continuare, descărcați cea mai recentă versiune.</string>
<string name="create_new_account">Va trebui să creați un cont nou, dar puteți să folosiți același pseudonim.</string> <string name="create_new_account">Va trebui să creați un cont nou, dar puteți să folosiți același pseudonim.</string>

View File

@@ -51,12 +51,6 @@
<item quantity="many">Toto je testovacia verzia aplikácie Briar. Platnosť vášho účtu vyprší o %d dní a nie je možné ho obnoviť.</item> <item quantity="many">Toto je testovacia verzia aplikácie Briar. Platnosť vášho účtu vyprší o %d dní a nie je možné ho obnoviť.</item>
<item quantity="other">Toto je testovacia verzia aplikácie Briar. Platnosť vášho účtu vyprší o %d dní a nie je možné ho obnoviť.</item> <item quantity="other">Toto je testovacia verzia aplikácie Briar. Platnosť vášho účtu vyprší o %d dní a nie je možné ho obnoviť.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Systém Android 4 už nie je podporovaný. Briar prestane fungovať na %s (za %d deň). Nainštalujte si Briar na novšie zariadenie a vytvorte si nový účet.</item>
<item quantity="few">Systém Android 4 už nie je podporovaný. Briar prestane fungovať na %s (za %d dni). Nainštalujte si Briar na novšie zariadenie a vytvorte si nový účet.</item>
<item quantity="many">Systém Android 4 už nie je podporovaný. Briar prestane fungovať na %s (za %d dní). Nainštalujte si Briar na novšie zariadenie a vytvorte si nový účet.</item>
<item quantity="other">Systém Android 4 už nie je podporovaný. Briar prestane fungovať na %s (za %d dní). Nainštalujte si Briar na novšie zariadenie a vytvorte si nový účet.</item>
</plurals>
<string name="expiry_date_reached">Platnosť tohto softvéru vypršala.\nĎakujeme za testovanie!</string> <string name="expiry_date_reached">Platnosť tohto softvéru vypršala.\nĎakujeme za testovanie!</string>
<string name="download_briar">Ak chcete pokračovať v používaní aplikácie Briar, stiahnite si najnovšiu verziu.</string> <string name="download_briar">Ak chcete pokračovať v používaní aplikácie Briar, stiahnite si najnovšiu verziu.</string>
<string name="create_new_account">Budete si musieť vytvoriť nový účet, ale môžete použiť rovnakú prezývku.</string> <string name="create_new_account">Budete si musieť vytvoriť nový účet, ale môžete použiť rovnakú prezývku.</string>

View File

@@ -50,10 +50,6 @@ dhe smund të hapet me këtë version.\n\nJu lutemi, përmirësojeni me versi
<item quantity="one">Ky është një version beta i Briar-it. Llogaria juaj do të skadojë për %d ditë dhe s\mund të rinovohet.</item> <item quantity="one">Ky është një version beta i Briar-it. Llogaria juaj do të skadojë për %d ditë dhe s\mund të rinovohet.</item>
<item quantity="other">Ky është një version beta i Briar-it. Llogaria juaj do të skadojë për %d ditë dhe s\mund të rinovohet.</item> <item quantity="other">Ky është një version beta i Briar-it. Llogaria juaj do të skadojë për %d ditë dhe s\mund të rinovohet.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Android 4 nuk mbulohet më. Briar-i do të reshtë së funksionuari më %s (për %d ditë). Ju lutemi, instalojeni Briar-in në një pajisje më të re dhe krijoni një llogari të re.</item>
<item quantity="other">Android 4 nuk mbulohet më. Briar-i do të reshtë së funksionuari më %s (për %d ditë). Ju lutemi, instalojeni Briar-in në një pajisje më të re dhe krijoni një llogari të re.</item>
</plurals>
<string name="expiry_date_reached">Ky software ka skaduar.\nFaleminderit që e provuat!</string> <string name="expiry_date_reached">Ky software ka skaduar.\nFaleminderit që e provuat!</string>
<string name="download_briar">Që të vazhdoni të përdorni Briar-in, ju lutemi shkarkoni versionin më të ri.</string> <string name="download_briar">Që të vazhdoni të përdorni Briar-in, ju lutemi shkarkoni versionin më të ri.</string>
<string name="create_new_account">Do tju duhet të krijoni një llogari të re, por mund të përdorni të njëjtën nofkë.</string> <string name="create_new_account">Do tju duhet të krijoni një llogari të re, por mund të përdorni të njëjtën nofkë.</string>

View File

@@ -49,10 +49,6 @@
<item quantity="one">Det här är en test-version av Briar. Ditt konto kommer att upphöra om %d dagar och kan ej förnyas.</item> <item quantity="one">Det här är en test-version av Briar. Ditt konto kommer att upphöra om %d dagar och kan ej förnyas.</item>
<item quantity="other">Det här är en test-version av Briar. Ditt konto kommer att upphöra om %d dag och kan ej förnyas.</item> <item quantity="other">Det här är en test-version av Briar. Ditt konto kommer att upphöra om %d dag och kan ej förnyas.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Android 4 stöds ej längre. Briar kommer att sluta fungera den %s (om %d dag). Vänligen installera Briar på en ny enhet och skapa ett nytt konto.</item>
<item quantity="other">Android 4 stöds ej längre. Briar kommer att sluta fungera den %s (om %d dagar). Vänligen installera Briar på en nyare enhet och skapa ett nytt konto.</item>
</plurals>
<string name="expiry_date_reached">Mjukvaran har gått ut.\nTack för att du bidragit till att testa!</string> <string name="expiry_date_reached">Mjukvaran har gått ut.\nTack för att du bidragit till att testa!</string>
<string name="download_briar">För att fortsätta använda Briar, hämta den senaste utgåvan.</string> <string name="download_briar">För att fortsätta använda Briar, hämta den senaste utgåvan.</string>
<string name="create_new_account">Du måste skapa ett nytt konto, men du kan använda samma användarnamn.</string> <string name="create_new_account">Du måste skapa ett nytt konto, men du kan använda samma användarnamn.</string>

View File

@@ -49,10 +49,6 @@
<item quantity="one">Bu Briar\'ın deneme sürümüdür. Hesabınız %d gün içinde sona erecek ve yenilenemez.</item> <item quantity="one">Bu Briar\'ın deneme sürümüdür. Hesabınız %d gün içinde sona erecek ve yenilenemez.</item>
<item quantity="other">Bu Briar\'ın deneme sürümüdür. Hesabınız %d gün içinde geçersiz olacak ve yenilenmeyecektir.</item> <item quantity="other">Bu Briar\'ın deneme sürümüdür. Hesabınız %d gün içinde geçersiz olacak ve yenilenmeyecektir.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Android 4 artık desteklenmiyor. Briar %s tarihinde (%d gün sonra) çalışmayı durduracaktır. Lütfen Briar\'ı daha yeni bir aygıta yükleyin ve yeni bir hesap oluşturun.</item>
<item quantity="other">Android 4 artık desteklenmiyor. Briar %s tarihinde (%d gün sonra) çalışmayı durduracaktır. Lütfen Briar\'ı daha yeni bir aygıta yükleyin ve yeni bir hesap oluşturun.</item>
</plurals>
<string name="expiry_date_reached">Bu yazılımın süresi doldu.\nDenediğiniz için teşekkürler!</string> <string name="expiry_date_reached">Bu yazılımın süresi doldu.\nDenediğiniz için teşekkürler!</string>
<string name="download_briar">Briar\'ı kullanmaya devam etmek için lütfen en son sürümü indirin.</string> <string name="download_briar">Briar\'ı kullanmaya devam etmek için lütfen en son sürümü indirin.</string>
<string name="create_new_account">Yeni bir hesap oluşturmanız gerekecek, fakat aynı takma adı kullanabilirsiniz.</string> <string name="create_new_account">Yeni bir hesap oluşturmanız gerekecek, fakat aynı takma adı kullanabilirsiniz.</string>

View File

@@ -47,12 +47,6 @@
<item quantity="many">Це тестова версія Briar. Термін дії вашого профілю спливає через %d днів. Його не можна продовжити.</item> <item quantity="many">Це тестова версія Briar. Термін дії вашого профілю спливає через %d днів. Його не можна продовжити.</item>
<item quantity="other">Це тестова версія Briar. Термін дії вашого профілю спливає через %d дня. Його не можна продовжити.</item> <item quantity="other">Це тестова версія Briar. Термін дії вашого профілю спливає через %d дня. Його не можна продовжити.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Android 4 більше не підтримується. Briar припинить роботу %s (за %d день). Будь ласка, встановіть Briar на новіший пристрій і створіть новий обліковий запис.</item>
<item quantity="few">Android 4 більше не підтримується. Briar припинить роботу %s (за %d дні). Будь ласка, встановіть Briar на новіший пристрій і створіть новий обліковий запис.</item>
<item quantity="many">Android 4 більше не підтримується. Briar припинить роботу %s (за %d днів). Будь ласка, встановіть Briar на новіший пристрій і створіть новий обліковий запис.</item>
<item quantity="other">Android 4 більше не підтримується. Briar припинить роботу %s (за %d днів). Будь ласка, встановіть Briar на новіший пристрій і створіть новий обліковий запис.</item>
</plurals>
<string name="expiry_date_reached">У цієї програми закінчився термін дії\nДякуємо, що тестували її!</string> <string name="expiry_date_reached">У цієї програми закінчився термін дії\nДякуємо, що тестували її!</string>
<string name="download_briar">Щоб далі користуватися Briar, будь ласка, завантажте найновіший випуск.</string> <string name="download_briar">Щоб далі користуватися Briar, будь ласка, завантажте найновіший випуск.</string>
<string name="create_new_account">Вам знадобиться створити новий обліковий запис, але ви можете використовувати те саме ім\'я користувача.</string> <string name="create_new_account">Вам знадобиться створити новий обліковий запис, але ви можете використовувати те саме ім\'я користувача.</string>
@@ -240,6 +234,7 @@
<string name="menu_contact">Контакти</string> <string name="menu_contact">Контакти</string>
<!--Adding Contacts--> <!--Adding Contacts-->
<string name="add_contact_title">Додати контакт поблизу</string> <string name="add_contact_title">Додати контакт поблизу</string>
<string name="add_contact_error_two_way">Чи Ви відсканували QR коди один одного?</string>
<string name="face_to_face">Ви маєте зустрітися з особою, яку ви бажаєте додати до списку контактів.\n\nЦе виключить можливість у майбутньому будь-кому видавати себе за вас або читати ваші повідомлення.</string> <string name="face_to_face">Ви маєте зустрітися з особою, яку ви бажаєте додати до списку контактів.\n\nЦе виключить можливість у майбутньому будь-кому видавати себе за вас або читати ваші повідомлення.</string>
<string name="continue_button">Продовжити</string> <string name="continue_button">Продовжити</string>
<string name="try_again_button">Спробувати ще раз</string> <string name="try_again_button">Спробувати ще раз</string>

View File

@@ -48,9 +48,6 @@
<plurals name="expiry_warning"> <plurals name="expiry_warning">
<item quantity="other">这是 Briar 的一个测试版本。您的帐户将在 %d 天后到期,且无法延期。</item> <item quantity="other">这是 Briar 的一个测试版本。您的帐户将在 %d 天后到期,且无法延期。</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="other">本应用不再支持 Android 4。Briar 将于 %s 停止工作(%d天内。请在更高版本系统上安装 Briar 并创建一个新帐户</item>
</plurals>
<string name="expiry_date_reached">本软件已过期。\n感谢您的测试</string> <string name="expiry_date_reached">本软件已过期。\n感谢您的测试</string>
<string name="download_briar">请下载最新版本以继续使用 Briar</string> <string name="download_briar">请下载最新版本以继续使用 Briar</string>
<string name="create_new_account">您将需要创建一个新帐户,但可以使用相同的昵称。</string> <string name="create_new_account">您将需要创建一个新帐户,但可以使用相同的昵称。</string>

View File

@@ -51,9 +51,6 @@
<plurals name="expiry_warning"> <plurals name="expiry_warning">
<item quantity="other">這是 Briar 的測試版本。您的帳戶將在 %d 天後到期,且無法延期。</item> <item quantity="other">這是 Briar 的測試版本。您的帳戶將在 %d 天後到期,且無法延期。</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="other">Briar 已不再支援 Android 4它將在%s停止運作(%d天時間)請在較新的設備上安裝Briar 並建立新帳號。</item>
</plurals>
<string name="expiry_date_reached">本軟件已過期。\n感謝您的測試</string> <string name="expiry_date_reached">本軟件已過期。\n感謝您的測試</string>
<string name="download_briar">爲了繼續使用 Briar請下載最新版。</string> <string name="download_briar">爲了繼續使用 Briar請下載最新版。</string>
<string name="create_new_account">您要創建一個新帳戶,但可以續用現時的暱稱。</string> <string name="create_new_account">您要創建一個新帳戶,但可以續用現時的暱稱。</string>

View File

@@ -733,6 +733,8 @@
<!-- Settings Actions --> <!-- Settings Actions -->
<string name="pref_category_actions">Actions</string> <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> <string name="send_feedback">Send feedback</string>
<!-- Link Warning --> <!-- Link Warning -->
@@ -787,6 +789,7 @@
<string name="permission_camera_title">Camera permission</string> <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_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_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_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_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> <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_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_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_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_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> <string name="wifi_settings_request_enable_body">To create a Wi-Fi hotspot, Briar needs to use Wi-Fi. Please enable it.</string>

View File

@@ -29,11 +29,6 @@
android:title="@string/mailbox_settings_title" android:title="@string/mailbox_settings_title"
app:icon="@drawable/ic_mailbox" /> 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 <PreferenceCategory
android:key="pref_key_actions" android:key="pref_key_actions"
android:layout="@layout/preferences_category" android:layout="@layout/preferences_category"
@@ -47,6 +42,11 @@
android:targetClass="org.briarproject.briar.android.hotspot.HotspotActivity" android:targetClass="org.briarproject.briar.android.hotspot.HotspotActivity"
android:targetPackage="@string/app_package" /> android:targetPackage="@string/app_package" />
</Preference> </Preference>
<Preference
android:key="pref_key_share_app_link"
android:title="@string/share_app_link"
app:icon="@drawable/ic_settings_share_link">
</Preference>
<Preference <Preference
android:key="pref_key_send_feedback" android:key="pref_key_send_feedback"
android:title="@string/send_feedback" android:title="@string/send_feedback"
@@ -74,4 +74,10 @@
</PreferenceCategory> </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> </PreferenceScreen>

View File

@@ -74,6 +74,13 @@ public interface BlogManager {
@Nullable String comment, BlogPostHeader parentHeader) @Nullable String comment, BlogPostHeader parentHeader)
throws DbException; 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. * Returns the blog with the given ID.
*/ */
@@ -99,6 +106,11 @@ public interface BlogManager {
*/ */
Collection<Blog> getBlogs() throws DbException; 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. * 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; 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. * Registers a hook to be called whenever a blog is removed.
*/ */

View File

@@ -96,12 +96,24 @@ public interface ConversationManager {
*/ */
DeletionResult deleteAllMessages(ContactId c) throws DbException; DeletionResult deleteAllMessages(ContactId c) throws DbException;
/**
* Deletes all messages exchanged with the given contact.
*/
DeletionResult deleteAllMessages(Transaction txn, ContactId c)
throws DbException;
/** /**
* Deletes the given set of messages associated with the given contact. * Deletes the given set of messages associated with the given contact.
*/ */
DeletionResult deleteMessages(ContactId c, Collection<MessageId> messageIds) DeletionResult deleteMessages(ContactId c, Collection<MessageId> messageIds)
throws DbException; throws DbException;
/**
* Deletes the given set of messages associated with the given contact.
*/
DeletionResult deleteMessages(Transaction txn, ContactId c,
Collection<MessageId> messageIds) throws DbException;
@NotNullByDefault @NotNullByDefault
interface ConversationClient { interface ConversationClient {

View File

@@ -257,12 +257,19 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
public void addLocalComment(LocalAuthor author, GroupId groupId, public void addLocalComment(LocalAuthor author, GroupId groupId,
@Nullable String comment, BlogPostHeader parentHeader) @Nullable String comment, BlogPostHeader parentHeader)
throws DbException { 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(); MessageType type = parentHeader.getType();
if (type != POST && type != COMMENT) if (type != POST && type != COMMENT)
throw new IllegalArgumentException("Comment on unknown type!"); throw new IllegalArgumentException("Comment on unknown type!");
Transaction txn = db.startTransaction(false);
try { try {
// Wrap post that we are commenting on // Wrap post that we are commenting on
MessageId parentOriginalId = MessageId parentOriginalId =
@@ -281,6 +288,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
meta.put(KEY_ORIGINAL_PARENT_MSG_ID, parentOriginalId); meta.put(KEY_ORIGINAL_PARENT_MSG_ID, parentOriginalId);
meta.put(KEY_PARENT_MSG_ID, parentCurrentId); meta.put(KEY_PARENT_MSG_ID, parentCurrentId);
meta.put(KEY_AUTHOR, clientHelper.toList(author)); meta.put(KEY_AUTHOR, clientHelper.toList(author));
meta.put(KEY_READ, true);
// Send comment // Send comment
clientHelper.addLocalMessage(txn, message, meta, true, false); clientHelper.addLocalMessage(txn, message, meta, true, false);
@@ -290,13 +298,10 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
message.getId(), meta); message.getId(), meta);
BlogPostAddedEvent event = new BlogPostAddedEvent(groupId, h, true); BlogPostAddedEvent event = new BlogPostAddedEvent(groupId, h, true);
txn.attach(event); txn.attach(event);
db.commitTransaction(txn);
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
throw new IllegalArgumentException("Invalid key of author", e); throw new IllegalArgumentException("Invalid key of author", e);
} finally {
db.endTransaction(txn);
} }
} }
@@ -429,18 +434,17 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
@Override @Override
public Collection<Blog> getBlogs() throws DbException { public Collection<Blog> getBlogs() throws DbException {
return db.transactionWithResult(true, this::getBlogs);
}
@Override
public Collection<Blog> getBlogs(Transaction txn) throws DbException {
try { try {
List<Blog> blogs = new ArrayList<>(); List<Blog> blogs = new ArrayList<>();
Collection<Group> groups; Collection<Group> groups =
Transaction txn = db.startTransaction(true); db.getGroups(txn, CLIENT_ID, MAJOR_VERSION);
try { for (Group g : groups) {
groups = db.getGroups(txn, CLIENT_ID, MAJOR_VERSION); blogs.add(blogFactory.parseBlog(g));
for (Group g : groups) {
blogs.add(blogFactory.parseBlog(g));
}
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);
} }
return blogs; return blogs;
} catch (FormatException e) { } catch (FormatException e) {
@@ -555,10 +559,18 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
@Override @Override
public void setReadFlag(MessageId m, boolean read) throws DbException { 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 { try {
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
meta.put(KEY_READ, read); meta.put(KEY_READ, read);
clientHelper.mergeMessageMetadata(m, meta); clientHelper.mergeMessageMetadata(txn, m, meta);
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -146,27 +146,37 @@ class ConversationManagerImpl implements ConversationManager {
@Override @Override
public DeletionResult deleteAllMessages(ContactId c) throws DbException { public DeletionResult deleteAllMessages(ContactId c) throws DbException {
return db.transactionWithResult(false, txn -> { return db.transactionWithResult(false, txn ->
DeletionResult result = new DeletionResult(); deleteAllMessages(txn, c));
for (ConversationClient client : clients) { }
result.addDeletionResult(client.deleteAllMessages(txn, c));
} @Override
return result; public DeletionResult deleteAllMessages(Transaction txn, ContactId c)
}); throws DbException {
DeletionResult result = new DeletionResult();
for (ConversationClient client : clients) {
result.addDeletionResult(client.deleteAllMessages(txn, c));
}
return result;
} }
@Override @Override
public DeletionResult deleteMessages(ContactId c, public DeletionResult deleteMessages(ContactId c,
Collection<MessageId> toDelete) throws DbException { Collection<MessageId> toDelete) throws DbException {
return db.transactionWithResult(false, txn -> { return db.transactionWithResult(false, txn ->
DeletionResult result = new DeletionResult(); deleteMessages(txn, c, toDelete));
for (ConversationClient client : clients) { }
Set<MessageId> idSet = client.getMessageIds(txn, c);
idSet.retainAll(toDelete); @Override
result.addDeletionResult(client.deleteMessages(txn, c, idSet)); public DeletionResult deleteMessages(Transaction txn, ContactId c,
} Collection<MessageId> toDelete) throws DbException {
return result; DeletionResult result = new DeletionResult();
}); for (ConversationClient client : clients) {
Set<MessageId> idSet = client.getMessageIds(txn, c);
idSet.retainAll(toDelete);
result.addDeletionResult(client.deleteMessages(txn, c, idSet));
}
return result;
} }
} }

View File

@@ -20,6 +20,7 @@ import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.test.BrambleMockTestCase; 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.Blog;
import org.briarproject.briar.api.blog.BlogCommentHeader; import org.briarproject.briar.api.blog.BlogCommentHeader;
import org.briarproject.briar.api.blog.BlogFactory; 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_MSG_ID, commentId),
new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, messageId), new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, messageId),
new BdfEntry(KEY_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() {{ context.checking(new DbExpectations() {{
oneOf(db).startTransaction(false); oneOf(db).transaction(with(false), withDbRunnable(txn));
will(returnValue(txn));
// Create the comment // Create the comment
oneOf(blogPostFactory).createBlogComment(blog1.getId(), oneOf(blogPostFactory).createBlogComment(blog1.getId(),
localAuthor1, comment, messageId, messageId); localAuthor1, comment, messageId, messageId);
@@ -422,8 +423,6 @@ public class BlogManagerImplTest extends BrambleMockTestCase {
will(returnValue(localAuthor1)); will(returnValue(localAuthor1));
oneOf(authorManager).getAuthorInfo(txn, localAuthor1.getId()); oneOf(authorManager).getAuthorInfo(txn, localAuthor1.getId());
will(returnValue(ourselvesInfo)); will(returnValue(ourselvesInfo));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}}); }});
BlogPostHeader postHeader = new BlogPostHeader(POST, blog1.getId(), 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_MSG_ID, commentId),
new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, messageId), new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, messageId),
new BdfEntry(KEY_PARENT_MSG_ID, wrappedPostId), 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() {{ context.checking(new DbExpectations() {{
oneOf(db).startTransaction(false); oneOf(db).transaction(with(false), withDbRunnable(txn));
will(returnValue(txn));
// Wrap the original post for blog 2 // Wrap the original post for blog 2
oneOf(clientHelper).getMessageAsList(txn, messageId); oneOf(clientHelper).getMessageAsList(txn, messageId);
will(returnValue(originalPostBody)); will(returnValue(originalPostBody));
@@ -533,8 +532,6 @@ public class BlogManagerImplTest extends BrambleMockTestCase {
will(returnValue(localAuthor1)); will(returnValue(localAuthor1));
oneOf(authorManager).getAuthorInfo(txn, localAuthor1.getId()); oneOf(authorManager).getAuthorInfo(txn, localAuthor1.getId());
will(returnValue(verifiedInfo)); will(returnValue(verifiedInfo));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}}); }});
BlogPostHeader originalPostHeader = new BlogPostHeader(POST, 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_MSG_ID, commentId),
new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, rssMessageId), new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, rssMessageId),
new BdfEntry(KEY_PARENT_MSG_ID, wrappedPostId), 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() {{ context.checking(new DbExpectations() {{
oneOf(db).startTransaction(false); oneOf(db).transaction(with(false), withDbRunnable(txn));
will(returnValue(txn));
// Wrap the original post for blog 1 // Wrap the original post for blog 1
oneOf(clientHelper).getMessageAsList(txn, rssMessageId); oneOf(clientHelper).getMessageAsList(txn, rssMessageId);
will(returnValue(originalPostBody)); will(returnValue(originalPostBody));
@@ -642,8 +639,6 @@ public class BlogManagerImplTest extends BrambleMockTestCase {
will(returnValue(wrappedPostMeta)); will(returnValue(wrappedPostMeta));
oneOf(clientHelper).parseAndValidateAuthor(rssAuthorList); oneOf(clientHelper).parseAndValidateAuthor(rssAuthorList);
will(returnValue(rssLocalAuthor)); will(returnValue(rssLocalAuthor));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}}); }});
BlogPostHeader originalPostHeader = new BlogPostHeader(POST, 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_MSG_ID, localCommentId),
new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, originalCommentId), new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, originalCommentId),
new BdfEntry(KEY_PARENT_MSG_ID, wrappedCommentId), 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() {{ context.checking(new DbExpectations() {{
oneOf(db).startTransaction(false); oneOf(db).transaction(with(false), withDbRunnable(txn));
will(returnValue(txn));
// Rewrap the wrapped post for blog 2 // Rewrap the wrapped post for blog 2
oneOf(clientHelper).getMessageAsList(txn, wrappedPostId); oneOf(clientHelper).getMessageAsList(txn, wrappedPostId);
will(returnValue(wrappedPostBody)); will(returnValue(wrappedPostBody));
@@ -790,8 +785,6 @@ public class BlogManagerImplTest extends BrambleMockTestCase {
will(returnValue(rewrappedPostMeta)); will(returnValue(rewrappedPostMeta));
oneOf(clientHelper).parseAndValidateAuthor(rssAuthorList); oneOf(clientHelper).parseAndValidateAuthor(rssAuthorList);
will(returnValue(rssLocalAuthor)); will(returnValue(rssLocalAuthor));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}}); }});
BlogPostHeader wrappedPostHeader = new BlogPostHeader(WRAPPED_POST, BlogPostHeader wrappedPostHeader = new BlogPostHeader(WRAPPED_POST,

View File

@@ -11,6 +11,8 @@ import org.briarproject.bramble.util.Base32;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import java.util.Locale;
import static java.lang.System.arraycopy; import static java.lang.System.arraycopy;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.FORMAT_VERSION; import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.FORMAT_VERSION;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.RAW_LINK_BYTES; import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.RAW_LINK_BYTES;
@@ -52,7 +54,7 @@ public class BriarTestUtils {
byte[] publicKey = keyPair.getPublic().getEncoded(); byte[] publicKey = keyPair.getPublic().getEncoded();
linkBytes[0] = FORMAT_VERSION; linkBytes[0] = FORMAT_VERSION;
arraycopy(publicKey, 0, linkBytes, 1, RAW_LINK_BYTES - 1); arraycopy(publicKey, 0, linkBytes, 1, RAW_LINK_BYTES - 1);
return ("briar://" + Base32.encode(linkBytes)).toLowerCase(); return ("briar://" + Base32.encode(linkBytes)).toLowerCase(Locale.US);
} }
} }

View File

@@ -9,7 +9,7 @@ or to develop your own user interface for it.
The REST API peer comes as a `jar` file The REST API peer comes as a `jar` file
and needs a Java Runtime Environment (JRE) that supports at least Java 8. 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: 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:aarch64LinuxJar
$ ./gradlew --configure-on-demand briar-headless:armhfLinuxJar $ ./gradlew --configure-on-demand briar-headless:armhfLinuxJar
$ ./gradlew --configure-on-demand briar-headless:windowsJar $ ./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: 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. Note that the HTTP request sets an `Authorization` header with the bearer token.
A missing or wrong token will result in a `401` response. 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 ## REST API
### Listing all contacts ### Listing all contacts

View File

@@ -20,6 +20,9 @@ configurations {
linux { linux {
extendsFrom runtimeClasspath extendsFrom runtimeClasspath
} }
macos {
extendsFrom runtimeClasspath
}
} }
sourceCompatibility = 1.8 sourceCompatibility = 1.8
@@ -38,6 +41,10 @@ dependencies {
windows "org.briarproject:obfs4proxy-windows:$obfs4proxy_version" windows "org.briarproject:obfs4proxy-windows:$obfs4proxy_version"
windows "org.briarproject:snowflake-windows:$snowflake_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 "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'io.javalin:javalin:3.5.0' implementation 'io.javalin:javalin:3.5.0'
implementation 'org.slf4j:slf4j-simple:1.7.30' implementation 'org.slf4j:slf4j-simple:1.7.30'
@@ -89,7 +96,7 @@ void jarFactory(Jar jarTask, os, architecture, configuration) {
} }
{ {
it.duplicatesStrategy(DuplicatesStrategy.EXCLUDE) it.duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
if (os == "linux") { if (os == "linux" || os == "macos") {
String[] architectures = [ String[] architectures = [
"aarch64", "aarch64",
"armhf", "armhf",
@@ -100,6 +107,7 @@ void jarFactory(Jar jarTask, os, architecture, configuration) {
exclude arch + "/obfs4proxy" exclude arch + "/obfs4proxy"
exclude arch + "/tor" exclude arch + "/tor"
exclude arch + "/snowflake" exclude arch + "/snowflake"
exclude arch + "/libevent-*.dylib"
} }
} }
} }
@@ -153,10 +161,22 @@ task windowsJar(type: Jar) {
jarFactory(it, 'windows', 'x86_64', configurations.windows) 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 { task linuxJars {
dependsOn(aarch64LinuxJar, armhfLinuxJar, x86LinuxJar) 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 // 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. // as 'generated source code' for correct highlighting and resolve in IDE.
idea { idea {

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.identity
import org.briarproject.bramble.api.identity.Author import org.briarproject.bramble.api.identity.Author
import org.briarproject.briar.api.identity.AuthorInfo import org.briarproject.briar.api.identity.AuthorInfo
import org.briarproject.briar.headless.json.JsonDict import org.briarproject.briar.headless.json.JsonDict
import java.util.Locale
fun Author.output() = JsonDict( fun Author.output() = JsonDict(
"formatVersion" to formatVersion, "formatVersion" to formatVersion,
@@ -11,4 +12,4 @@ fun Author.output() = JsonDict(
"publicKey" to publicKey.encoded "publicKey" to publicKey.encoded
) )
fun AuthorInfo.Status.output() = name.toLowerCase() fun AuthorInfo.Status.output() = name.lowercase(Locale.US)

View File

@@ -3,6 +3,7 @@ package org.briarproject.briar.headless
import dagger.Component import dagger.Component
import org.briarproject.bramble.BrambleCoreEagerSingletons import org.briarproject.bramble.BrambleCoreEagerSingletons
import org.briarproject.bramble.BrambleCoreModule import org.briarproject.bramble.BrambleCoreModule
import org.briarproject.bramble.BrambleJavaEagerSingletons
import org.briarproject.bramble.BrambleJavaModule import org.briarproject.bramble.BrambleJavaModule
import org.briarproject.briar.BriarCoreEagerSingletons import org.briarproject.briar.BriarCoreEagerSingletons
import org.briarproject.briar.BriarCoreModule import org.briarproject.briar.BriarCoreModule
@@ -19,7 +20,7 @@ import javax.inject.Singleton
) )
@Singleton @Singleton
internal interface BriarHeadlessApp : BrambleCoreEagerSingletons, BriarCoreEagerSingletons, internal interface BriarHeadlessApp : BrambleCoreEagerSingletons, BriarCoreEagerSingletons,
HeadlessEagerSingletons { BrambleJavaEagerSingletons, HeadlessEagerSingletons {
fun getRouter(): Router fun getRouter(): Router

View File

@@ -18,6 +18,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory
import org.briarproject.bramble.battery.DefaultBatteryManagerModule import org.briarproject.bramble.battery.DefaultBatteryManagerModule
import org.briarproject.bramble.event.DefaultEventExecutorModule 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.UnixTorPluginFactory
import org.briarproject.bramble.plugin.tor.WindowsTorPluginFactory import org.briarproject.bramble.plugin.tor.WindowsTorPluginFactory
import org.briarproject.bramble.system.ClockModule import org.briarproject.bramble.system.ClockModule
@@ -92,10 +93,12 @@ internal class HeadlessModule(private val appDir: File) {
@Singleton @Singleton
internal fun providePluginConfig( internal fun providePluginConfig(
unixTor: UnixTorPluginFactory, unixTor: UnixTorPluginFactory,
macTor: MacTorPluginFactory,
winTor: WindowsTorPluginFactory winTor: WindowsTorPluginFactory
): PluginConfig { ): PluginConfig {
val duplex: List<DuplexPluginFactory> = when { val duplex: List<DuplexPluginFactory> = when {
isLinux() || isMac() -> listOf(unixTor) isLinux() -> listOf(unixTor)
isMac() -> listOf(macTor)
isWindows() -> listOf(winTor) isWindows() -> listOf(winTor)
else -> emptyList() else -> emptyList()
} }

View File

@@ -8,6 +8,7 @@ import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.int import com.github.ajalt.clikt.parameters.types.int
import org.bouncycastle.util.encoders.Base64.toBase64String import org.bouncycastle.util.encoders.Base64.toBase64String
import org.briarproject.bramble.BrambleCoreEagerSingletons import org.briarproject.bramble.BrambleCoreEagerSingletons
import org.briarproject.bramble.BrambleJavaEagerSingletons
import org.briarproject.bramble.util.OsUtils.isLinux import org.briarproject.bramble.util.OsUtils.isLinux
import org.briarproject.bramble.util.OsUtils.isMac import org.briarproject.bramble.util.OsUtils.isMac
import org.briarproject.briar.BriarCoreEagerSingletons 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.Level.WARNING
import java.util.logging.LogManager 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 val DEFAULT_DATA_DIR = getProperty("user.home") + separator + ".briar"
private class Main : CliktCommand( private class Main : CliktCommand(
@@ -77,6 +79,7 @@ private class Main : CliktCommand(
// We need to load the eager singletons directly after making the // We need to load the eager singletons directly after making the
// dependency graphs // dependency graphs
BrambleCoreEagerSingletons.Helper.injectEagerSingletons(app) BrambleCoreEagerSingletons.Helper.injectEagerSingletons(app)
BrambleJavaEagerSingletons.Helper.injectEagerSingletons(app)
BriarCoreEagerSingletons.Helper.injectEagerSingletons(app) BriarCoreEagerSingletons.Helper.injectEagerSingletons(app)
HeadlessEagerSingletons.Helper.injectEagerSingletons(app) HeadlessEagerSingletons.Helper.injectEagerSingletons(app)

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.identity.output
import org.briarproject.briar.api.blog.BlogPostHeader import org.briarproject.briar.api.blog.BlogPostHeader
import org.briarproject.briar.api.blog.MessageType import org.briarproject.briar.api.blog.MessageType
import org.briarproject.briar.headless.json.JsonDict import org.briarproject.briar.headless.json.JsonDict
import java.util.Locale
internal fun BlogPostHeader.output(text: String) = JsonDict( internal fun BlogPostHeader.output(text: String) = JsonDict(
"text" to text, "text" to text,
@@ -18,4 +19,4 @@ internal fun BlogPostHeader.output(text: String) = JsonDict(
"timestampReceived" to timeReceived "timestampReceived" to timeReceived
) )
internal fun MessageType.output() = name.toLowerCase() internal fun MessageType.output() = name.lowercase(Locale.US)

View File

@@ -3,6 +3,7 @@ package org.briarproject.briar.headless
import dagger.Component import dagger.Component
import org.briarproject.bramble.BrambleCoreEagerSingletons import org.briarproject.bramble.BrambleCoreEagerSingletons
import org.briarproject.bramble.BrambleCoreModule import org.briarproject.bramble.BrambleCoreModule
import org.briarproject.bramble.BrambleJavaEagerSingletons
import org.briarproject.bramble.BrambleJavaModule import org.briarproject.bramble.BrambleJavaModule
import org.briarproject.bramble.api.crypto.CryptoComponent import org.briarproject.bramble.api.crypto.CryptoComponent
import org.briarproject.briar.BriarCoreEagerSingletons import org.briarproject.briar.BriarCoreEagerSingletons
@@ -20,7 +21,7 @@ import javax.inject.Singleton
) )
@Singleton @Singleton
internal interface BriarHeadlessTestApp : BrambleCoreEagerSingletons, BriarCoreEagerSingletons, internal interface BriarHeadlessTestApp : BrambleCoreEagerSingletons, BriarCoreEagerSingletons,
HeadlessEagerSingletons { BrambleJavaEagerSingletons, HeadlessEagerSingletons {
fun getRouter(): Router fun getRouter(): Router

View File

@@ -8,6 +8,7 @@ import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
import org.briarproject.bramble.BrambleCoreEagerSingletons import org.briarproject.bramble.BrambleCoreEagerSingletons
import org.briarproject.bramble.BrambleJavaEagerSingletons
import org.briarproject.bramble.api.crypto.CryptoComponent import org.briarproject.bramble.api.crypto.CryptoComponent
import org.briarproject.briar.BriarCoreEagerSingletons import org.briarproject.briar.BriarCoreEagerSingletons
import org.briarproject.briar.api.test.TestDataCreator import org.briarproject.briar.api.test.TestDataCreator
@@ -38,6 +39,7 @@ abstract class IntegrationTest {
.headlessTestModule(HeadlessTestModule(dataDir)) .headlessTestModule(HeadlessTestModule(dataDir))
.build() .build()
BrambleCoreEagerSingletons.Helper.injectEagerSingletons(app) BrambleCoreEagerSingletons.Helper.injectEagerSingletons(app)
BrambleJavaEagerSingletons.Helper.injectEagerSingletons(app)
BriarCoreEagerSingletons.Helper.injectEagerSingletons(app) BriarCoreEagerSingletons.Helper.injectEagerSingletons(app)
HeadlessEagerSingletons.Helper.injectEagerSingletons(app) HeadlessEagerSingletons.Helper.injectEagerSingletons(app)
router = app.getRouter() router = app.getRouter()

View File

@@ -35,8 +35,8 @@ dependencyVerification {
'javax.servlet:javax.servlet-api:3.1.0:javax.servlet-api-3.1.0.jar:af456b2dd41c4e82cf54f3e743bc678973d9fe35bd4d3071fa05c7e5333b8482', '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-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.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-platform:5.13.0:jna-platform-5.13.0.jar:474d7b88f6e97009b6ec1d98c3024dd95c23187c65dabfbc35331bcac3d173dd',
'net.java.dev.jna:jna:4.5.2:jna-4.5.2.jar:0c8eb7acf67261656d79005191debaba3b6bf5dd60a43735a245429381dbecff', '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.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', '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', '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:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b',
'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011', '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-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:obfs4proxy-windows:0.0.14-tor2:obfs4proxy-windows-0.0.14-tor2.jar:b5fbd00a8c35ccf095b265370752390e4cd46055331049c4dfcc236dc9c650ac',
'org.briarproject:onionwrapper-core:0.0.3:onionwrapper-core-0.0.3.jar:2645dd7e4f2e73fc2ba30696aeac4c960ef8b8f405d85e529fdf8b105ef374f1', 'org.briarproject:onionwrapper-core:0.0.4:onionwrapper-core-0.0.4.jar:28a01a62e96aa763989a8afc325abd3bee54f8021269f91aa48b247a6e717870',
'org.briarproject:onionwrapper-java:0.0.3:onionwrapper-java-0.0.3.jar:25e3a28010e054d7976ff05b8bfed0f25fb60e748a38aa3236451fc4339955e8', '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-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: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-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.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-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.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb',

View File

@@ -37,7 +37,7 @@ buildscript {
junit_version = "4.13.2" junit_version = "4.13.2"
jmock_version = '2.12.0' jmock_version = '2.12.0'
mockwebserver_version = '4.10.0' mockwebserver_version = '4.10.0'
onionwrapper_version = '0.0.3' onionwrapper_version = '0.0.4'
} }
dependencies { dependencies {
// upgrading this let's us run into https://github.com/gradle/gradle/issues/20330 // upgrading this let's us run into https://github.com/gradle/gradle/issues/20330