Compare commits

...

111 Commits

Author SHA1 Message Date
Nico Alt
fcd52261fe Temporarily add self-built Windows tor binary
Until it's officially published by the Briar Project.
2022-02-09 00:00:01 +00:00
Nico Alt
fa6f7e62ed Make use of Tor on Windows 2022-02-09 00:00:00 +00:00
akwizgran
36670a8bf6 Bump version numbers for 1.4.4 release. 2022-01-27 11:56:54 +00:00
akwizgran
32d62f9960 Update translations. 2022-01-27 11:56:10 +00:00
akwizgran
eafd6a1ca1 Merge branch '2143-security-exception-image-loading' into 'master'
Add FLAG_GRANT_READ_URI_PERMISSION when getting content

Closes #2143

See merge request briar/briar!1583
2022-01-27 11:47:03 +00:00
Torsten Grote
1614e72c43 Add FLAG_GRANT_READ_URI_PERMISSION when getting content 2022-01-27 07:57:51 -03:00
akwizgran
a53a49e543 Merge branch '2250-refuse-to-start-if-android4-expired' into 'master'
Refuse to start app on Android 4 beyond expiry date

Closes #2250

See merge request briar/briar!1578
2022-01-18 15:03:25 +00:00
akwizgran
78b993bda4 Merge branch 'mailbox-integration-test' into 'master'
Add MailboxIntegrationTest against a real mailbox instance

See merge request briar/briar!1575
2022-01-18 14:46:38 +00:00
akwizgran
6b956611e7 Merge branch '2251-warn-briar-expires-on-android4' into 'master'
Show expiry warning when running on Android 4

Closes #2251

See merge request briar/briar!1577
2022-01-18 14:36:33 +00:00
Torsten Grote
d0c3c1f9f6 Move wait-for-mailbox.sh to bramble-core/src/test/bash 2022-01-18 11:33:08 -03:00
Torsten Grote
24d058cdcc Merge branch 'add-more-feature-flags-for-desktop' into 'master'
Add feature flags for private groups, forums and blogs

See merge request briar/briar!1572
2022-01-18 11:41:26 +00:00
Torsten Grote
a9ab7fd60f Do not kill mailbox at the end of integration test 2022-01-17 11:10:49 -03:00
Daniel Lublin
9e5201d571 Don't try to sign out; we know we aren't signed in 2022-01-17 13:09:40 +01:00
Daniel Lublin
39eebe4c02 Remove, BaseActivity does this 2022-01-17 11:21:49 +01:00
Daniel Lublin
171df265ab Let our activity actually show up, by avoiding to extend BriarActivity
If user needs to sign in or create an account, BriarActivity launches
StartupActivity. But we want to show up before that.

Implement our own signOut with BriarActivity as a template.
2022-01-17 11:04:50 +01:00
Daniel Lublin
9436757215 Reuse existing string (same kind of context) 2022-01-13 12:59:55 +01:00
Daniel Lublin
75370c8124 Refuse to start on Android 4 beyond the set expiry date 2022-01-13 12:59:55 +01:00
Daniel Lublin
10dceafde1 Show expiry warning when running on Android 4
After a set date a snackbar is shown, warning that Briar will stop
working at a later set date.
2022-01-13 12:59:03 +01:00
akwizgran
e3126f931e Merge branch 'move-proguard-rules' into 'master'
Move ProGuard rules for Briar to briar-android

See merge request briar/briar!1580
2022-01-13 11:44:38 +00:00
akwizgran
6ddedbba36 Move ProGuard rules for Briar to briar-android. 2022-01-13 10:37:10 +00:00
akwizgran
982637a0b0 Merge branch 'correct-proguard' into 'master'
Restore proguard rule to keep us alive

See merge request briar/briar!1579
2022-01-13 10:35:37 +00:00
Daniel Lublin
78ef8c8117 Restore proguard, letting us stay alive by keeping more classes
For one, fragment classes referenced *only* from settings.xml (in
app:fragment-attributes) where not pulled in. Accessing such settings
would cause a crash.
2022-01-12 13:51:44 +01:00
Torsten Grote
7319398c3b Merge branch 'dex-method-limit' into 'master'
Update ProGuard rules to stay within dex method limit

See merge request briar/briar!1576
2022-01-11 14:45:22 +00:00
akwizgran
841b8133d1 Update ProGuard rules to stay within dex method limit. 2022-01-11 10:50:49 +00:00
akwizgran
b334e8da27 Suppress ProGuard warning about Jackson's Java7SupportImpl. 2022-01-11 10:48:56 +00:00
akwizgran
0ac26883c6 Build APK for CI pipelines. 2022-01-11 10:48:03 +00:00
Torsten Grote
519837e829 Add MailboxIntegrationTest against a real mailbox instance 2022-01-07 15:34:19 -03:00
Torsten Grote
9fa54bf15c Actually throw TolerableFailureException when *deleting* a contact
Before, this was accidentally added to *listing* contacts.
2022-01-07 14:29:14 -03:00
akwizgran
af3389e0e1 Merge branch '2187-delete-contact-from-mailbox' into 'master'
Add method for deleting a contact and retrieving contact list from own mailbox

Closes #2182 and #2187

See merge request briar/briar!1574
2022-01-07 15:13:19 +00:00
Torsten Grote
f5cdad9100 Throw TolerableFailureException when deleting a contact returns 404 2022-01-07 12:03:21 -03:00
Torsten Grote
df4e6aa207 Add method for retrieving contact list from own mailbox 2022-01-07 11:33:57 -03:00
Torsten Grote
82443d9708 Add method for deleting a contact from own mailbox 2022-01-07 10:46:43 -03:00
akwizgran
27058ba0ca Merge branch '2183-mailbox-add-contact' into 'master'
Add method for adding a contact to own mailbox

Closes #2183

See merge request briar/briar!1573
2022-01-07 13:37:40 +00:00
Torsten Grote
f400cf5aa0 Throw ApiException when adding contact is not successful 2022-01-07 10:22:22 -03:00
Torsten Grote
e52c5ddc8e Rename PermanentFailureException to ApiException 2022-01-07 10:13:31 -03:00
Torsten Grote
835e9f6994 Add mailbox API endpoint for adding a contact 2022-01-07 10:13:31 -03:00
akwizgran
4193179eb8 Merge branch '2243-okhttp-mailbox-calls' into 'master'
Add /status mailbox API call and a test for it

Closes #2207 and #2243

See merge request briar/briar!1564
2022-01-07 13:10:49 +00:00
Torsten Grote
421b00517f Address review comments for MailboxApi 2022-01-07 09:51:29 -03:00
Sebastian Kürten
707802c459 Add feature flags for private groups, forums and blogs 2022-01-06 15:35:37 +01:00
Torsten Grote
9f1757ccaf Remove concept of fatal permanent exceptions
All exceptions will just cause the request to be tried again with some backoff.
2022-01-03 14:12:21 -03:00
Torsten Grote
d665fc17ec Add /status and /setup mailbox API call with tests 2022-01-03 14:10:44 -03:00
akwizgran
65be2d2b26 Merge branch 'transactional-attachment-reader' into 'master'
Transactional version of AttachmentReader#getAttachment()

See merge request briar/briar!1570
2021-12-23 17:05:32 +00:00
Sebastian Kürten
d2a39da3e0 Transactional version of AttachmentReader#getAttachment() 2021-12-20 13:55:08 +01:00
akwizgran
d13e4c976e Merge branch 'fix-string-substitutions' into 'master'
Fix multiple substitutions specified in non-positional format

See merge request briar/briar!1568
2021-12-17 11:23:03 +00:00
akwizgran
20b52804bf Merge branch 'add-pending-contact-transactional' into 'master'
Transactional versions of some more API calls

See merge request briar/briar!1561
2021-12-16 16:38:15 +00:00
ialokim
5b27eb354c transactional versions of addPendingContact, getPendingContacts, getConversationId and respondToIntroduction 2021-12-16 17:21:57 +01:00
Torsten Grote
c340071469 Merge branch 'jmock-java-11' into 'master'
Replace ClassImposteriser with ByteBuddyClassImposteriser

See merge request briar/briar!1569
2021-12-15 16:34:04 +00:00
akwizgran
506e274dff Merge branch '2242-migrate-okhttp' into 'master'
Migrate OkHttp to bramble-core

Closes #2242

See merge request briar/briar!1562
2021-12-15 16:04:33 +00:00
Torsten Grote
423356fdda Add missing bouncycastle dependency to witness 2021-12-15 10:48:55 -03:00
Torsten Grote
043a173828 Migrate OkHttp to bramble-core 2021-12-15 10:46:48 -03:00
akwizgran
f0501bbfab Merge branch '1483-peer-session-crash' into 'master'
Do not create PeerSession for groups we created

Closes #1483

See merge request briar/briar!1344
2021-12-15 12:37:46 +00:00
akwizgran
5cafde7b14 Merge branch 'test-deps' into 'master'
Upgrade briar-android unit test dependencies

See merge request briar/briar!1567
2021-12-15 12:34:54 +00:00
akwizgran
5117dbad7e Merge branch 'bouncycastle-java8' into 'master'
Switch to bouncycastle dependency for Java 1.8 to prevent Java 15 class warnings of multi-jar

See merge request briar/briar!1566
2021-12-15 10:39:38 +00:00
Torsten Grote
3a22388495 Fix multiple substitutions specified in non-positional format
The build warnings might persist until translations have been updated
2021-12-14 16:51:52 -03:00
Torsten Grote
1d4de46dfd Upgrade briar-android unit test dependencies 2021-12-14 16:22:11 -03:00
Torsten Grote
d805069dfe Switch to bouncycastle dependency for Java 1.8 to prevent Java 15 class warnings of multi-jar 2021-12-14 15:42:10 -03:00
akwizgran
74cb2a6ce5 Merge branch 'readme-reproducible' into 'master'
Add a section about reproducible builds to the readme

See merge request briar/briar!1565
2021-12-14 13:23:46 +00:00
Torsten Grote
2880a4adac Add a section about reproducible builds to the readme 2021-12-14 10:00:20 -03:00
akwizgran
e032e0ccd5 Bump version numbers for 1.4.3 release. 2021-12-13 16:58:13 +00:00
Torsten Grote
38a07e1d54 Merge branch 'fix-headless-jar-build-on-java-11' into 'master'
Fix headless jar build on Java 11, add to CI pipeline

See merge request briar/briar!1563
2021-12-13 16:22:43 +00:00
akwizgran
07b35db4d4 Fix headless jar build on Java 11, add to CI pipeline. 2021-12-13 15:38:17 +00:00
akwizgran
3b03db9f43 Bump version numbers for 1.4.2 release. 2021-12-13 15:17:34 +00:00
akwizgran
de3a74eedf Update translations. 2021-12-13 15:16:17 +00:00
akwizgran
5a39f9730f Merge branch 'non-default-bridges' into 'master'
Use non-default obfs4 bridges in Russia

See merge request briar/briar!1556
2021-12-13 13:34:19 +00:00
akwizgran
bdf02bbc6c Increase test timeout because meek bridge is super slow. 2021-12-13 12:53:42 +00:00
akwizgran
d5b2ebdb23 Include meek bridge in test and mark it as essential. 2021-12-13 11:56:52 +00:00
Torsten Grote
98bb8d4af1 Merge branch 'fix-database-locking' into 'master'
Use ReentrantLock as a lock (not a monitor) when marking DB as open.

See merge request briar/briar!1549
2021-12-13 11:48:28 +00:00
akwizgran
75cc19e578 Remove failing non-default bridge. 2021-12-13 11:47:03 +00:00
akwizgran
aad87e6e98 Update default bridges from upstream. 2021-12-13 11:46:43 +00:00
akwizgran
dad895c30d Merge branch '2225-error-handling-for-mailbox-downloads' into 'master'
Add methods to get StreamContext from tag, and mark it as recognised

Closes #2225

See merge request briar/briar!1560
2021-12-10 14:45:34 +00:00
Daniel Lublin
f8b3d79813 Add methods to get StreamContext from tag, and mark it as recognised
Separate methods are needed to be able to restart reading from a stream
in the case of errors. Tag should be marked as recognised only after
successfully reading the stream.

Closes #2225
2021-12-10 15:23:30 +01:00
akwizgran
0a98566298 Merge branch '2190-mailbox-reset-retransmission-times' into 'master'
Resolve "Method for resetting retransmission times"

Closes #2190

See merge request briar/briar!1559
2021-12-09 12:11:54 +00:00
Daniel Lublin
93a03d7e15 Reset using a single db query 2021-12-09 12:55:01 +01:00
Daniel Lublin
3eb3dbde09 Add database method to reset retransmission times
Will be used to ensure messages are not stranded on a Mailbox, when such
is added, removed, or otherwise changed.

Closes #2190.
2021-12-09 11:10:50 +01:00
akwizgran
fd56176450 Merge branch 'desktop-98-transactional' into 'master'
Add transactional versions of some API calls for Briar Desktop

See merge request briar/briar!1557
2021-12-08 11:08:47 +00:00
ialokim
d29812f055 add transactional versions of some API calls 2021-12-07 20:39:55 +01:00
akwizgran
403601b9f2 Merge branch '2088-fix-error-with-null-group' into 'master'
Offline hotspot: don't crash if group is null

Closes #2088

See merge request briar/briar!1552
2021-12-07 15:20:35 +00:00
akwizgran
992215b78a Merge branch '2230-tracking-uploads' into 'master'
Add MailboxSettingsManager methods for tracking pending uploads

Closes #2230

See merge request briar/briar!1558
2021-12-07 15:12:24 +00:00
Torsten Grote
658ca8de21 Add MailboxSettingsManager methods for tracking pending uploads 2021-12-07 10:07:54 -03:00
akwizgran
e0e2c0cc89 Add unit test. 2021-12-07 10:33:16 +00:00
akwizgran
114d80ad43 Use non-default obfs4 bridges in Russia. 2021-12-06 18:11:16 +00:00
Torsten Grote
8d5803098b Merge branch 'network-usage-metrics' into 'master'
Include network usage metrics in crash reports and feedback

See merge request briar/briar!1555
2021-11-30 18:08:07 +00:00
akwizgran
a9ed9da822 Include network usage metrics in crash reports and feedback. 2021-11-30 14:12:45 +00:00
akwizgran
1d04bbcb4f Merge branch '1607-share-http-client-instance' into 'master'
Use a single OkHttpClient instance for all RSS fetches

Closes #1607

See merge request briar/briar!1536
2021-11-23 11:51:23 +00:00
akwizgran
43b0d1d543 Merge branch 'gradle-7-3' into 'master'
Upgrade gradle to v7.3

See merge request briar/briar!1553
2021-11-23 11:43:47 +00:00
Sebastian Kürten
1bfd9e4eb0 Upgrade gradle to v7.3 2021-11-23 10:13:11 +01:00
Sebastian Kürten
2295db4361 Offline hotspot: don't crash if group is null 2021-11-22 17:26:01 +01:00
akwizgran
8fca06e040 Merge branch 'gradle-7' into 'master'
Upgrade gradle and android plugin to v7

See merge request briar/briar!1551
2021-11-22 15:36:30 +00:00
akwizgran
3f7c9af3a9 Create the HTTP client lazily and allow it to be garbage collected. 2021-11-22 12:01:51 +00:00
akwizgran
93178d2f28 Merge branch '2171-store-time-of-last-connection-to-own-mailbox' into 'master'
Store time of last attempted and successful connections to own mailbox

Closes #2171

See merge request briar/briar!1547
2021-11-22 11:09:37 +00:00
akwizgran
2755d3f470 Merge branch '2169-store-own-mailbox-properties' into 'master'
Add mailbox settings manager

Closes #2169

See merge request briar/briar!1546
2021-11-22 11:09:03 +00:00
Torsten Grote
7efc3ca78f Fix AuthorView using the wrong constant 2021-11-19 15:11:23 -03:00
Torsten Grote
d6767a62b9 Upgrade gradle and android plugin to v7 2021-11-19 14:55:16 -03:00
Torsten Grote
2821460648 Merge branch 'use-synchroniser-for-mock-tests' into 'master'
Use Synchroniser for all mock tests

See merge request briar/briar!1550
2021-11-18 13:37:16 +00:00
akwizgran
7aa1073bf5 Replace ClassImposteriser with ByteBuddyClassImposteriser.
This may avoid problems with ClassImposteriser when using Java 11. See
https://github.com/jmock-developers/jmock-library/releases/tag/2.10.0
2021-11-18 13:28:08 +00:00
akwizgran
3ff7349b40 Set threading policy for the one test that does things differently. 2021-11-18 13:27:38 +00:00
akwizgran
22593722a7 Convert remaining tests to use BrambleMockTestCase. 2021-11-18 12:56:39 +00:00
akwizgran
e91ad962cb Use Synchroniser for all mock tests.
Android Studio tries to collect the output of failed tests on a different thread, which results in an exception when using Mockery's default threading policy, causing the tests to appear as incomplete rather than failed.
2021-11-18 11:32:33 +00:00
akwizgran
53d9a9b43b Merge branch '2153-tor-0.3.5.17' into 'master'
Upgrade Tor to 0.3.5.17 and remove v2 onion code

Closes #1280 and #2153

See merge request briar/briar!1548
2021-11-17 15:40:44 +00:00
akwizgran
6c702bad0a Use ReentrantLock as a lock (not a monitor) when marking DB as open. 2021-11-17 15:03:58 +00:00
Torsten Grote
0dc2aba22f Remove old v2 onion code from TorPlugin 2021-11-17 10:54:49 -03:00
Torsten Grote
5a8b822e08 Upgrade Tor to 0.3.5.17 2021-11-17 09:46:49 -03:00
akwizgran
8ac6b0155b Store time of last attempted and successful connections to mailbox. 2021-11-17 10:49:00 +00:00
akwizgran
372810f48e Add mailbox settings manager. 2021-11-16 12:53:17 +00:00
akwizgran
1b4ab4f945 Merge branch '2167-decode-qr-code-for-pairing-with-mailbox' into 'master'
Create initial MailboxPairViewModel that decodes QR code

Closes #2167

See merge request briar/briar!1544
2021-11-16 12:15:26 +00:00
akwizgran
85dc27ed77 Merge branch 'message-tracked-event' into 'master'
Implement and use new message tracked event

See merge request briar/briar!1541
2021-11-12 11:35:59 +00:00
ialokim
3f8df34f5c use new event to update contacts view model 2021-11-11 20:56:13 +01:00
ialokim
eb08781460 broadcast event when a conversation message is tracked 2021-11-11 20:56:02 +01:00
akwizgran
e79abeff2e Use a single OkHttpClient instance for all RSS fetches. 2021-09-30 14:09:15 +01:00
Torsten Grote
d3dbcfd62d Recreate plausible private group sharing sessions when re-adding contact from group 2021-04-15 14:46:22 -03:00
Torsten Grote
c4c70f5ac2 Do not create PeerSession for groups we created
This needs a CreatorSession which gets created on-demand.
2021-04-15 14:44:58 -03:00
171 changed files with 4467 additions and 1166 deletions

View File

@@ -32,8 +32,9 @@ test:
extends: .base-test
stage: test
script:
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources check
- ./gradlew -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
- ./gradlew -Djava.security.egd=file:/dev/urandom assembleOfficialDebug :briar-headless:linuxJars
- ./gradlew -Djava.security.egd=file:/dev/urandom compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources check
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
@@ -61,7 +62,7 @@ android test:
when: on_failure
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: on_success
when: manual
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- briar-android/**/*
@@ -84,35 +85,43 @@ test_reproducible:
.optional_tests:
stage: optional_tests
before_script:
- set -e
- export GRADLE_USER_HOME=$PWD/.gradle
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- .gradle/wrapper
- .gradle/caches
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
after_script:
# these file change every time but should not be cached
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
extends: .base-test
bridge test:
extends: .optional_tests
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: on_success
allow_failure: true
allow_failure: false
- if: '$CI_COMMIT_TAG == null'
when: manual
allow_failure: true
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
mailbox integration test:
extends: .optional_tests
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: on_success
allow_failure: false
- if: '$CI_COMMIT_TAG == null'
when: manual
allow_failure: false
script:
# start mailbox
- cd /opt && git clone --depth 1 https://code.briarproject.org/briar/briar-mailbox.git briar-mailbox
- cd briar-mailbox
- mkdir -p /root/.local/share # create directory that mailbox (currently) expects to exist
- ./gradlew run --args="--debug --setup-token 54686973206973206120736574757020746f6b656e20666f722042726961722e" &
# run mailbox integration test once mailbox has started
- cd "$CI_PROJECT_DIR"
- bramble-core/src/test/bash/wait-for-mailbox.sh
- OPTIONAL_TESTS=org.briarproject.bramble.mailbox.MailboxIntegrationTest ./gradlew --info bramble-core:test --tests MailboxIntegrationTest
pre_release_tests:
extends: .optional_tests
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
only:
- tags

View File

@@ -22,6 +22,15 @@ our site.
[Wiki](https://code.briarproject.org/briar/briar/-/wikis/home)
## Reproducible builds
We provide [docker images](https://code.briarproject.org/briar/briar-reproducer#briar-reproducer)
to ease the task of verifying that the published APK binaries
include nothing but our publicly available source code.
You can either use those images or use them as a blueprint to build your own environment
for reproduction.
## Donate
[![Donate using Liberapay](https://briarproject.org/img/liberapay.svg)](https://liberapay.com/Briar/donate) [![Flattr this](https://briarproject.org/img/flattr-badge-large.png "Flattr this")](https://flattr.com/t/592836/)

View File

@@ -15,8 +15,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
versionCode 10401
versionName "1.4.1"
versionCode 10404
versionName "1.4.4"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -42,8 +42,8 @@ configurations {
dependencies {
implementation project(path: ':bramble-core', configuration: 'default')
tor 'org.briarproject:tor-android:0.3.5.15'
tor 'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a@zip'
tor "org.briarproject:tor-android:$tor_version"
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version@zip"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
@@ -53,7 +53,7 @@ dependencies {
testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-legacy:$jmock_version"
testImplementation "org.jmock:jmock-imposters:$jmock_version"
}
def torBinariesDir = 'src/main/res/raw'

View File

@@ -1,6 +1,8 @@
-keep,includedescriptorclasses class org.briarproject.** { *; }
-keep class org.h2.** { *; }
# Keep the H2 classes that are loaded via reflection
-keep class org.h2.Driver { *; }
-keep class org.h2.engine.Engine { *; }
-keep class org.h2.store.fs.** { *; }
# Don't warn about unused dependencies of H2 classes
-dontwarn org.h2.**
-dontnote org.h2.**
@@ -15,5 +17,4 @@
-dontwarn sun.misc.Unsafe
-dontnote com.google.common.**
# UPnP library isn't used
-dontwarn org.bitlet.weupnp.**
-dontwarn com.fasterxml.jackson.databind.ext.Java7SupportImpl

View File

@@ -9,7 +9,7 @@ import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -44,7 +44,7 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
private AndroidAccountManager accountManager;
public AndroidAccountManagerTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
app = context.mock(Application.class);
applicationInfo = new ApplicationInfo();
applicationInfo.dataDir = testDir.getAbsolutePath();

View File

@@ -1,50 +1,50 @@
dependencyVerification {
verify = [
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
'com.android.tools.analytics-library:protos:27.1.3:protos-27.1.3.jar:0d9e6cff60b318baac250b6f5bb076a8161103338bf2749cdf1db8a5a13a1f12',
'com.android.tools.analytics-library:shared:27.1.3:shared-27.1.3.jar:10d2a51d8f89ff4ac849888e5a9c60b10e879c30d78545ec1da4d3df7bd56ae4',
'com.android.tools.analytics-library:tracker:27.1.3:tracker-27.1.3.jar:589b355a2ba796cbc0a2b2295737de6661f078262e5f87cd6f540b8d011e5ebb',
'com.android.tools.analytics-library:protos:30.0.3:protos-30.0.3.jar:f62b89dcd9de719c6a7b7e15fb1dd20e45b57222e675cf633607bd0ed6bca7e7',
'com.android.tools.analytics-library:shared:30.0.3:shared-30.0.3.jar:05aa9ba3cc890354108521fdf99802565aae5dd6ca44a6ac8bb8d594d1c1cd15',
'com.android.tools.analytics-library:tracker:30.0.3:tracker-30.0.3.jar:5d0ef35bf6733e96210b5085a2a202152921bf834d345959dce1ca3369b528df',
'com.android.tools.build:aapt2-proto:4.1.0-alpha01-6193524:aapt2-proto-4.1.0-alpha01-6193524.jar:17e75523e1e92dd4f222c7368ee41df9e964a508232f591e265d0c499baf9dca',
'com.android.tools.build:apksig:4.1.3:apksig-4.1.3.jar:a851980c678ff7a6785388b9a9e8cc094788ce3c4a985ad2b19c2028fd3c631a',
'com.android.tools.build:apkzlib:4.1.3:apkzlib-4.1.3.jar:475903065e7e83a8c1ba78d267c97a54dc5a04d768b535093850423d7b11f2c8',
'com.android.tools.build:builder-model:4.1.3:builder-model-4.1.3.jar:2624a1436c3ab39dd91d3ecf9409a594b0f89ea5cab255f2e9ff11f5ee03d274',
'com.android.tools.build:builder-test-api:4.1.3:builder-test-api-4.1.3.jar:3d2af66726b06b53b8d6d497efcee39ff9f77eb2f8d2cce38b31502383a40d2c',
'com.android.tools.build:builder:4.1.3:builder-4.1.3.jar:a40426cd6d68f6a722ef4950058c075e4547025e8c2fd78e732ad89f15176f84',
'com.android.tools.build:gradle-api:4.1.3:gradle-api-4.1.3.jar:11b1fb9de658bdcf9290b1c1517060d0c4d93f2b27975934989ca4ac890bc077',
'com.android.tools.build:manifest-merger:27.1.3:manifest-merger-27.1.3.jar:ce8d4009b1f1584777a7ffa1da3b0551dc316bc8e08112e442c352af70f46f2d',
'com.android.tools.ddms:ddmlib:27.1.3:ddmlib-27.1.3.jar:8f76e8236d2b9eebf26378746dad025c4c7c056a02e133dae4ddef47b283c710',
'com.android.tools.external.com-intellij:intellij-core:27.1.3:intellij-core-27.1.3.jar:652814fa099b4746fb6f10e19718e476952e8b5bac24e17d914f90650ad21808',
'com.android.tools.external.com-intellij:kotlin-compiler:27.1.3:kotlin-compiler-27.1.3.jar:8d7a78d5efd213c5e467e42bd205582aad73ffc77ee5dc18eb1361c9af72f125',
'com.android.tools.external.org-jetbrains:uast:27.1.3:uast-27.1.3.jar:aea53944a1ac6a05f12297b55290e8cbecfe54c4166260cfba4405823bfe1c78',
'com.android.tools.layoutlib:layoutlib-api:27.1.3:layoutlib-api-27.1.3.jar:23875ce0a8429f33a4e86cc358f658faa0ba9c576f5f05760e544b453d67d04b',
'com.android.tools.lint:lint-api:27.1.3:lint-api-27.1.3.jar:97666be32bcadacd944416ea334a9575ef8f4ad0c8f333151491ff4a7df43e1c',
'com.android.tools.lint:lint-checks:27.1.3:lint-checks-27.1.3.jar:b2d71ae84a31490fe9ff26c706163fe245b2aea98e3eb747214c1085df444708',
'com.android.tools.lint:lint-gradle-api:27.1.3:lint-gradle-api-27.1.3.jar:e54131c287a2954e6ed78a3351e5e10e35a1da2f09ac443bf44b705c71b63a4d',
'com.android.tools.lint:lint-gradle:27.1.3:lint-gradle-27.1.3.jar:6a79e48943649d63665db7b17dbaff7af93e94ab9b15072f1a4d90486294ee9f',
'com.android.tools.lint:lint-model:27.1.3:lint-model-27.1.3.jar:acb9e792db7000e38e3c3ca21a9b14f2de6549d7a3fc92a97ffba3d06345e5bf',
'com.android.tools.lint:lint:27.1.3:lint-27.1.3.jar:5a2e69d0901a3a476a5b2d5001de755868113145f5f6aa557750cfad5389a44b',
'com.android.tools:annotations:27.1.3:annotations-27.1.3.jar:904dd771883496d5dfc86619ab2555968ea4e8a29d7a5f4f7cae6fbf5429f8f5',
'com.android.tools:common:27.1.3:common-27.1.3.jar:17ab4728e3ea50f047dd5937f0faf35f2c5416962ed74891057087ddc328bf96',
'com.android.tools:dvlib:27.1.3:dvlib-27.1.3.jar:cead1c0c356cbe43e6855b0330fe09ef4bec2c72e22bdb4c6e7cf7e6b1dfbc37',
'com.android.tools:repository:27.1.3:repository-27.1.3.jar:99de1a178855b56b8cd91a56296f1e0a9399c445e6acc51f1d2927947cc472cb',
'com.android.tools:sdk-common:27.1.3:sdk-common-27.1.3.jar:b591e2aa0f1be600795f5c9e2bf81cba9b052bee452fc86c3362b5dd9e427a14',
'com.android.tools:sdklib:27.1.3:sdklib-27.1.3.jar:ad6c08a45fe2904d05656bdddf9f623fa5c1d16bbd7b8d6a270a0734136ae02e',
'com.android:signflinger:4.1.3:signflinger-4.1.3.jar:f3103b55ccdc8dd9ee2517eb26af93b904d41303726594372d0df59d51156e5c',
'com.android:zipflinger:4.1.3:zipflinger-4.1.3.jar:48569896c0497268308a8014c66eb0f2bace2b9e2fc9390f3012823fb86387d5',
'com.android.tools.build:apksig:7.0.3:apksig-7.0.3.jar:012337a2803c9a30dfc41dcbc6450686ee9e5f582549f7f126479f743a343ec9',
'com.android.tools.build:apkzlib:7.0.3:apkzlib-7.0.3.jar:b31e53174c92db83c5cc6e7dac6734ea4e907a72e452c2bf1818dfd082c59397',
'com.android.tools.build:builder-model:7.0.3:builder-model-7.0.3.jar:483f99d7494a5bed027e1e8d29111384cf535d4842f0be5a79805bd44bb68d4e',
'com.android.tools.build:builder-test-api:7.0.3:builder-test-api-7.0.3.jar:f6de4bc2cef545e8367bf82d7c733829c7be3b0b3b8b09fd8c58f2150e59ab46',
'com.android.tools.build:builder:7.0.3:builder-7.0.3.jar:c6952da0094b094c2ba0fe84c675622097c5d9b9f9beb53485b860320540cf1d',
'com.android.tools.build:manifest-merger:30.0.3:manifest-merger-30.0.3.jar:72b346ba6318b4b6260e6e49df4bea5da2e12329ab6c2beb2269c49a9f51f178',
'com.android.tools.ddms:ddmlib:30.0.3:ddmlib-30.0.3.jar:7a914a68ab93393657297234e2f37b22410ae9a433cba692ce8c727c9607e3bb',
'com.android.tools.external.com-intellij:intellij-core:30.0.3:intellij-core-30.0.3.jar:1ebe858d3f58eeaa8c06507f8ac0f1c7051e6c61f35a70f3c3967d5734d3abc5',
'com.android.tools.external.com-intellij:kotlin-compiler:30.0.3:kotlin-compiler-30.0.3.jar:ed00e441f427cb4e0d418287b9da30b12b7f735f9af32e6b5d3dc960b6a742fc',
'com.android.tools.external.org-jetbrains:uast:30.0.3:uast-30.0.3.jar:a77801bee6ff509910e459525c9c34d7f04b066ade123547f16f1917548eadea',
'com.android.tools.layoutlib:layoutlib-api:30.0.3:layoutlib-api-30.0.3.jar:4caa87e9ca2e11315f650d576cd59fec1793373bc3fca3f6d53c029e7534e7c4',
'com.android.tools.lint:lint-api:30.0.3:lint-api-30.0.3.jar:bcecbd2f752a6560096a9029a47d1de6bd788a51bab505c5ebfba6a18524b983',
'com.android.tools.lint:lint-checks:30.0.3:lint-checks-30.0.3.jar:25a7cd42dc3ad502337f131fb8b7e873c53301db0a67b1c64dd4ae7a8eb66cec',
'com.android.tools.lint:lint-gradle:30.0.3:lint-gradle-30.0.3.jar:94544d6147a809bf2fd3440e51f28a4e42e547d74aab53eefd74938cdad42c26',
'com.android.tools.lint:lint-model:30.0.3:lint-model-30.0.3.jar:0b940a7f575c2ff5cbd038260f41dde686a93c672213881ead3ce8af3513b396',
'com.android.tools.lint:lint:30.0.3:lint-30.0.3.jar:ee4f11001e0c7e3b776e0d67399ad354b19b0f168822ec2b7db47c0910ed227d',
'com.android.tools:annotations:30.0.3:annotations-30.0.3.jar:5c1944982fda8555855c4f5422fabf0dc8e2306e1f5460e9ad82dae71316bc31',
'com.android.tools:common:30.0.3:common-30.0.3.jar:8751efaaf2c2ddd1f0a37526c794347def6a3057ca9fc510307c13a6cf0d036f',
'com.android.tools:dvlib:30.0.3:dvlib-30.0.3.jar:5affafcec390041e5afd64cb924153f5e474db47ee8ccc2f555b495083141233',
'com.android.tools:repository:30.0.3:repository-30.0.3.jar:0a40c6f16c506903ce2c609affd8228aceda73a69d93dfa42d4f02b8491449f6',
'com.android.tools:sdk-common:30.0.3:sdk-common-30.0.3.jar:b45570a380360236ffee0f6bb593d66b673bad3834dfe0d6c9871fa7188ee0eb',
'com.android.tools:sdklib:30.0.3:sdklib-30.0.3.jar:7088f20a414fab170a21e457825e14ebe099f753558e02c8acc12c67eb412162',
'com.android:signflinger:7.0.3:signflinger-7.0.3.jar:903a4536db3e96b4e1e1dc1e400eb0b91bf7866d9b39cd7ec94d75dde158f152',
'com.android:zipflinger:7.0.3:zipflinger-7.0.3.jar:fd209c960a3eff7a339e6fcba07d5e9ef4604d1633c69ab2df987460d9804140',
'com.beust:jcommander:1.78:jcommander-1.78.jar:7891debb84b5f83e9bd57593ebece3399abbe0fd938cf306b3534c57913b9615',
'com.github.javaparser:javaparser-core:3.17.0:javaparser-core-3.17.0.jar:23f5c982e1c7771423d37d52c774e8d2e80fd7ea7305ebe448797a96f67e6fca',
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.code.gson:gson:2.8.5:gson-2.8.5.jar:233a0149fc365c9f6edbd683cfe266b19bdc773be98eabdaf6b3c924b48e7d81',
'com.google.code.gson:gson:2.8.6:gson-2.8.6.jar:c8fb4839054d280b3033f800d1f5a97de2f028eb8ba2eb458ad287e536f3f25f',
'com.google.dagger:dagger-compiler:2.33:dagger-compiler-2.33.jar:aa8a0d8370c578fd6999802d0d90b9829377a46d2c1141e11b8f737970e7155e',
'com.google.dagger:dagger-producers:2.33:dagger-producers-2.33.jar:5897f0b6eef799c2adfe3ccacc58c0fb374d58acb063c3ebe5366c38a8bce5c8',
'com.google.dagger:dagger-spi:2.33:dagger-spi-2.33.jar:e2dcab2221b8afb9556ef0a1c83b0bd5f42552e254322a257330f754cdbbb9d4',
'com.google.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb',
'com.google.errorprone:error_prone_annotations:2.2.0:error_prone_annotations-2.2.0.jar:6ebd22ca1b9d8ec06d41de8d64e0596981d9607b42035f9ed374f9de271a481a',
'com.google.errorprone:error_prone_annotations:2.3.2:error_prone_annotations-2.3.2.jar:357cd6cfb067c969226c442451502aee13800a24e950fdfde77bcdb4565a668d',
'com.google.errorprone:error_prone_annotations:2.3.4:error_prone_annotations-2.3.4.jar:baf7d6ea97ce606c53e11b6854ba5f2ce7ef5c24dddf0afa18d1260bd25b002c',
'com.google.errorprone:javac-shaded:9-dev-r4023-3:javac-shaded-9-dev-r4023-3.jar:65bfccf60986c47fbc17c9ebab0be626afc41741e0a6ec7109e0768817a36f30',
'com.google.googlejavaformat:google-java-format:1.5:google-java-format-1.5.jar:aa19ad7850fb85178aa22f2fddb163b84d6ce4d0035872f30d4408195ca1144e',
'com.google.guava:failureaccess:1.0.1:failureaccess-1.0.1.jar:a171ee4c734dd2da837e4b16be9df4661afab72a41adaf31eb84dfdaf936ca26',
'com.google.guava:guava:27.1-jre:guava-27.1-jre.jar:4a5aa70cc968a4d137e599ad37553e5cfeed2265e8c193476d7119036c536fe7',
'com.google.guava:guava:28.1-jre:guava-28.1-jre.jar:30beb8b8527bd07c6e747e77f1a92122c2f29d57ce347461a4a55eb26e382da4',
'com.google.guava:guava:30.1-jre:guava-30.1-jre.jar:e6dd072f9d3fe02a4600688380bd422bdac184caf6fe2418cfdd0934f09432aa',
'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99',
'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6',
'com.google.j2objc:j2objc-annotations:1.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b',
@@ -54,63 +54,108 @@ dependencyVerification {
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
'com.squareup:javawriter:2.5.0:javawriter-2.5.0.jar:fcfb09fb0ea0aa97d3cfe7ea792398081348e468f126b3603cb3803f240197f0',
'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce',
'com.sun.istack:istack-commons-runtime:3.0.7:istack-commons-runtime-3.0.7.jar:6443e10ba2e259fb821d9b6becf10db5316285fc30c53cec9d7b19a3877e7fdf',
'com.sun.xml.fastinfoset:FastInfoset:1.2.15:FastInfoset-1.2.15.jar:785861db11ca1bd0d1956682b974ad73eb19cd3e01a4b3fa82d62eca97210aec',
'com.sun.istack:istack-commons-runtime:3.0.8:istack-commons-runtime-3.0.8.jar:4ffabb06be454a05e4398e20c77fa2b6308d4b88dfbef7ca30a76b5b7d5505ef',
'com.sun.xml.fastinfoset:FastInfoset:1.2.16:FastInfoset-1.2.16.jar:056f3a1e144409f21ed16afc26805f58e9a21f3fce1543c42d400719d250c511',
'com.thoughtworks.qdox:qdox:1.12.1:qdox-1.12.1.jar:21fba22f830e9268f07cf4ab2d99e8181abbdcb0cb91ee0228eb3cb918dcdd1d',
'commons-codec:commons-codec:1.10:commons-codec-1.10.jar:4241dfa94e711d435f29a4604a3e2de5c4aa3c165e23bd066be6fc1fc4309569',
'commons-io:commons-io:2.4:commons-io-2.4.jar:cc6a41dc3eaacc9e440a6bd0d2890b20d36b4ee408fe2d67122f328bb6e01581',
'commons-logging:commons-logging:1.2:commons-logging-1.2.jar:daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636',
'it.unimi.dsi:fastutil:7.2.0:fastutil-7.2.0.jar:74fa208043740642f7e6eb09faba15965218ad2f50ce3020efb100136e4b591c',
'javax.activation:javax.activation-api:1.2.0:javax.activation-api-1.2.0.jar:43fdef0b5b6ceb31b0424b208b930c74ab58fac2ceeb7b3f6fd3aeb8b5ca4393',
'info.picocli:picocli:4.5.2:picocli-4.5.2.jar:b4395e9a67932616efd2245d984bf5fcd453c2c5049558c3ce959ac2af4d3fac',
'it.unimi.dsi:fastutil:8.4.0:fastutil-8.4.0.jar:2ad2824a4a0a0eb836b52ee2fc84ba2134f44bce7bfa54015ae3f31c710a3071',
'jakarta.activation:jakarta.activation-api:1.2.1:jakarta.activation-api-1.2.1.jar:8b0a0f52fa8b05c5431921a063ed866efaa41dadf2e3a7ee3e1961f2b0d9645b',
'jakarta.xml.bind:jakarta.xml.bind-api:2.3.2:jakarta.xml.bind-api-2.3.2.jar:69156304079bdeed9fc0ae3b39389f19b3cc4ba4443bc80508995394ead742ea',
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'javax.xml.bind:jaxb-api:2.3.1:jaxb-api-2.3.1.jar:88b955a0df57880a26a74708bc34f74dcaf8ebf4e78843a28b50eae945732b06',
'jline:jline:2.14.6:jline-2.14.6.jar:97d1acaac82409be42e622d7a54d3ae9d08517e8aefdea3d2ba9791150c2f02d',
'junit:junit:4.13.1:junit-4.13.1.jar:c30719db974d6452793fe191b3638a5777005485bae145924044530ffa5f6122',
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
'net.java.dev.jna:jna-platform:5.6.0:jna-platform-5.6.0.jar:9ecea8bf2b1b39963939d18b70464eef60c508fed8820f9dcaba0c35518eabf7',
'net.java.dev.jna:jna:5.6.0:jna-5.6.0.jar:5557e235a8aa2f9766d5dc609d67948f2a8832c2d796cea9ef1d6cbe0b3b7eaf',
'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.sf.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5',
'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.apache.commons:commons-compress:1.12:commons-compress-1.12.jar:2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6',
'org.apache.ant:ant-antlr:1.10.9:ant-antlr-1.10.9.jar:7623dc9d0f20ea713290c6bf1a23f4c059447aef7ff9f5b2be75960f3f028d2e',
'org.apache.ant:ant-junit:1.10.9:ant-junit-1.10.9.jar:960bdc8827954d62206ba42d0a68a7ee4476175ba47bb113e17e77cce7394630',
'org.apache.ant:ant-launcher:1.10.9:ant-launcher-1.10.9.jar:fcce891f57f3be72149ff96ac2a80574165b3e0839866b95d24528f3027d50c1',
'org.apache.ant:ant:1.10.9:ant-1.10.9.jar:0715478af585ea80a18985613ebecdc7922122d45b2c3c970ff9b352cddb75fc',
'org.apache.commons:commons-compress:1.20:commons-compress-1.20.jar:0aeb625c948c697ea7b205156e112363b59ed5e2551212cd4e460bdb72c7c06e',
'org.apache.httpcomponents:httpclient:4.5.6:httpclient-4.5.6.jar:c03f813195e7a80e3608d0ddd8da80b21696a4c92a6a2298865bf149071551c7',
'org.apache.httpcomponents:httpcore:4.4.10:httpcore-4.4.10.jar:78ba1096561957db1b55200a159b648876430342d15d461277e62360da19f6fd',
'org.apache.httpcomponents:httpmime:4.5.6:httpmime-4.5.6.jar:0b2b1102c18d3c7e05a77214b9b7501a6f6056174ae5604e0e256776eda7553e',
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a:obfs4proxy-android-0.0.12-dev-40245c4a.zip:8ab05a8f8391be2cb5ab2b665c281a06d9e3a756bd0f95a40a36ca927866ea82',
'org.briarproject:tor-android:0.3.5.15:tor-android-0.3.5.15.jar:560c5070166300b396cb2f28d82d9f639ee1fb5479096a3cef67da56d39937ad',
'org.briarproject:tor-android:0.3.5.17:tor-android-0.3.5.17.jar:1888afc10a26b93d00a010ea27bf0b1b162a6d524688b08b98d70d14dc363b54',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.checkerframework:checker-qual:2.8.1:checker-qual-2.8.1.jar:9103499008bcecd4e948da29b17864abb64304e15706444ae209d17ebe0575df',
'org.codehaus.groovy:groovy-all:2.4.15:groovy-all-2.4.15.jar:51d6c4e71782e85674239189499854359d380fb75e1a703756e3aaa5b98a5af0',
'org.checkerframework:checker-qual:3.5.0:checker-qual-3.5.0.jar:729990b3f18a95606fc2573836b6958bcdb44cb52bfbd1b7aa9c339cff35a5a4',
'org.codehaus.groovy:groovy-ant:3.0.7:groovy-ant-3.0.7.jar:6ed2ba82813d128f7050c24142e87b3dc2ad8b504786280eb03e81f0cf6a5793',
'org.codehaus.groovy:groovy-astbuilder:3.0.7:groovy-astbuilder-3.0.7.jar:b290451eb1583666e906c41f7d14747b4cc96363c99c478b244634fd5dfc9013',
'org.codehaus.groovy:groovy-cli-picocli:3.0.7:groovy-cli-picocli-3.0.7.jar:71b4bd11fb30a9c7b5618e22122c9c5141958fb27f4dcf0068b6f715088f6916',
'org.codehaus.groovy:groovy-console:3.0.7:groovy-console-3.0.7.jar:0541b358b6b8e5363215026736168fccfec1d91bac678d066fa77349eeeaa5dd',
'org.codehaus.groovy:groovy-datetime:3.0.7:groovy-datetime-3.0.7.jar:b9823d14b1a4f94236ae2f8a471701aab17e093e1b33402b91550b5c8dd88f04',
'org.codehaus.groovy:groovy-docgenerator:3.0.7:groovy-docgenerator-3.0.7.jar:bf53f7a11c9eb1e278e1b8ed2714c741bcf781235c803ad3ba1555f2614573f3',
'org.codehaus.groovy:groovy-groovydoc:3.0.7:groovy-groovydoc-3.0.7.jar:86b24dfc23c005066ab83927cdb54177f06c9531773f2e2d2ecc9a131f7c2677',
'org.codehaus.groovy:groovy-groovysh:3.0.7:groovy-groovysh-3.0.7.jar:5c40e78cbc09726aedd1c75fab112d245d665d6294870f9119e6cd3013ed14ab',
'org.codehaus.groovy:groovy-jmx:3.0.7:groovy-jmx-3.0.7.jar:0a89f3007884eb156751937d93382038b83d39c7c2f0ab156ebf251a7251f2ab',
'org.codehaus.groovy:groovy-json:3.0.7:groovy-json-3.0.7.jar:df1f0ee475e3fc93a6a0d17548294e160cca5de6d9d36817a7be1fbe650de03b',
'org.codehaus.groovy:groovy-jsr223:3.0.7:groovy-jsr223-3.0.7.jar:1dbd969595332416193baa660fbb45743d19696eaa25fe98e591a2739e13517e',
'org.codehaus.groovy:groovy-macro:3.0.7:groovy-macro-3.0.7.jar:c6cc06df526b39e2c359e2435f0071594c5a1c7babafaa6c184fdd8fa931531f',
'org.codehaus.groovy:groovy-nio:3.0.7:groovy-nio-3.0.7.jar:db54c577882b294cd8c975ec5451596441baf54781319c61627dca0e0c2361ef',
'org.codehaus.groovy:groovy-servlet:3.0.7:groovy-servlet-3.0.7.jar:5b6a909bf501c209adfb6205b9e740649609074455fd979bf9da4853e6ff9a39',
'org.codehaus.groovy:groovy-sql:3.0.7:groovy-sql-3.0.7.jar:252bb6c74e1a9f41756ad4fbd3b0d2eddc93bb61109961dd1952a37bf2d57a64',
'org.codehaus.groovy:groovy-swing:3.0.7:groovy-swing-3.0.7.jar:bd942032d9328d54c6679c49a41f6caa0d4a0039ebe598493b8a647730d98cff',
'org.codehaus.groovy:groovy-templates:3.0.7:groovy-templates-3.0.7.jar:f119e07f650ef186ae5a4b944f9e30915b14311bad47c94a6b32de8d4f69bc80',
'org.codehaus.groovy:groovy-test-junit5:3.0.7:groovy-test-junit5-3.0.7.jar:c16eeea07b8e396891e266d7ba9388b24ac804237ffdd9a792b0d08969bad014',
'org.codehaus.groovy:groovy-test:3.0.7:groovy-test-3.0.7.jar:f71afd7c25d43017f89ea47e6de6daec971d159047dae083c1513a8422d44b90',
'org.codehaus.groovy:groovy-testng:3.0.7:groovy-testng-3.0.7.jar:713d5f2231bbb5712aefd362151b9ffd884aeb7ef2e773315cc54259cbdd063d',
'org.codehaus.groovy:groovy-xml:3.0.7:groovy-xml-3.0.7.jar:8a62e7c9ddece3e82676c4bef2f2c100f459602cd1fb6a14e94187bf863e97ff',
'org.codehaus.groovy:groovy:3.0.7:groovy-3.0.7.jar:51d1777e8dd1f00e60ea56e00d8a354ff5aab1f00fc8464ae8d39d71867e401f',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
'org.codehaus.mojo:animal-sniffer-annotations:1.18:animal-sniffer-annotations-1.18.jar:47f05852b48ee9baefef80fa3d8cea60efa4753c0013121dd7fe5eef2e5c729d',
'org.glassfish.jaxb:jaxb-runtime:2.3.1:jaxb-runtime-2.3.1.jar:45fecfa5c8217ce1f3652ab95179790ec8cc0dec0384bca51cbeb94a293d9f2f',
'org.glassfish.jaxb:txw2:2.3.1:txw2-2.3.1.jar:34975dde1c6920f1a39791142235689bc3cd357e24d05edd8ff93b885bd68d60',
'org.glassfish.jaxb:jaxb-runtime:2.3.2:jaxb-runtime-2.3.2.jar:e6e0a1e89fb6ff786279e6a0082d5cef52dc2ebe67053d041800737652b4fd1b',
'org.glassfish.jaxb:txw2:2.3.2:txw2-2.3.2.jar:4a6a9f483388d461b81aa9a28c685b8b74c0597993bf1884b04eddbca95f48fe',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
'org.jetbrains.kotlin:kotlin-reflect:1.3.72:kotlin-reflect-1.3.72.jar:a188d9367de1c4ee9479db630985c0597b20709c83161b1430d24edb27e38c40',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72:kotlin-stdlib-common-1.3.72.jar:5e7d1552863e480c1628b1cc39ce230ef829f5b7230106215a05acda5172203a',
'org.jacoco:org.jacoco.agent:0.8.3:org.jacoco.agent-0.8.3.jar:522deb254ee16a04cc8341cc8f335f5cb7232982994d961b9cf3a0454709209f',
'org.jacoco:org.jacoco.ant:0.8.3:org.jacoco.ant-0.8.3.jar:735844e1ae15f9b875b42a27ac5cb61cc26e106d9e839e5d1c6756709b424ce0',
'org.jacoco:org.jacoco.core:0.8.3:org.jacoco.core-0.8.3.jar:0818437bc060a0c7cc798148f22b713702aae2771aba104444407697d578f1ea',
'org.jacoco:org.jacoco.report:0.8.3:org.jacoco.report-0.8.3.jar:aae08fa4ff043c807b8876cdb2d8705eb8449a55efce461baa6c09da245088c1',
'org.jetbrains.intellij.deps:trove4j:1.0.20181211:trove4j-1.0.20181211.jar:affb7c85a3c87bdcf69ff1dbb84de11f63dc931293934bc08cd7ab18de083601',
'org.jetbrains.kotlin:kotlin-reflect:1.4.32:kotlin-reflect-1.4.32.jar:dbf19e9cdaa9c3c170f3f6f6ce3922f38dfc1d7fa1cab5b7c23a19da8b5eec5b',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20:kotlin-stdlib-common-1.4.20.jar:a7112c9b3cefee418286c9c9372f7af992bd1e6e030691d52f60cb36dbec8320',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72:kotlin-stdlib-jdk7-1.3.72.jar:40566c0c08d414b9413ba556ff7f8a0b04b98b9f0f424d122dd2088510efccc4',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72:kotlin-stdlib-jdk8-1.3.72.jar:133da70cfc07b56094282eac5c59bccd59f167ee2ead22e5282876d8bc10bf95',
'org.jetbrains.kotlin:kotlin-stdlib:1.3.72:kotlin-stdlib-1.3.72.jar:3856a7349ebacd6d1be6802b2fed9c4dc2c5a564ea92b6b945ac988243d4b16b',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32:kotlin-stdlib-common-1.4.32.jar:e1ff6f55ee9e7591dcc633f7757bac25a7edb1cc7f738b37ec652f10f66a4145',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.32:kotlin-stdlib-jdk7-1.4.32.jar:5f801e75ca27d8791c14b07943c608da27620d910a8093022af57f543d5d98b6',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.32:kotlin-stdlib-jdk8-1.4.32.jar:adc43e54757b106e0cd7b3b7aa257dff471b61efdabe067fc02b2f57e2396262',
'org.jetbrains.kotlin:kotlin-stdlib:1.4.20:kotlin-stdlib-1.4.20.jar:b8ab1da5cdc89cb084d41e1f28f20a42bd431538642a5741c52bbfae3fa3e656',
'org.jetbrains.kotlin:kotlin-stdlib:1.4.32:kotlin-stdlib-1.4.32.jar:13e9fd3e69dc7230ce0fc873a92a4e5d521d179bcf1bef75a6705baac3bfecba',
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0:kotlinx-metadata-jvm-0.1.0.jar:9753bb39efef35957c5c15df9a3cb769aabf2cdfa74b47afcb7760e5146be3b5',
'org.jetbrains.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
'org.jmock:jmock-junit4:2.12.0:jmock-junit4-2.12.0.jar:3233062fc889637c151a24f1ee086bad04321ab7d8264fef279daff0fa27205b',
'org.jmock:jmock-legacy:2.12.0:jmock-legacy-2.12.0.jar:dea3a9cca653d082e2fe7e40232e982fe03a9984c7d67ceff24f3e03fe580dcd',
'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
'org.jvnet.staxex:stax-ex:1.8:stax-ex-1.8.jar:95b05d9590af4154c6513b9c5dc1fb2e55b539972ba0a9ef28e9a0c01d83ad77',
'org.junit.jupiter:junit-jupiter-api:5.7.0:junit-jupiter-api-5.7.0.jar:b03f78e0daeed2d77a0af9bcd662b4cdb9693f7ee72e01a539b508b84c63d182',
'org.junit.jupiter:junit-jupiter-engine:5.7.0:junit-jupiter-engine-5.7.0.jar:dfa26af94644ac2612dde6625852fcb550a0d21caa243257de54cba738ba87af',
'org.junit.platform:junit-platform-commons:1.7.0:junit-platform-commons-1.7.0.jar:5330ee87cc7586e6e25175a34e9251624ff12ff525269d3415d0b4ca519b6fea',
'org.junit.platform:junit-platform-engine:1.7.0:junit-platform-engine-1.7.0.jar:75f21a20dc594afdc875736725b408cec6d0344874d29f34b2dd3075500236f2',
'org.junit.platform:junit-platform-launcher:1.7.0:junit-platform-launcher-1.7.0.jar:fbdc748fde4c4279fe1d3c607447cb3b7ccd45d7338fc574f8a894ddf2d16818',
'org.jvnet.staxex:stax-ex:1.8.1:stax-ex-1.8.1.jar:20522549056e9e50aa35ef0b445a2e47a53d06be0b0a9467d704e2483ffb049a',
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
'org.opentest4j:opentest4j:1.2.0:opentest4j-1.2.0.jar:58812de60898d976fb81ef3b62da05c6604c18fd4a249f5044282479fc286af2',
'org.ow2.asm:asm-analysis:7.0:asm-analysis-7.0.jar:e981f8f650c4d900bb033650b18e122fa6b161eadd5f88978d08751f72ee8474',
'org.ow2.asm:asm-commons:7.0:asm-commons-7.0.jar:fed348ef05958e3e846a3ac074a12af5f7936ef3d21ce44a62c4fa08a771927d',
'org.ow2.asm:asm-tree:7.0:asm-tree-7.0.jar:cfd7a0874f9de36a999c127feeadfbfe6e04d4a71ee954d7af3d853f0be48a6c',
'org.ow2.asm:asm-util:7.0:asm-util-7.0.jar:75fbbca440ef463f41c2b0ab1a80abe67e910ac486da60a7863cbcb5bae7e145',
'org.ow2.asm:asm:7.0:asm-7.0.jar:b88ef66468b3c978ad0c97fd6e90979e56155b4ac69089ba7a44e9aa7ffe9acf',
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
'org.testng:testng:7.3.0:testng-7.3.0.jar:63727488f9717d57f0d0a0fee5a1fc10a2be9cfcff2ec3a7187656d663c0774e',
'xerces:xercesImpl:2.12.0:xercesImpl-2.12.0.jar:b50d3a4ca502faa4d1c838acb8aa9480446953421f7327e338c5dda3da5e76d0',
'xml-apis:xml-apis:1.4.01:xml-apis-1.4.01.jar:a840968176645684bb01aed376e067ab39614885f9eee44abe35a5f20ebe7fad',
]
}

View File

@@ -13,7 +13,6 @@ dependencies {
testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-legacy:$jmock_version"
signature 'org.codehaus.mojo.signature:java16:1.1@signature'
}

View File

@@ -10,4 +10,10 @@ public interface FeatureFlags {
boolean shouldEnableProfilePictures();
boolean shouldEnableDisappearingMessages();
boolean shouldEnablePrivateGroupsInCore();
boolean shouldEnableForumsInCore();
boolean shouldEnableBlogsInCore();
}

View File

@@ -0,0 +1,35 @@
package org.briarproject.bramble.api;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.lang.ref.WeakReference;
import javax.annotation.concurrent.GuardedBy;
import javax.inject.Provider;
/**
* A {@link Provider} that keeps a {@link WeakReference} to the last provided
* instance and provides the same instance again until the instance is garbage
* collected.
*/
@NotNullByDefault
public abstract class WeakSingletonProvider<T> implements Provider<T> {
private final Object lock = new Object();
@GuardedBy("lock")
private WeakReference<T> ref = new WeakReference<>(null);
@Override
public T get() {
synchronized (lock) {
T instance = ref.get();
if (instance == null) {
instance = createInstance();
ref = new WeakReference<>(instance);
}
return instance;
}
}
public abstract T createInstance();
}

View File

@@ -128,7 +128,7 @@ public interface ClientHelper {
* group.
*/
ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException, FormatException;
throws DbException;
/**
* Stores the given contact ID in the group metadata of the given contact

View File

@@ -107,6 +107,32 @@ public interface ContactManager {
*/
String getHandshakeLink() throws DbException;
/**
* Returns the handshake link that needs to be sent to a contact we want
* to add.
*/
String getHandshakeLink(Transaction txn) throws DbException;
/**
* Creates a {@link PendingContact} from the given handshake link and
* alias, adds it to the database and returns it.
*
* @param link The handshake link received from the pending contact
* @param alias The alias the user has given this pending contact
* @throws UnsupportedVersionException If the link uses a format version
* that is not supported
* @throws FormatException If the link is invalid
* @throws GeneralSecurityException If the pending contact's handshake
* public key is invalid
* @throws ContactExistsException If a contact with the same handshake
* public key already exists
* @throws PendingContactExistsException If a pending contact with the same
* handshake public key already exists
*/
PendingContact addPendingContact(Transaction txn, String link, String alias)
throws DbException, FormatException, GeneralSecurityException,
ContactExistsException, PendingContactExistsException;
/**
* Creates a {@link PendingContact} from the given handshake link and
* alias, adds it to the database and returns it.
@@ -140,6 +166,13 @@ public interface ContactManager {
Collection<Pair<PendingContact, PendingContactState>> getPendingContacts()
throws DbException;
/**
* Returns a list of {@link PendingContact PendingContacts} and their
* {@link PendingContactState states}.
*/
Collection<Pair<PendingContact, PendingContactState>> getPendingContacts(Transaction txn)
throws DbException;
/**
* Removes a {@link PendingContact}.
*/

View File

@@ -471,6 +471,14 @@ public interface DatabaseComponent extends TransactionManager {
Map<MessageId, Integer> getUnackedMessagesToSend(Transaction txn,
ContactId c) throws DbException;
/**
* Reset the transmission count, expiry time and ETA of all messages that
* are eligible to be sent to the given contact. This includes messages that
* have already been sent and are not yet due for retransmission.
*/
void resetUnackedMessagesToSend(Transaction txn, ContactId c)
throws DbException;
/**
* Returns the total length, including headers, of all messages that are
* eligible to be sent to the given contact. This may include messages

View File

@@ -0,0 +1,32 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class MailboxProperties {
private final String onionAddress, authToken;
private final boolean owner;
public MailboxProperties(String onionAddress, String authToken,
boolean owner) {
this.onionAddress = onionAddress;
this.authToken = authToken;
this.owner = owner;
}
public String getOnionAddress() {
return onionAddress;
}
public String getAuthToken() {
return authToken;
}
public boolean isOwner() {
return owner;
}
}

View File

@@ -0,0 +1,33 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
@NotNullByDefault
public interface MailboxSettingsManager {
@Nullable
MailboxProperties getOwnMailboxProperties(Transaction txn)
throws DbException;
void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
throws DbException;
MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException;
void recordSuccessfulConnection(Transaction txn, long now)
throws DbException;
void recordFailedConnectionAttempt(Transaction txn, long now)
throws DbException;
void setPendingUpload(Transaction txn, ContactId id,
@Nullable String filename) throws DbException;
@Nullable
String getPendingUpload(Transaction txn, ContactId id) throws DbException;
}

View File

@@ -0,0 +1,59 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class MailboxStatus {
private final long lastAttempt, lastSuccess;
private final int attemptsSinceSuccess;
public MailboxStatus(long lastAttempt, long lastSuccess,
int attemptsSinceSuccess) {
this.lastAttempt = lastAttempt;
this.lastSuccess = lastSuccess;
this.attemptsSinceSuccess = attemptsSinceSuccess;
}
/**
* Returns the time of the last attempt to connect to the mailbox, in
* milliseconds since the Unix epoch, or -1 if no attempt has been made.
* <p>
* If an attempt is in progress and has not yet succeeded or failed then
* this method returns the time of the previous attempt, or -1 if the
* current attempt is the first.
*/
public long getTimeOfLastAttempt() {
return lastAttempt;
}
/**
* Returns the time of the last successful attempt to connect to the
* mailbox, in milliseconds since the Unix epoch, or -1 if no attempt has
* succeeded.
* <p>
* If the last attempt was successful then this method returns the same
* value as {@link #getTimeOfLastAttempt()}. If an attempt is in progress
* and has not yet succeeded or failed then this method returns the time
* of the previous successful connection, or -1 if no attempt has
* succeeded.
*/
public long getTimeOfLastSuccess() {
return lastSuccess;
}
/**
* Returns the number of attempts to connect to the mailbox that have
* failed since the last attempt succeeded, or the number of attempts that
* have been made, if no attempt has ever succeeded.
* <p>
* If an attempt is in progress and has not yet succeeded or failed then
* it is not included in this count.
*/
public int getAttemptsSinceSuccess() {
return attemptsSinceSuccess;
}
}

View File

@@ -1,13 +1,10 @@
package org.briarproject.bramble.api.plugin;
import static java.util.concurrent.TimeUnit.DAYS;
public interface TorConstants {
TransportId ID = new TransportId("org.briarproject.bramble.tor");
// Transport properties
String PROP_ONION_V2 = "onion";
String PROP_ONION_V3 = "onion3";
int DEFAULT_SOCKS_PORT = 59050;
@@ -21,14 +18,7 @@ public interface TorConstants {
String PREF_TOR_PORT = "port";
String PREF_TOR_MOBILE = "useMobileData";
String PREF_TOR_ONLY_WHEN_CHARGING = "onlyWhenCharging";
String HS_PRIVATE_KEY_V2 = "onionPrivKey";
String HS_PRIVATE_KEY_V3 = "onionPrivKey3";
String HS_V3_CREATED = "onionPrivKey3Created";
/**
* How long to publish a v3 hidden service before retiring the v2 service.
*/
long V3_MIGRATION_PERIOD_MS = DAYS.toMillis(180);
// Values for PREF_TOR_NETWORK
int PREF_TOR_NETWORK_AUTOMATIC = 0;

View File

@@ -22,4 +22,11 @@ public interface SettingsManager {
* namespace.
*/
void mergeSettings(Settings s, String namespace) throws DbException;
/**
* Merges the given settings with any existing settings in the given
* namespace.
*/
void mergeSettings(Transaction txn, Settings s, String namespace)
throws DbException;
}

View File

@@ -113,9 +113,25 @@ public interface KeyManager {
/**
* Looks up the given tag and returns a {@link StreamContext} for reading
* from the corresponding stream, or null if an error occurs or the tag was
* unexpected.
* unexpected. Marks the tag as recognised and updates the reordering
* window.
*/
@Nullable
StreamContext getStreamContext(TransportId t, byte[] tag)
throws DbException;
/**
* Looks up the given tag and returns a {@link StreamContext} for reading
* from the corresponding stream, or null if an error occurs or the tag was
* unexpected. Only returns the StreamContext; does not mark the tag as
* recognised.
*/
@Nullable
StreamContext getStreamContextOnly(TransportId t, byte[] tag)
throws DbException;
/**
* Marks the tag as recognised and updates the reordering window.
*/
void markTagAsRecognised(TransportId t, byte[] tag) throws DbException;
}

View File

@@ -1,12 +1,17 @@
package org.briarproject.bramble.test;
import org.jmock.Mockery;
import org.jmock.lib.concurrent.Synchroniser;
import org.junit.After;
public abstract class BrambleMockTestCase extends BrambleTestCase {
protected final Mockery context = new Mockery();
public BrambleMockTestCase() {
context.setThreadingPolicy(new Synchroniser());
}
@After
public void checkExpectations() {
context.assertIsSatisfied();

View File

@@ -31,6 +31,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
@@ -46,6 +47,7 @@ import static org.briarproject.bramble.api.sync.ClientId.MAX_CLIENT_ID_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
public class TestUtils {
@@ -209,6 +211,10 @@ public class TestUtils {
getAgreementPublicKey(), verified);
}
public static String getMailboxSecret() {
return toHexString(getRandomBytes(32)).toLowerCase(Locale.US);
}
public static double getMedian(Collection<? extends Number> samples) {
int size = samples.size();
if (size == 0) throw new IllegalArgumentException();

View File

@@ -10,13 +10,18 @@ apply from: '../dagger.gradle'
dependencies {
implementation project(path: ':bramble-api', configuration: 'default')
implementation 'org.bouncycastle:bcprov-jdk15on:1.69'
implementation 'org.bouncycastle:bcprov-jdk15to18:1.70'
//noinspection GradleDependency
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
implementation 'org.bitlet:weupnp:0.1.4'
implementation 'net.i2p.crypto:eddsa:0.2.0'
implementation 'org.whispersystems:curve25519-java:0.5.0'
implementation 'org.briarproject:jtorctl:0.3'
//noinspection GradleDependency
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
@@ -25,7 +30,8 @@ dependencies {
testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-legacy:$jmock_version"
testImplementation "org.jmock:jmock-imposters:$jmock_version"
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.3"
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.io.IoModule;
import org.briarproject.bramble.keyagreement.KeyAgreementModule;
import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.mailbox.MailboxModule;
import org.briarproject.bramble.plugin.PluginModule;
import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.record.RecordModule;
@@ -43,6 +44,7 @@ import dagger.Module;
IoModule.class,
KeyAgreementModule.class,
LifecycleModule.class,
MailboxModule.class,
PluginModule.class,
PropertiesModule.class,
RecordModule.class,

View File

@@ -121,28 +121,39 @@ class ContactManagerImpl implements ContactManager, EventListener {
@Override
public String getHandshakeLink() throws DbException {
KeyPair keyPair = db.transactionWithResult(true,
identityManager::getHandshakeKeys);
return db.transactionWithResult(true, this::getHandshakeLink);
}
@Override
public String getHandshakeLink(Transaction txn) throws DbException {
KeyPair keyPair = identityManager.getHandshakeKeys(txn);
return pendingContactFactory.createHandshakeLink(keyPair.getPublic());
}
@Override
public PendingContact addPendingContact(Transaction txn, String link, String alias)
throws DbException, FormatException, GeneralSecurityException {
PendingContact p =
pendingContactFactory.createPendingContact(link, alias);
AuthorId local = identityManager.getLocalAuthor(txn).getId();
db.addPendingContact(txn, p, local);
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
keyManager.addPendingContact(txn, p.getId(), p.getPublicKey(),
ourKeyPair);
return p;
}
@Override
public PendingContact addPendingContact(String link, String alias)
throws DbException, FormatException, GeneralSecurityException {
PendingContact p =
pendingContactFactory.createPendingContact(link, alias);
Transaction txn = db.startTransaction(false);
try {
AuthorId local = identityManager.getLocalAuthor(txn).getId();
db.addPendingContact(txn, p, local);
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
keyManager.addPendingContact(txn, p.getId(), p.getPublicKey(),
ourKeyPair);
PendingContact p = addPendingContact(txn, link, alias);
db.commitTransaction(txn);
return p;
} finally {
db.endTransaction(txn);
}
return p;
}
@Override
@@ -154,8 +165,13 @@ class ContactManagerImpl implements ContactManager, EventListener {
@Override
public Collection<Pair<PendingContact, PendingContactState>> getPendingContacts()
throws DbException {
Collection<PendingContact> pendingContacts =
db.transactionWithResult(true, db::getPendingContacts);
return db.transactionWithResult(true, this::getPendingContacts);
}
@Override
public Collection<Pair<PendingContact, PendingContactState>> getPendingContacts(Transaction txn)
throws DbException {
Collection<PendingContact> pendingContacts = db.getPendingContacts(txn);
List<Pair<PendingContact, PendingContactState>> pairs =
new ArrayList<>(pendingContacts.size());
for (PendingContact p : pendingContacts) {

View File

@@ -757,6 +757,13 @@ interface Database<T> {
*/
void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException;
/**
* Resets the transmission count, expiry time and ETA of all messages that
* are eligible to be sent to the given contact. This includes messages that
* have already been sent and are not yet due for retransmission.
*/
void resetUnackedMessagesToSend(T txn, ContactId c) throws DbException;
/**
* Sets the cleanup timer duration for the given message. This does not
* start the message's cleanup timer.
@@ -845,8 +852,8 @@ interface Database<T> {
* of the given message with respect to the given contact, using the latency
* of the transport over which it was sent.
*/
void updateExpiryTimeAndEta(T txn, ContactId c, MessageId m, long maxLatency)
throws DbException;
void updateExpiryTimeAndEta(T txn, ContactId c, MessageId m,
long maxLatency) throws DbException;
/**
* Stores the given transport keys, deleting any keys they have replaced.

View File

@@ -750,6 +750,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getUnackedMessagesToSend(txn, c);
}
@Override
public void resetUnackedMessagesToSend(Transaction transaction, ContactId c)
throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
db.resetUnackedMessagesToSend(txn, c);
}
@Override
public long getUnackedMessageBytesToSend(Transaction transaction,
ContactId c) throws DbException {

View File

@@ -429,8 +429,11 @@ abstract class JdbcDatabase implements Database<Connection> {
compactAndClose();
logDuration(LOG, "Compacting database", start);
// Allow the next transaction to reopen the DB
synchronized (connectionsLock) {
connectionsLock.lock();
try {
closed = false;
} finally {
connectionsLock.unlock();
}
txn = startTransaction();
try {
@@ -3290,6 +3293,29 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Override
public void resetUnackedMessagesToSend(Connection txn, ContactId c)
throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE statuses SET expiry = 0, txCount = 0, eta = 0"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
int affected = ps.executeUpdate();
if (affected < 0) {
throw new DbStateException();
}
ps.close();
} catch (SQLException e) {
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public void setCleanupTimerDuration(Connection txn, MessageId m,
long duration) throws DbException {

View File

@@ -1,18 +1,49 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.api.WeakSingletonProvider;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import javax.annotation.Nonnull;
import javax.inject.Singleton;
import javax.net.SocketFactory;
import dagger.Module;
import dagger.Provides;
import okhttp3.Dns;
import okhttp3.OkHttpClient;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@Module
public class IoModule {
private static final int CONNECT_TIMEOUT = 60_000; // Milliseconds
@Provides
@Singleton
TimeoutMonitor provideTimeoutMonitor(TimeoutMonitorImpl timeoutMonitor) {
return timeoutMonitor;
}
// Share an HTTP client instance between requests where possible, while
// allowing the client to be garbage-collected between requests. The
// provider keeps a weak reference to the last client instance and reuses
// the instance until it gets garbage-collected. See
// https://medium.com/@leandromazzuquini/if-you-are-using-okhttp-you-should-know-this-61d68e065a2b
@Provides
@Singleton
WeakSingletonProvider<OkHttpClient> provideOkHttpClientProvider(
SocketFactory torSocketFactory, Dns noDnsLookups) {
return new WeakSingletonProvider<OkHttpClient>() {
@Override
@Nonnull
public OkHttpClient createInstance() {
return new OkHttpClient.Builder()
.socketFactory(torSocketFactory)
.dns(noDnsLookups) // Don't make local DNS lookups
.connectTimeout(CONNECT_TIMEOUT, MILLISECONDS)
.build();
}
};
}
}

View File

@@ -0,0 +1,88 @@
package org.briarproject.bramble.mailbox;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import java.io.IOException;
import java.util.Collection;
import javax.annotation.concurrent.Immutable;
interface MailboxApi {
/**
* Sets up the mailbox with the setup token.
*
* @param properties MailboxProperties with the setup token
* @return the owner token
* @throws ApiException for 401 response.
*/
String setup(MailboxProperties properties)
throws IOException, ApiException;
/**
* Checks the status of the mailbox.
*
* @return true if the status is OK, false otherwise.
* @throws ApiException for 401 response.
*/
boolean checkStatus(MailboxProperties properties)
throws IOException, ApiException;
/**
* Adds a new contact to the mailbox.
*
* @throws TolerableFailureException if response code is 409
* (contact was already added).
*/
void addContact(MailboxProperties properties, MailboxContact contact)
throws IOException, ApiException, TolerableFailureException;
/**
* Deletes a contact from the mailbox.
* This should get called after a contact was removed from Briar.
*
* @throws TolerableFailureException if response code is 404
* (contact probably was already deleted).
*/
void deleteContact(MailboxProperties properties, ContactId contactId)
throws IOException, ApiException, TolerableFailureException;
/**
* Gets a list of {@link ContactId}s from the mailbox.
* These are the contacts that the mailbox already knows about.
*/
Collection<ContactId> getContacts(MailboxProperties properties)
throws IOException, ApiException;
@Immutable
@JsonSerialize
class MailboxContact {
public final int contactId;
public final String token, inboxId, outboxId;
MailboxContact(ContactId contactId,
String token,
String inboxId,
String outboxId) {
this.contactId = contactId.getInt();
this.token = token;
this.inboxId = inboxId;
this.outboxId = outboxId;
}
}
@Immutable
class ApiException extends Exception {
}
/**
* A failure that does not need to be retried,
* e.g. when adding a contact that already exists.
*/
@Immutable
class TolerableFailureException extends Exception {
}
}

View File

@@ -0,0 +1,172 @@
package org.briarproject.bramble.mailbox;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.briarproject.bramble.api.WeakSingletonProvider;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.inject.Inject;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import static com.fasterxml.jackson.databind.MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES;
import static java.util.Objects.requireNonNull;
import static okhttp3.internal.Util.EMPTY_REQUEST;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
@NotNullByDefault
class MailboxApiImpl implements MailboxApi {
private final WeakSingletonProvider<OkHttpClient> httpClientProvider;
private final JsonMapper mapper = JsonMapper.builder()
.enable(BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES)
.build();
private static final MediaType JSON =
requireNonNull(MediaType.parse("application/json; charset=utf-8"));
@Inject
MailboxApiImpl(WeakSingletonProvider<OkHttpClient> httpClientProvider) {
this.httpClientProvider = httpClientProvider;
}
@Override
public String setup(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Request request = getRequestBuilder(properties.getAuthToken())
.url(properties.getOnionAddress() + "/setup")
.put(EMPTY_REQUEST)
.build();
OkHttpClient client = httpClientProvider.get();
Response response = client.newCall(request).execute();
// TODO consider throwing a special exception for the 401 case
if (response.code() == 401) throw new ApiException();
if (!response.isSuccessful()) throw new ApiException();
ResponseBody body = response.body();
if (body == null) throw new ApiException();
try {
JsonNode node = mapper.readTree(body.string());
JsonNode tokenNode = node.get("token");
if (tokenNode == null) {
throw new ApiException();
}
String ownerToken = tokenNode.textValue();
if (ownerToken == null || !isValidToken(ownerToken)) {
throw new ApiException();
}
return ownerToken;
} catch (JacksonException e) {
throw new ApiException();
}
}
private boolean isValidToken(String token) {
if (token.length() != 64) return false;
try {
// try to convert to bytes
fromHexString(token);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
@Override
public boolean checkStatus(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Response response = sendGetRequest(properties, "/status");
if (response.code() == 401) throw new ApiException();
return response.isSuccessful();
}
@Override
public void addContact(MailboxProperties properties, MailboxContact contact)
throws IOException, ApiException,
TolerableFailureException {
if (!properties.isOwner()) throw new IllegalArgumentException();
byte[] bodyBytes = mapper.writeValueAsBytes(contact);
RequestBody body = RequestBody.create(JSON, bodyBytes);
Request request = getRequestBuilder(properties.getAuthToken())
.url(properties.getOnionAddress() + "/contacts")
.post(body)
.build();
OkHttpClient client = httpClientProvider.get();
Response response = client.newCall(request).execute();
if (response.code() == 409) throw new TolerableFailureException();
if (!response.isSuccessful()) throw new ApiException();
}
@Override
public void deleteContact(MailboxProperties properties, ContactId contactId)
throws IOException, ApiException, TolerableFailureException {
if (!properties.isOwner()) throw new IllegalArgumentException();
String url = properties.getOnionAddress() + "/contacts/" +
contactId.getInt();
Request request = getRequestBuilder(properties.getAuthToken())
.delete()
.url(url)
.build();
OkHttpClient client = httpClientProvider.get();
Response response = client.newCall(request).execute();
if (response.code() == 404) throw new TolerableFailureException();
if (response.code() != 200) throw new ApiException();
}
@Override
public Collection<ContactId> getContacts(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Response response = sendGetRequest(properties, "/contacts");
if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body();
if (body == null) throw new ApiException();
try {
JsonNode node = mapper.readTree(body.string());
JsonNode contactsNode = node.get("contacts");
if (contactsNode == null || !contactsNode.isArray()) {
throw new ApiException();
}
List<ContactId> list = new ArrayList<>();
for (JsonNode contactNode : contactsNode) {
if (!contactNode.isNumber()) throw new ApiException();
int id = contactNode.intValue();
if (id < 1) throw new ApiException();
list.add(new ContactId(id));
}
return list;
} catch (JacksonException e) {
throw new ApiException();
}
}
private Response sendGetRequest(MailboxProperties properties, String path)
throws IOException {
Request request = getRequestBuilder(properties.getAuthToken())
.url(properties.getOnionAddress() + path)
.build();
OkHttpClient client = httpClientProvider.get();
return client.newCall(request).execute();
}
private Request.Builder getRequestBuilder(String token) {
return new Request.Builder()
.addHeader("Authorization", "Bearer " + token);
}
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import dagger.Module;
import dagger.Provides;
@Module
public class MailboxModule {
@Provides
MailboxSettingsManager provideMailboxSettingsManager(
MailboxSettingsManagerImpl mailboxSettingsManager) {
return mailboxSettingsManager;
}
}

View File

@@ -0,0 +1,109 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable
@NotNullByDefault
class MailboxSettingsManagerImpl implements MailboxSettingsManager {
// Package access for testing
static final String SETTINGS_NAMESPACE = "mailbox";
static final String SETTINGS_KEY_ONION = "onion";
static final String SETTINGS_KEY_TOKEN = "token";
static final String SETTINGS_KEY_LAST_ATTEMPT = "lastAttempt";
static final String SETTINGS_KEY_LAST_SUCCESS = "lastSuccess";
static final String SETTINGS_KEY_ATTEMPTS = "attempts";
static final String SETTINGS_UPLOADS_NAMESPACE = "mailbox-uploads";
private final SettingsManager settingsManager;
@Inject
MailboxSettingsManagerImpl(SettingsManager settingsManager) {
this.settingsManager = settingsManager;
}
@Override
public MailboxProperties getOwnMailboxProperties(Transaction txn)
throws DbException {
Settings s = settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
String onion = s.get(SETTINGS_KEY_ONION);
String token = s.get(SETTINGS_KEY_TOKEN);
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return null;
return new MailboxProperties(onion, token, true);
}
@Override
public void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
throws DbException {
Settings s = new Settings();
s.put(SETTINGS_KEY_ONION, p.getOnionAddress());
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken());
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
}
@Override
public MailboxStatus getOwnMailboxStatus(Transaction txn)
throws DbException {
Settings s = settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
long lastAttempt = s.getLong(SETTINGS_KEY_LAST_ATTEMPT, -1);
long lastSuccess = s.getLong(SETTINGS_KEY_LAST_SUCCESS, -1);
int attempts = s.getInt(SETTINGS_KEY_ATTEMPTS, 0);
return new MailboxStatus(lastAttempt, lastSuccess, attempts);
}
@Override
public void recordSuccessfulConnection(Transaction txn, long now)
throws DbException {
Settings s = new Settings();
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
}
@Override
public void recordFailedConnectionAttempt(Transaction txn, long now)
throws DbException {
Settings oldSettings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
int attempts = oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
Settings newSettings = new Settings();
newSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
newSettings.putInt(SETTINGS_KEY_ATTEMPTS, attempts + 1);
settingsManager.mergeSettings(txn, newSettings, SETTINGS_NAMESPACE);
}
@Override
public void setPendingUpload(Transaction txn, ContactId id,
@Nullable String filename) throws DbException {
Settings s = new Settings();
String value = filename == null ? "" : filename;
s.put(String.valueOf(id.getInt()), value);
settingsManager.mergeSettings(txn, s, SETTINGS_UPLOADS_NAMESPACE);
}
@Nullable
@Override
public String getPendingUpload(Transaction txn, ContactId id)
throws DbException {
Settings s =
settingsManager.getSettings(txn, SETTINGS_UPLOADS_NAMESPACE);
String filename = s.get(String.valueOf(id.getInt()));
if (isNullOrEmpty(filename)) return null;
return filename;
}
}

View File

@@ -1,40 +1,71 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List;
// TODO: Create a module for this so it doesn't have to be public
@NotNullByDefault
public interface CircumventionProvider {
enum BridgeType {
DEFAULT_OBFS4,
NON_DEFAULT_OBFS4,
MEEK
}
/**
* Countries where Tor is blocked, i.e. vanilla Tor connection won't work.
*
* <p>
* See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
* and https://trac.torproject.org/projects/tor/wiki/doc/OONI/censorshipwiki
*/
String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE"};
String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE", "RU"};
/**
* Countries where obfs4 or meek bridge connections are likely to work.
* Should be a subset of {@link #BLOCKED}.
* Should be a subset of {@link #BLOCKED} and the union of
* {@link #DEFAULT_OBFS4_BRIDGES}, {@link #NON_DEFAULT_OBFS4_BRIDGES} and
* {@link #MEEK_BRIDGES}.
*/
String[] BRIDGES = { "CN", "IR", "EG", "BY", "TR", "SY", "VE" };
String[] BRIDGES = {"CN", "IR", "EG", "BY", "TR", "SY", "VE", "RU"};
/**
* Countries where default obfs4 bridges are likely to work.
* Should be a subset of {@link #BRIDGES}.
*/
String[] DEFAULT_OBFS4_BRIDGES = {"EG", "BY", "TR", "SY", "VE"};
/**
* Countries where non-default obfs4 bridges are likely to work.
* Should be a subset of {@link #BRIDGES}.
*/
String[] NON_DEFAULT_OBFS4_BRIDGES = {"RU"};
/**
* Countries where obfs4 bridges won't work and meek is needed.
* Should be a subset of {@link #BRIDGES}.
*/
String[] NEEDS_MEEK = {"CN", "IR"};
String[] MEEK_BRIDGES = {"CN", "IR"};
/**
* Returns true if vanilla Tor connections are blocked in the given country.
*/
boolean isTorProbablyBlocked(String countryCode);
/**
* Returns true if bridge connections of some type work in the given
* country.
*/
boolean doBridgesWork(String countryCode);
boolean needsMeek(String countryCode);
/**
* Returns the best type of bridge connection for the given country, or
* {@link #DEFAULT_OBFS4_BRIDGES} if no bridge type is known to work.
*/
BridgeType getBestBridgeType(String countryCode);
@IoExecutor
List<String> getBridges(boolean meek);
List<String> getBridges(BridgeType type);
}

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.InputStream;
import java.util.ArrayList;
@@ -9,24 +10,31 @@ import java.util.List;
import java.util.Scanner;
import java.util.Set;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.Arrays.asList;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
@Immutable
@NotNullByDefault
class CircumventionProviderImpl implements CircumventionProvider {
private final static String BRIDGE_FILE_NAME = "bridges";
private static final Set<String> BLOCKED_IN_COUNTRIES =
new HashSet<>(asList(BLOCKED));
private static final Set<String> BRIDGES_WORK_IN_COUNTRIES =
private static final Set<String> BRIDGE_COUNTRIES =
new HashSet<>(asList(BRIDGES));
private static final Set<String> BRIDGES_NEED_MEEK =
new HashSet<>(asList(NEEDS_MEEK));
@Nullable
private volatile List<String> bridges = null;
private static final Set<String> DEFAULT_OBFS4_BRIDGE_COUNTRIES =
new HashSet<>(asList(DEFAULT_OBFS4_BRIDGES));
private static final Set<String> NON_DEFAULT_OBFS4_BRIDGE_COUNTRIES =
new HashSet<>(asList(NON_DEFAULT_OBFS4_BRIDGES));
private static final Set<String> MEEK_COUNTRIES =
new HashSet<>(asList(MEEK_BRIDGES));
@Inject
CircumventionProviderImpl() {
@@ -39,33 +47,42 @@ class CircumventionProviderImpl implements CircumventionProvider {
@Override
public boolean doBridgesWork(String countryCode) {
return BRIDGES_WORK_IN_COUNTRIES.contains(countryCode);
return BRIDGE_COUNTRIES.contains(countryCode);
}
@Override
public boolean needsMeek(String countryCode) {
return BRIDGES_NEED_MEEK.contains(countryCode);
public BridgeType getBestBridgeType(String countryCode) {
if (DEFAULT_OBFS4_BRIDGE_COUNTRIES.contains(countryCode)) {
return DEFAULT_OBFS4;
} else if (NON_DEFAULT_OBFS4_BRIDGE_COUNTRIES.contains(countryCode)) {
return NON_DEFAULT_OBFS4;
} else if (MEEK_COUNTRIES.contains(countryCode)) {
return MEEK;
} else {
return DEFAULT_OBFS4;
}
}
@Override
@IoExecutor
public List<String> getBridges(boolean useMeek) {
List<String> bridges = this.bridges;
if (bridges != null) return new ArrayList<>(bridges);
InputStream is = getClass().getClassLoader()
.getResourceAsStream(BRIDGE_FILE_NAME);
public List<String> getBridges(BridgeType type) {
InputStream is = requireNonNull(getClass().getClassLoader()
.getResourceAsStream(BRIDGE_FILE_NAME));
Scanner scanner = new Scanner(is);
bridges = new ArrayList<>();
List<String> bridges = new ArrayList<>();
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
boolean isMeekBridge = line.startsWith("Bridge meek");
if (useMeek && !isMeekBridge || !useMeek && isMeekBridge) continue;
if (!line.startsWith("#")) bridges.add(line);
boolean isDefaultObfs4 = line.startsWith("d ");
boolean isNonDefaultObfs4 = line.startsWith("n ");
boolean isMeek = line.startsWith("m ");
if ((type == DEFAULT_OBFS4 && isDefaultObfs4) ||
(type == NON_DEFAULT_OBFS4 && isNonDefaultObfs4) ||
(type == MEEK && isMeek)) {
bridges.add(line.substring(2));
}
}
scanner.close();
this.bridges = bridges;
return bridges;
}

View File

@@ -33,6 +33,7 @@ import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
@@ -79,9 +80,7 @@ import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_PLUG
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_MOBILE;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_NETWORK;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING;
import static org.briarproject.bramble.api.plugin.TorConstants.HS_PRIVATE_KEY_V2;
import static org.briarproject.bramble.api.plugin.TorConstants.HS_PRIVATE_KEY_V3;
import static org.briarproject.bramble.api.plugin.TorConstants.HS_V3_CREATED;
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
@@ -90,12 +89,11 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WITH_BRIDGES;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V2;
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
import static org.briarproject.bramble.api.plugin.TorConstants.V3_MIGRATION_PERIOD_MS;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@@ -107,44 +105,46 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@ParametersNotNullByDefault
abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final Logger LOG = getLogger(TorPlugin.class.getName());
static final Logger LOG = getLogger(TorPlugin.class.getName());
private static final String[] EVENTS = {
static final String[] EVENTS = {
"CIRC", "ORCONN", "HS_DESC", "NOTICE", "WARN", "ERR"
};
private static final String OWNER = "__OwningControllerProcess";
private static final int COOKIE_TIMEOUT_MS = 3000;
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
private static final Pattern ONION_V2 = Pattern.compile("[a-z2-7]{16}");
static final String OWNER = "__OwningControllerProcess";
static final int COOKIE_TIMEOUT_MS = 3000;
static final int COOKIE_POLLING_INTERVAL_MS = 200;
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
private final Executor ioExecutor, wakefulIoExecutor;
private final Executor connectionStatusExecutor;
private final NetworkManager networkManager;
final NetworkManager networkManager;
private final LocationUtils locationUtils;
private final SocketFactory torSocketFactory;
private final Clock clock;
private final BatteryManager batteryManager;
final Clock clock;
final BatteryManager batteryManager;
private final Backoff backoff;
private final TorRendezvousCrypto torRendezvousCrypto;
private final PluginCallback callback;
final PluginCallback callback;
private final String architecture;
private final CircumventionProvider circumventionProvider;
private final ResourceProvider resourceProvider;
private final long maxLatency;
private final int maxIdleTime;
private final int socketTimeout;
private final File torDirectory, geoIpFile, configFile;
private int torSocksPort;
private int torControlPort;
private final File doneFile, cookieFile;
private final AtomicBoolean used = new AtomicBoolean(false);
final File torDirectory;
final File geoIpFile;
final File configFile;
final int torSocksPort;
final int torControlPort;
private final File doneFile;
final File cookieFile;
final AtomicBoolean used = new AtomicBoolean(false);
protected final PluginState state = new PluginState();
private volatile Socket controlSocket = null;
private volatile TorControlConnection controlConnection = null;
private volatile Settings settings = null;
volatile Socket controlSocket = null;
volatile TorControlConnection controlConnection = null;
volatile Settings settings = null;
protected abstract int getProcessId();
@@ -245,6 +245,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
Process torProcess;
ProcessBuilder pb =
new ProcessBuilder(torPath, "-f", configPath, OWNER, pid);
// TODO: pb.redirectErrorStream on Linux, too?
Map<String, String> env = pb.environment();
env.put("HOME", torDirectory.getAbsolutePath());
pb.directory(torDirectory);
@@ -284,6 +285,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (LOG.isLoggable(INFO)) listFiles(torDirectory);
throw new PluginException();
}
//noinspection BusyWait
Thread.sleep(COOKIE_POLLING_INTERVAL_MS);
}
LOG.info("Auth cookie created");
@@ -320,8 +322,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
bind();
}
// TODO: Remove after a reasonable migration period (added 2020-06-25)
private Settings migrateSettings(Settings settings) {
Settings migrateSettings(Settings settings) {
int network = settings.getInt(PREF_TOR_NETWORK,
DEFAULT_PREF_TOR_NETWORK);
if (network == PREF_TOR_NETWORK_NEVER) {
@@ -332,11 +335,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return settings;
}
private boolean assetsAreUpToDate() {
boolean assetsAreUpToDate() {
return doneFile.lastModified() > getLastUpdateTime();
}
private void installAssets() throws PluginException {
void installAssets() throws PluginException {
try {
// The done file may already exist from a previous installation
//noinspection ResultOfMethodCallIgnored
@@ -391,20 +394,20 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private InputStream getObfs4InputStream() throws IOException {
InputStream in = resourceProvider
.getResourceInputStream("obfs4proxy_" + architecture, ".zip");
.getResourceInputStream("obfs4proxy_" + "linux-x86_64", ".zip");
ZipInputStream zin = new ZipInputStream(in);
if (zin.getNextEntry() == null) throw new IOException();
return zin;
}
private static void append(StringBuilder strb, String name, int value) {
protected static void append(StringBuilder strb, String name, int value) {
strb.append(name);
strb.append(" ");
strb.append(value);
strb.append("\n");
}
private InputStream getConfigInputStream() {
protected InputStream getConfigInputStream() {
StringBuilder strb = new StringBuilder();
append(strb, "ControlPort", torControlPort);
append(strb, "CookieAuthentication", 1);
@@ -412,11 +415,12 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
append(strb, "RunAsDaemon", 1);
append(strb, "SafeSocks", 1);
append(strb, "SocksPort", torSocksPort);
//noinspection CharsetObjectCanBeUsed
return new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("UTF-8")));
}
private void listFiles(File f) {
void listFiles(File f) {
if (f.isDirectory()) {
File[] children = f.listFiles();
if (children != null) for (File child : children) listFiles(child);
@@ -425,7 +429,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
private byte[] read(File f) throws IOException {
byte[] read(File f) throws IOException {
byte[] b = new byte[(int) f.length()];
FileInputStream in = new FileInputStream(f);
try {
@@ -441,7 +445,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
private void bind() {
void bind() {
ioExecutor.execute(() -> {
// If there's already a port number stored in config, reuse it
String portString = settings.get(PREF_TOR_PORT);
@@ -478,54 +482,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void publishHiddenService(String port) {
if (!state.isTorRunning()) return;
// TODO: Remove support for v2 hidden services after a reasonable
// migration period (migration started 2020-06-30)
String privKey2 = settings.get(HS_PRIVATE_KEY_V2);
String privKey3 = settings.get(HS_PRIVATE_KEY_V3);
String v3Created = settings.get(HS_V3_CREATED);
// Publish a v2 hidden service if we've already created one, and
// either we've never published a v3 hidden service or we're still
// in the migration period since first publishing it
if (!isNullOrEmpty(privKey2)) {
long now = clock.currentTimeMillis();
long then = v3Created == null ? now : Long.parseLong(v3Created);
if (now - then >= V3_MIGRATION_PERIOD_MS) retireV2HiddenService();
else publishV2HiddenService(port, privKey2);
}
publishV3HiddenService(port, privKey3);
}
private void publishV2HiddenService(String port, String privKey) {
LOG.info("Creating v2 hidden service");
Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port);
Map<String, String> response;
try {
response = controlConnection.addOnion(privKey, portLines);
} catch (IOException e) {
logException(LOG, WARNING, e);
return;
}
if (!response.containsKey(HS_ADDRESS)) {
LOG.warning("Tor did not return a hidden service address");
return;
}
String onion2 = response.get(HS_ADDRESS);
if (LOG.isLoggable(INFO)) {
LOG.info("V2 hidden service " + scrubOnion(onion2));
}
// The hostname has already been published and the private key stored
}
private void retireV2HiddenService() {
LOG.info("Retiring v2 hidden service");
TransportProperties p = new TransportProperties();
p.put(PROP_ONION_V2, "");
callback.mergeLocalProperties(p);
Settings s = new Settings();
s.put(HS_PRIVATE_KEY_V2, "");
callback.mergeSettings(s);
}
private void publishV3HiddenService(String port, @Nullable String privKey) {
LOG.info("Creating v3 hidden service");
Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port);
@@ -562,7 +522,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// Save the hidden service's private key for next time
Settings s = new Settings();
s.put(HS_PRIVATE_KEY_V3, response.get(HS_PRIVKEY));
s.put(HS_V3_CREATED, String.valueOf(clock.currentTimeMillis()));
callback.mergeSettings(s);
}
}
@@ -590,20 +549,20 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
}
private void enableBridges(boolean enable, boolean needsMeek)
private void enableBridges(boolean enable, BridgeType bridgeType)
throws IOException {
if (enable) {
Collection<String> conf = new ArrayList<>();
conf.add("UseBridges 1");
File obfs4File = getObfs4ExecutableFile();
if (needsMeek) {
if (bridgeType == MEEK) {
conf.add("ClientTransportPlugin meek_lite exec " +
obfs4File.getAbsolutePath());
} else {
conf.add("ClientTransportPlugin obfs4 exec " +
obfs4File.getAbsolutePath());
}
conf.addAll(circumventionProvider.getBridges(needsMeek));
conf.addAll(circumventionProvider.getBridges(bridgeType));
controlConnection.setConf(conf);
} else {
controlConnection.setConf("UseBridges", "0");
@@ -669,49 +628,30 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public DuplexTransportConnection createConnection(TransportProperties p) {
if (getState() != ACTIVE) return null;
// TODO: Remove support for v2 hidden services after a reasonable
// migration period (migration started 2020-06-30)
String bestOnion = null, version = null;
String onion2 = p.get(PROP_ONION_V2);
String onion3 = p.get(PROP_ONION_V3);
if (!isNullOrEmpty(onion2)) {
if (ONION_V2.matcher(onion2).matches()) {
bestOnion = onion2;
version = "v2";
} else {
// Don't scrub the address so we can find the problem
if (LOG.isLoggable(INFO))
LOG.info("Invalid v2 hostname: " + onion2);
if (onion3 != null && !ONION_V3.matcher(onion3).matches()) {
// Don't scrub the address so we can find the problem
if (LOG.isLoggable(INFO)) {
LOG.info("Invalid v3 hostname: " + onion3);
}
onion3 = null;
}
if (!isNullOrEmpty(onion3)) {
if (ONION_V3.matcher(onion3).matches()) {
bestOnion = onion3;
version = "v3";
} else {
// Don't scrub the address so we can find the problem
if (LOG.isLoggable(INFO))
LOG.info("Invalid v3 hostname: " + onion3);
}
}
if (bestOnion == null) return null;
if (onion3 == null) return null;
Socket s = null;
try {
if (LOG.isLoggable(INFO)) {
LOG.info("Connecting to " + version + " "
+ scrubOnion(bestOnion));
LOG.info("Connecting to v3 " + scrubOnion(onion3));
}
s = torSocketFactory.createSocket(bestOnion + ".onion", 80);
s = torSocketFactory.createSocket(onion3 + ".onion", 80);
s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO)) {
LOG.info("Connected to " + version + " "
+ scrubOnion(bestOnion));
LOG.info("Connected to v3 " + scrubOnion(onion3));
}
return new TorTransportConnection(this, s);
} catch (IOException e) {
if (LOG.isLoggable(INFO)) {
LOG.info("Could not connect to " + version + " "
+ scrubOnion(bestOnion) + ": " + e.toString());
LOG.info("Could not connect to v3 "
+ scrubOnion(onion3) + ": " + e.toString());
}
tryToClose(s, LOG, WARNING);
return null;
@@ -879,8 +819,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
});
}
private void updateConnectionStatus(NetworkStatus status,
boolean charging) {
void updateConnectionStatus(NetworkStatus status,
boolean charging) {
connectionStatusExecutor.execute(() -> {
if (!state.isTorRunning()) return;
boolean online = status.isConnected();
@@ -911,7 +851,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
int reasonsDisabled = 0;
boolean enableNetwork = false, enableBridges = false;
boolean useMeek = false, enableConnectionPadding = false;
boolean enableConnectionPadding = false;
BridgeType bridgeType =
circumventionProvider.getBestBridgeType(country);
if (!online) {
LOG.info("Disabling network, device is offline");
@@ -940,14 +882,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
enableNetwork = true;
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
(automatic && bridgesWork)) {
if (ipv6Only ||
circumventionProvider.needsMeek(country)) {
LOG.info("Using meek bridges");
enableBridges = true;
useMeek = true;
} else {
LOG.info("Using obfs4 bridges");
enableBridges = true;
if (ipv6Only) bridgeType = MEEK;
enableBridges = true;
if (LOG.isLoggable(INFO)) {
LOG.info("Using bridge type " + bridgeType);
}
} else {
LOG.info("Not using bridges");
@@ -965,7 +903,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
try {
if (enableNetwork) {
enableBridges(enableBridges, useMeek);
enableBridges(enableBridges, bridgeType);
enableConnectionPadding(enableConnectionPadding);
useIpv6(ipv6Only);
}
@@ -1005,17 +943,17 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Nullable
private ServerSocket serverSocket = null;
synchronized void setStarted() {
private synchronized void setStarted() {
started = true;
callback.pluginStateChanged(getState());
}
synchronized boolean isTorRunning() {
private synchronized boolean isTorRunning() {
return started && !stopped;
}
@Nullable
synchronized ServerSocket setStopped() {
private synchronized ServerSocket setStopped() {
stopped = true;
ServerSocket ss = serverSocket;
serverSocket = null;
@@ -1023,44 +961,44 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return ss;
}
synchronized void setBootstrapped() {
private synchronized void setBootstrapped() {
bootstrapped = true;
callback.pluginStateChanged(getState());
}
synchronized boolean getAndSetCircuitBuilt() {
private synchronized boolean getAndSetCircuitBuilt() {
boolean firstCircuit = !circuitBuilt;
circuitBuilt = true;
callback.pluginStateChanged(getState());
return firstCircuit;
}
synchronized void enableNetwork(boolean enable) {
private synchronized void enableNetwork(boolean enable) {
networkInitialised = true;
networkEnabled = enable;
if (!enable) circuitBuilt = false;
callback.pluginStateChanged(getState());
}
synchronized void setReasonsDisabled(int reasonsDisabled) {
private synchronized void setReasonsDisabled(int reasonsDisabled) {
settingsChecked = true;
this.reasonsDisabled = reasonsDisabled;
callback.pluginStateChanged(getState());
}
// Doesn't affect getState()
synchronized boolean setServerSocket(ServerSocket ss) {
private synchronized boolean setServerSocket(ServerSocket ss) {
if (stopped || serverSocket != null) return false;
serverSocket = ss;
return true;
}
// Doesn't affect getState()
synchronized void clearServerSocket(ServerSocket ss) {
private synchronized void clearServerSocket(ServerSocket ss) {
if (serverSocket == ss) serverSocket = null;
}
synchronized State getState() {
private synchronized State getState() {
if (!started || stopped || !settingsChecked) {
return STARTING_STOPPING;
}
@@ -1070,7 +1008,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return bootstrapped && circuitBuilt ? ACTIVE : ENABLING;
}
synchronized int getReasonsDisabled() {
private synchronized int getReasonsDisabled() {
return getState() == DISABLED ? reasonsDisabled : 0;
}
}

View File

@@ -37,4 +37,10 @@ class SettingsManagerImpl implements SettingsManager {
public void mergeSettings(Settings s, String namespace) throws DbException {
db.transaction(false, txn -> db.mergeSettings(txn, s, namespace));
}
@Override
public void mergeSettings(Transaction txn, Settings s, String namespace)
throws DbException {
db.mergeSettings(txn, s, namespace);
}
}

View File

@@ -0,0 +1,12 @@
package org.briarproject.bramble.system;
import javax.annotation.Nullable;
import java.security.Provider;
public class WindowsSecureRandomProvider extends AbstractSecureRandomProvider {
@Nullable
@Override
public Provider getProvider() {
return null;
}
}

View File

@@ -215,6 +215,23 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
m.getStreamContext(txn, tag)));
}
@Override
public StreamContext getStreamContextOnly(TransportId t, byte[] tag)
throws DbException {
return withManager(t, m ->
db.transactionWithNullableResult(false, txn ->
m.getStreamContextOnly(txn, tag)));
}
@Override
public void markTagAsRecognised(TransportId t, byte[] tag)
throws DbException {
withManager(t, m -> {
db.transaction(false, txn -> m.markTagAsRecognised(txn, tag));
return null;
});
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactRemovedEvent) {

View File

@@ -48,4 +48,9 @@ interface TransportKeyManager {
StreamContext getStreamContext(Transaction txn, byte[] tag)
throws DbException;
@Nullable
StreamContext getStreamContextOnly(Transaction txn, byte[] tag);
void markTagAsRecognised(Transaction txn, byte[] tag) throws DbException;
}

View File

@@ -393,56 +393,82 @@ class TransportKeyManagerImpl implements TransportKeyManager {
throws DbException {
lock.lock();
try {
// Look up the incoming keys for the tag
TagContext tagCtx = inContexts.remove(new Bytes(tag));
if (tagCtx == null) return null;
MutableIncomingKeys inKeys = tagCtx.inKeys;
// Create a stream context
StreamContext ctx = new StreamContext(tagCtx.contactId,
tagCtx.pendingContactId, transportId,
inKeys.getTagKey(), inKeys.getHeaderKey(),
tagCtx.streamNumber, tagCtx.handshakeMode);
// Update the reordering window
ReorderingWindow window = inKeys.getWindow();
Change change = window.setSeen(tagCtx.streamNumber);
// Add tags for any stream numbers added to the window
for (long streamNumber : change.getAdded()) {
byte[] addTag = new byte[TAG_LENGTH];
transportCrypto.encodeTag(addTag, inKeys.getTagKey(),
PROTOCOL_VERSION, streamNumber);
TagContext tagCtx1 = new TagContext(tagCtx.keySetId,
tagCtx.contactId, tagCtx.pendingContactId, inKeys,
streamNumber, tagCtx.handshakeMode);
inContexts.put(new Bytes(addTag), tagCtx1);
}
// Remove tags for any stream numbers removed from the window
for (long streamNumber : change.getRemoved()) {
if (streamNumber == tagCtx.streamNumber) continue;
byte[] removeTag = new byte[TAG_LENGTH];
transportCrypto.encodeTag(removeTag, inKeys.getTagKey(),
PROTOCOL_VERSION, streamNumber);
inContexts.remove(new Bytes(removeTag));
}
// Write the window back to the DB
db.setReorderingWindow(txn, tagCtx.keySetId, transportId,
inKeys.getTimePeriod(), window.getBase(),
window.getBitmap());
// If the outgoing keys are inactive, activate them
MutableTransportKeySet ks = keys.get(tagCtx.keySetId);
MutableOutgoingKeys outKeys =
ks.getKeys().getCurrentOutgoingKeys();
if (!outKeys.isActive()) {
LOG.info("Activating outgoing keys");
outKeys.activate();
considerReplacingOutgoingKeys(ks);
db.setTransportKeysActive(txn, transportId, tagCtx.keySetId);
}
StreamContext ctx = streamContextFromTag(tag);
if (ctx == null) return null;
markTagAsRecognised(txn, tag);
return ctx;
} finally {
lock.unlock();
}
}
@Override
public StreamContext getStreamContextOnly(Transaction txn, byte[] tag) {
lock.lock();
try {
return streamContextFromTag(tag);
} finally {
lock.unlock();
}
}
@GuardedBy("lock")
@Nullable
private StreamContext streamContextFromTag(byte[] tag) {
// Look up the incoming keys for the tag
TagContext tagCtx = inContexts.get(new Bytes(tag));
if (tagCtx == null) return null;
MutableIncomingKeys inKeys = tagCtx.inKeys;
// Create a stream context
return new StreamContext(tagCtx.contactId,
tagCtx.pendingContactId, transportId,
inKeys.getTagKey(), inKeys.getHeaderKey(),
tagCtx.streamNumber, tagCtx.handshakeMode);
}
@Override
public void markTagAsRecognised(Transaction txn, byte[] tag)
throws DbException {
TagContext tagCtx = inContexts.remove(new Bytes(tag));
if (tagCtx == null) return;
MutableIncomingKeys inKeys = tagCtx.inKeys;
// Update the reordering window
ReorderingWindow window = inKeys.getWindow();
Change change = window.setSeen(tagCtx.streamNumber);
// Add tags for any stream numbers added to the window
for (long streamNumber : change.getAdded()) {
byte[] addTag = new byte[TAG_LENGTH];
transportCrypto.encodeTag(addTag, inKeys.getTagKey(),
PROTOCOL_VERSION, streamNumber);
TagContext tagCtx1 = new TagContext(tagCtx.keySetId,
tagCtx.contactId, tagCtx.pendingContactId, inKeys,
streamNumber, tagCtx.handshakeMode);
inContexts.put(new Bytes(addTag), tagCtx1);
}
// Remove tags for any stream numbers removed from the window
for (long streamNumber : change.getRemoved()) {
if (streamNumber == tagCtx.streamNumber) continue;
byte[] removeTag = new byte[TAG_LENGTH];
transportCrypto.encodeTag(removeTag, inKeys.getTagKey(),
PROTOCOL_VERSION, streamNumber);
inContexts.remove(new Bytes(removeTag));
}
// Write the window back to the DB
db.setReorderingWindow(txn, tagCtx.keySetId, transportId,
inKeys.getTimePeriod(), window.getBase(),
window.getBitmap());
// If the outgoing keys are inactive, activate them
MutableTransportKeySet ks = keys.get(tagCtx.keySetId);
MutableOutgoingKeys outKeys =
ks.getKeys().getCurrentOutgoingKeys();
if (!outKeys.isActive()) {
LOG.info("Activating outgoing keys");
outKeys.activate();
considerReplacingOutgoingKeys(ks);
db.setTransportKeysActive(txn, transportId, tagCtx.keySetId);
}
}
@DatabaseExecutor
@Wakeful
private void updateKeys(Transaction txn) throws DbException {

View File

@@ -1,10 +1,17 @@
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0
Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
d Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
d Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
d Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
d Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
d Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
d Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
d Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
d Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
d Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
d Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
d Bridge obfs4 185.100.85.3:443 5B403DFE34F4872EB027059CECAE30B0C864B3A2 cert=bWUdFUe8io9U6JkSLoGAvSAUDcB779/shovCYmYAQb/pW/iEAMZtO/lCd94OokOF909TPA iat-mode=2
n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cert=t8tA9q2AeGlmp/dO6oW9bkY5RqqmvqjArCEM9wjJoDnk6XtnaejkF0JTA7VamdyOzcvuBg iat-mode=0
n Bridge obfs4 74.104.165.202:9002 EF432018A6AA5D970B2F84E39CD30A147030141C cert=PhppfUusY85dHGvWtGTybZ1fED4DtbHmALkNMIOIYrAz1B4xN7/2a5gyiZe1epju1BOHVg iat-mode=0
n Bridge obfs4 23.88.49.56:443 1CDA1660823AE2565D7F50DE8EB99DFDDE96074B cert=4bwNXedHutVD0ZqCm6ph90Vik9dRY4n9qnBHiLiqQOSsIvui4iHwuMFQK6oqiK8tyhVcDw iat-mode=0
n Bridge obfs4 185.65.206.101:443 8A3E001D4C5105ED41060597DEEB21FF19CDC4D3 cert=Nd6XZ+f00sGKL1u6USmyvfqR34HN/pt7jEVbgMpXPF/yyGaLBiXRH/x0SIjX5TceYnd+Dg iat-mode=0
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com

View File

@@ -0,0 +1,21 @@
#!/bin/bash
set -e
URL="http://127.0.0.1:8000/status"
attempt_counter=0
max_attempts=200 # 10min - CI for mailbox currently takes ~5min
echo "Waiting for mailbox to come online at $URL"
until [[ "$(curl -s -o /dev/null -w '%{http_code}' $URL)" == "401" ]]; do
if [ ${attempt_counter} -eq ${max_attempts} ]; then
echo "Timed out waiting for mailbox"
exit 1
fi
printf '.'
attempt_counter=$((attempt_counter + 1))
sleep 3
done
echo "Mailbox started"

View File

@@ -13,7 +13,7 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageContext;
import org.briarproject.bramble.test.ValidatorTestCase;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.junit.Test;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
@@ -38,7 +38,7 @@ public class BdfMessageValidatorTest extends ValidatorTestCase {
private final Metadata meta = new Metadata();
public BdfMessageValidatorTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
}
@Test(expected = InvalidMessageException.class)

View File

@@ -24,11 +24,10 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.util.StringUtils;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
@@ -53,9 +52,8 @@ import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class ClientHelperImplTest extends BrambleTestCase {
public class ClientHelperImplTest extends BrambleMockTestCase {
private final Mockery context = new Mockery();
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final MessageFactory messageFactory =
context.mock(MessageFactory.class);
@@ -100,7 +98,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
}});
clientHelper.addLocalMessage(message, dictionary, shared);
context.assertIsSatisfied();
}
@Test
@@ -112,7 +109,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
}});
clientHelper.createMessage(groupId, timestamp, list);
context.assertIsSatisfied();
}
@Test
@@ -127,7 +123,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
}});
clientHelper.getMessageAsList(messageId);
context.assertIsSatisfied();
}
@Test
@@ -144,7 +139,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
assertEquals(dictionary,
clientHelper.getGroupMetadataAsDictionary(groupId));
context.assertIsSatisfied();
}
@Test
@@ -161,7 +155,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
assertEquals(dictionary,
clientHelper.getMessageMetadataAsDictionary(messageId));
context.assertIsSatisfied();
}
@Test
@@ -179,7 +172,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
}});
assertEquals(map, clientHelper.getMessageMetadataAsDictionary(groupId));
context.assertIsSatisfied();
}
@Test
@@ -204,7 +196,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
assertEquals(map,
clientHelper.getMessageMetadataAsDictionary(groupId, query));
context.assertIsSatisfied();
}
@Test
@@ -219,7 +210,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
}});
clientHelper.mergeGroupMetadata(groupId, dictionary);
context.assertIsSatisfied();
}
@Test
@@ -234,7 +224,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
}});
clientHelper.mergeMessageMetadata(messageId, dictionary);
context.assertIsSatisfied();
}
@Test
@@ -242,7 +231,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
byte[] bytes = expectToByteArray(list);
assertArrayEquals(bytes, clientHelper.toByteArray(list));
context.assertIsSatisfied();
}
@Test
@@ -250,7 +238,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
expectToList(true);
assertEquals(list, clientHelper.toList(getRandomBytes(123)));
context.assertIsSatisfied();
}
@Test
@@ -262,7 +249,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
fail();
} catch (FormatException e) {
// expected
context.assertIsSatisfied();
}
}
@@ -279,7 +265,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
assertArrayEquals(signature,
clientHelper.sign(label, list, privateKey));
context.assertIsSatisfied();
}
@Test
@@ -295,7 +280,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
}});
clientHelper.verifySignature(signature, label, list, publicKey);
context.assertIsSatisfied();
}
@Test
@@ -315,7 +299,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
fail();
} catch (GeneralSecurityException e) {
// expected
context.assertIsSatisfied();
}
}

View File

@@ -2197,6 +2197,55 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.close();
}
@Test
public void testResetRetransmissionTimes() throws Exception {
long now = System.currentTimeMillis();
AtomicLong time = new AtomicLong(now);
Database<Connection> db =
open(false, new TestMessageFactory(), new SettableClock(time));
Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message
db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), null, true));
db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, false, null);
// Time: now
// Retrieve the message from the database
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
ONE_MEGABYTE, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
// Time: now
// Mark the message as sent
db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY);
// The message should expire after 2 * MAX_LATENCY
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
// Time: now + MAX_LATENCY * 2 - 1
// The message should not yet be sendable
time.set(now + MAX_LATENCY * 2 - 1);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
assertTrue(ids.isEmpty());
// Reset the retransmission times
db.resetUnackedMessagesToSend(txn, contactId);
// The message should have infinitely short expiry
assertEquals(0, db.getNextSendTime(txn, contactId));
// The message should be sendable
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
assertFalse(ids.isEmpty());
db.commitTransaction(txn);
db.close();
}
@Test
public void testCompactionTime() throws Exception {
MessageFactory messageFactory = new TestMessageFactory();

View File

@@ -11,8 +11,9 @@ import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
import org.briarproject.bramble.test.BrambleTestCase;
import org.jmock.Expectations;
import org.jmock.auto.Mock;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.jmock.integration.junit4.JUnitRuleMockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.jmock.lib.concurrent.Synchroniser;
import org.junit.Rule;
import org.junit.Test;
@@ -34,7 +35,8 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
@Rule
public JUnitRuleMockery context = new JUnitRuleMockery() {{
// So we can mock concrete classes like KeyAgreementTransport
setImposteriser(ClassImposteriser.INSTANCE);
setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
setThreadingPolicy(new Synchroniser());
}};
private final PublicKey alicePubKey = getAgreementPublicKey();

View File

@@ -14,7 +14,7 @@ import org.briarproject.bramble.api.record.RecordWriterFactory;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.junit.Test;
import java.io.InputStream;
@@ -58,7 +58,7 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
private KeyAgreementTransport kat;
public KeyAgreementTransportTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
inputStream = context.mock(InputStream.class);
outputStream = context.mock(OutputStream.class);
}

View File

@@ -0,0 +1,383 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.WeakSingletonProvider;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
import javax.net.SocketFactory;
import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getMailboxSecret;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class MailboxApiTest extends BrambleTestCase {
private final OkHttpClient client = new OkHttpClient.Builder()
.socketFactory(SocketFactory.getDefault())
.connectTimeout(60_000, MILLISECONDS)
.build();
private final WeakSingletonProvider<OkHttpClient> httpClientProvider =
new WeakSingletonProvider<OkHttpClient>() {
@Override
@Nonnull
public OkHttpClient createInstance() {
return client;
}
};
private final MailboxApiImpl api = new MailboxApiImpl(httpClientProvider);
private final String token = getMailboxSecret();
private final String token2 = getMailboxSecret();
private final ContactId contactId = getContactId();
private final String contactToken = getMailboxSecret();
private final String contactInboxId = getMailboxSecret();
private final String contactOutboxId = getMailboxSecret();
private final MailboxContact mailboxContact = new MailboxContact(
contactId, contactToken, contactInboxId, contactOutboxId);
@Test
public void testSetup() throws Exception {
String validResponse = "{\"token\":\"" + token2 + "\"}";
String invalidResponse = "{\"foo\":\"bar\"}";
String invalidTokenResponse = "{\"token\":{\"foo\":\"bar\"}}";
String invalidTokenResponse2 =
"{\"token\":\"" + getRandomString(64) + "\"}";
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setBody(validResponse));
server.enqueue(new MockResponse());
server.enqueue(new MockResponse().setBody(invalidResponse));
server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(500));
server.enqueue(new MockResponse().setBody(invalidTokenResponse));
server.enqueue(new MockResponse().setBody(invalidTokenResponse2));
server.start();
String baseUrl = getBaseUrl(server);
MailboxProperties properties =
new MailboxProperties(baseUrl, token, true);
MailboxProperties properties2 =
new MailboxProperties(baseUrl, token2, true);
// valid response with valid token
assertEquals(token2, api.setup(properties));
RecordedRequest request1 = server.takeRequest();
assertEquals("/setup", request1.getPath());
assertEquals("PUT", request1.getMethod());
assertToken(request1, token);
// empty body
assertThrows(ApiException.class, () -> api.setup(properties));
RecordedRequest request2 = server.takeRequest();
assertEquals("/setup", request2.getPath());
assertEquals("PUT", request2.getMethod());
assertToken(request2, token);
// invalid response
assertThrows(ApiException.class, () -> api.setup(properties));
RecordedRequest request3 = server.takeRequest();
assertEquals("/setup", request3.getPath());
assertEquals("PUT", request3.getMethod());
assertToken(request3, token);
// 401 response
assertThrows(ApiException.class, () -> api.setup(properties2));
RecordedRequest request4 = server.takeRequest();
assertEquals("/setup", request4.getPath());
assertEquals("PUT", request4.getMethod());
assertToken(request4, token2);
// 500 response
assertThrows(ApiException.class, () -> api.setup(properties));
RecordedRequest request5 = server.takeRequest();
assertEquals("/setup", request5.getPath());
assertEquals("PUT", request5.getMethod());
assertToken(request5, token);
// invalid json dict token response
assertThrows(ApiException.class, () -> api.setup(properties));
RecordedRequest request6 = server.takeRequest();
assertEquals("/setup", request6.getPath());
assertEquals("PUT", request6.getMethod());
assertToken(request6, token);
// invalid non-hex string token response
assertThrows(ApiException.class, () -> api.setup(properties));
RecordedRequest request7 = server.takeRequest();
assertEquals("/setup", request7.getPath());
assertEquals("PUT", request7.getMethod());
assertToken(request7, token);
}
@Test
public void testSetupOnlyForOwner() {
MailboxProperties properties =
new MailboxProperties("", token, false);
assertThrows(
IllegalArgumentException.class,
() -> api.setup(properties)
);
}
@Test
public void testStatus() throws Exception {
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse());
server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(500));
server.start();
String baseUrl = getBaseUrl(server);
MailboxProperties properties =
new MailboxProperties(baseUrl, token, true);
MailboxProperties properties2 =
new MailboxProperties(baseUrl, token2, true);
assertTrue(api.checkStatus(properties));
RecordedRequest request1 = server.takeRequest();
assertEquals("/status", request1.getPath());
assertToken(request1, token);
assertThrows(ApiException.class, () -> api.checkStatus(properties2));
RecordedRequest request2 = server.takeRequest();
assertEquals("/status", request2.getPath());
assertToken(request2, token2);
assertFalse(api.checkStatus(properties));
RecordedRequest request3 = server.takeRequest();
assertEquals("/status", request3.getPath());
assertToken(request3, token);
}
@Test
public void testStatusOnlyForOwner() {
MailboxProperties properties =
new MailboxProperties("", token, false);
assertThrows(
IllegalArgumentException.class,
() -> api.checkStatus(properties)
);
}
@Test
public void testAddContact() throws Exception {
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse());
server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(409));
server.start();
String baseUrl = getBaseUrl(server);
MailboxProperties properties =
new MailboxProperties(baseUrl, token, true);
// contact gets added as expected
api.addContact(properties, mailboxContact);
RecordedRequest request1 = server.takeRequest();
assertEquals("/contacts", request1.getPath());
assertToken(request1, token);
String expected = "{\"contactId\":" + contactId.getInt() +
",\"token\":\"" + contactToken +
"\",\"inboxId\":\"" + contactInboxId +
"\",\"outboxId\":\"" + contactOutboxId +
"\"}";
assertEquals(expected, request1.getBody().readUtf8());
// request is not successful
assertThrows(ApiException.class, () ->
api.addContact(properties, mailboxContact));
RecordedRequest request2 = server.takeRequest();
assertEquals("/contacts", request2.getPath());
assertToken(request2, token);
// contact already exists
assertThrows(TolerableFailureException.class, () ->
api.addContact(properties, mailboxContact));
RecordedRequest request3 = server.takeRequest();
assertEquals("/contacts", request3.getPath());
assertToken(request3, token);
}
@Test
public void testAddContactOnlyForOwner() {
MailboxProperties properties =
new MailboxProperties("", token, false);
assertThrows(IllegalArgumentException.class, () ->
api.addContact(properties, mailboxContact));
}
@Test
public void testDeleteContact() throws Exception {
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse());
server.enqueue(new MockResponse().setResponseCode(205));
server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(404));
server.start();
String baseUrl = getBaseUrl(server);
MailboxProperties properties =
new MailboxProperties(baseUrl, token, true);
// contact gets deleted as expected
api.deleteContact(properties, contactId);
RecordedRequest request1 = server.takeRequest();
assertEquals("DELETE", request1.getMethod());
assertEquals("/contacts/" + contactId.getInt(), request1.getPath());
assertToken(request1, token);
// request is not returning 200
assertThrows(ApiException.class, () ->
api.deleteContact(properties, contactId));
RecordedRequest request2 = server.takeRequest();
assertEquals("DELETE", request2.getMethod());
assertEquals("/contacts/" + contactId.getInt(), request2.getPath());
assertToken(request2, token);
// request is not authorized
assertThrows(ApiException.class, () ->
api.deleteContact(properties, contactId));
RecordedRequest request3 = server.takeRequest();
assertEquals("DELETE", request3.getMethod());
assertEquals("/contacts/" + contactId.getInt(), request3.getPath());
assertToken(request3, token);
// tolerable 404 not found error
assertThrows(TolerableFailureException.class,
() -> api.deleteContact(properties, contactId));
RecordedRequest request4 = server.takeRequest();
assertEquals("/contacts/" + contactId.getInt(), request4.getPath());
assertEquals("DELETE", request4.getMethod());
assertToken(request4, token);
}
@Test
public void testDeleteContactOnlyForOwner() {
MailboxProperties properties =
new MailboxProperties("", token, false);
assertThrows(IllegalArgumentException.class, () ->
api.deleteContact(properties, contactId));
}
@Test
public void testGetContacts() throws Exception {
ContactId contactId2 = getContactId();
String validResponse1 = "{\"contacts\": [" + contactId.getInt() + "] }";
String validResponse2 = "{\"contacts\": [" + contactId.getInt() + ", " +
contactId2.getInt() + "] }";
String invalidResponse1 = "{\"foo\":\"bar\"}";
String invalidResponse2 = "{\"contacts\":{\"foo\":\"bar\"}}";
String invalidResponse3 = "{\"contacts\": [1, 2, \"foo\"] }";
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setBody(validResponse1));
server.enqueue(new MockResponse().setBody(validResponse2));
server.enqueue(new MockResponse());
server.enqueue(new MockResponse().setBody(invalidResponse1));
server.enqueue(new MockResponse().setBody(invalidResponse2));
server.enqueue(new MockResponse().setBody(invalidResponse3));
server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(500));
server.start();
String baseUrl = getBaseUrl(server);
MailboxProperties properties =
new MailboxProperties(baseUrl, token, true);
// valid response with two contacts
assertEquals(singletonList(contactId), api.getContacts(properties));
RecordedRequest request1 = server.takeRequest();
assertEquals("/contacts", request1.getPath());
assertEquals("GET", request1.getMethod());
assertToken(request1, token);
// valid response with two contacts
List<ContactId> contacts = new ArrayList<>();
contacts.add(contactId);
contacts.add(contactId2);
assertEquals(contacts, api.getContacts(properties));
RecordedRequest request2 = server.takeRequest();
assertEquals("/contacts", request2.getPath());
assertEquals("GET", request2.getMethod());
assertToken(request2, token);
// empty body
assertThrows(ApiException.class, () -> api.getContacts(properties));
RecordedRequest request3 = server.takeRequest();
assertEquals("/contacts", request3.getPath());
assertEquals("GET", request3.getMethod());
assertToken(request3, token);
// invalid response: no contacts key
assertThrows(ApiException.class, () -> api.getContacts(properties));
RecordedRequest request4 = server.takeRequest();
assertEquals("/contacts", request4.getPath());
assertEquals("GET", request4.getMethod());
assertToken(request4, token);
// invalid response: no list in contacts
assertThrows(ApiException.class, () -> api.getContacts(properties));
RecordedRequest request5 = server.takeRequest();
assertEquals("/contacts", request5.getPath());
assertEquals("GET", request5.getMethod());
assertToken(request5, token);
// invalid response: list with non-numbers
assertThrows(ApiException.class, () -> api.getContacts(properties));
RecordedRequest request6 = server.takeRequest();
assertEquals("/contacts", request6.getPath());
assertEquals("GET", request6.getMethod());
assertToken(request6, token);
// 401 not authorized
assertThrows(ApiException.class, () -> api.getContacts(properties));
RecordedRequest request7 = server.takeRequest();
assertEquals("/contacts", request7.getPath());
assertEquals("GET", request7.getMethod());
assertToken(request7, token);
// 500 internal server error
assertThrows(ApiException.class, () -> api.getContacts(properties));
RecordedRequest request8 = server.takeRequest();
assertEquals("/contacts", request8.getPath());
assertEquals("GET", request8.getMethod());
assertToken(request8, token);
}
@Test
public void testGetContactsOnlyForOwner() {
MailboxProperties properties =
new MailboxProperties("", token, false);
assertThrows(
IllegalArgumentException.class,
() -> api.getContacts(properties)
);
}
private String getBaseUrl(MockWebServer server) {
String baseUrl = server.url("").toString();
return baseUrl.substring(0, baseUrl.length() - 1);
}
private void assertToken(RecordedRequest request, String token) {
assertNotNull(request.getHeader("Authorization"));
assertEquals("Bearer " + token, request.getHeader("Authorization"));
}
}

View File

@@ -0,0 +1,109 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.WeakSingletonProvider;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.util.Arrays;
import javax.annotation.Nonnull;
import javax.net.SocketFactory;
import okhttp3.OkHttpClient;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.test.TestUtils.getMailboxSecret;
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
public class MailboxIntegrationTest extends BrambleTestCase {
private final static String URL_BASE = "http://127.0.0.1:8000";
private final static String SETUP_TOKEN =
"54686973206973206120736574757020746f6b656e20666f722042726961722e";
private final OkHttpClient client = new OkHttpClient.Builder()
.socketFactory(SocketFactory.getDefault())
.connectTimeout(60_000, MILLISECONDS)
.build();
private final WeakSingletonProvider<OkHttpClient> httpClientProvider =
new WeakSingletonProvider<OkHttpClient>() {
@Override
@Nonnull
public OkHttpClient createInstance() {
return client;
}
};
private final MailboxApiImpl api = new MailboxApiImpl(httpClientProvider);
// needs to be static to keep values across different tests
private static MailboxProperties ownerProperties;
/**
* Called before each test to make sure the mailbox is setup once
* before starting with individual tests.
* {@link BeforeClass} needs to be static, so we can't use the API class.
*/
@Before
public void ensureSetup() throws IOException, ApiException {
// Skip this test unless it's explicitly enabled in the environment
assumeTrue(isOptionalTestEnabled(MailboxIntegrationTest.class));
if (ownerProperties != null) return;
MailboxProperties setupProperties =
new MailboxProperties(URL_BASE, SETUP_TOKEN, true);
String ownerToken = api.setup(setupProperties);
ownerProperties = new MailboxProperties(URL_BASE, ownerToken, true);
}
@Test
public void testStatus() throws Exception {
assertTrue(api.checkStatus(ownerProperties));
}
@Test
public void testContactApi() throws Exception {
ContactId contactId1 = new ContactId(1);
ContactId contactId2 = new ContactId(2);
MailboxContact mailboxContact1 = getMailboxContact(contactId1);
MailboxContact mailboxContact2 = getMailboxContact(contactId2);
// no contacts initially
assertEquals(emptyList(), api.getContacts(ownerProperties));
// added contact gets returned
api.addContact(ownerProperties, mailboxContact1);
assertEquals(singletonList(contactId1),
api.getContacts(ownerProperties));
// second contact also gets returned
api.addContact(ownerProperties, mailboxContact2);
assertEquals(Arrays.asList(contactId1, contactId2),
api.getContacts(ownerProperties));
// after both contacts get deleted, the list is empty again
api.deleteContact(ownerProperties, contactId1);
api.deleteContact(ownerProperties, contactId2);
assertEquals(emptyList(), api.getContacts(ownerProperties));
// deleting again is tolerable
assertThrows(TolerableFailureException.class,
() -> api.deleteContact(ownerProperties, contactId2));
}
private MailboxContact getMailboxContact(ContactId contactId) {
return new MailboxContact(contactId, getMailboxSecret(),
getMailboxSecret(), getMailboxSecret());
}
}

View File

@@ -0,0 +1,225 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.junit.Test;
import java.util.Random;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ATTEMPTS;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_ATTEMPT;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_SUCCESS;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ONION;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_TOKEN;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_NAMESPACE;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_UPLOADS_NAMESPACE;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
private final SettingsManager settingsManager =
context.mock(SettingsManager.class);
private final MailboxSettingsManager manager =
new MailboxSettingsManagerImpl(settingsManager);
private final Random random = new Random();
private final String onion = getRandomString(64);
private final String token = getRandomString(64);
private final ContactId contactId1 = new ContactId(random.nextInt());
private final ContactId contactId2 = new ContactId(random.nextInt());
private final ContactId contactId3 = new ContactId(random.nextInt());
private final long now = System.currentTimeMillis();
private final long lastAttempt = now - 1234;
private final long lastSuccess = now - 2345;
private final int attempts = 123;
@Test
public void testReturnsNullPropertiesIfSettingsAreEmpty() throws Exception {
Transaction txn = new Transaction(null, true);
Settings emptySettings = new Settings();
context.checking(new Expectations() {{
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
will(returnValue(emptySettings));
}});
assertNull(manager.getOwnMailboxProperties(txn));
}
@Test
public void testReturnsProperties() throws Exception {
Transaction txn = new Transaction(null, true);
Settings settings = new Settings();
settings.put(SETTINGS_KEY_ONION, onion);
settings.put(SETTINGS_KEY_TOKEN, token);
context.checking(new Expectations() {{
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
will(returnValue(settings));
}});
MailboxProperties properties = manager.getOwnMailboxProperties(txn);
assertNotNull(properties);
assertEquals(onion, properties.getOnionAddress());
assertEquals(token, properties.getAuthToken());
assertTrue(properties.isOwner());
}
@Test
public void testStoresProperties() throws Exception {
Transaction txn = new Transaction(null, false);
Settings expectedSettings = new Settings();
expectedSettings.put(SETTINGS_KEY_ONION, onion);
expectedSettings.put(SETTINGS_KEY_TOKEN, token);
MailboxProperties properties =
new MailboxProperties(onion, token, true);
context.checking(new Expectations() {{
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
SETTINGS_NAMESPACE);
}});
manager.setOwnMailboxProperties(txn, properties);
}
@Test
public void testReturnsDefaultStatusIfSettingsAreEmpty() throws Exception {
Transaction txn = new Transaction(null, true);
Settings emptySettings = new Settings();
context.checking(new Expectations() {{
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
will(returnValue(emptySettings));
}});
MailboxStatus status = manager.getOwnMailboxStatus(txn);
assertEquals(-1, status.getTimeOfLastAttempt());
assertEquals(-1, status.getTimeOfLastSuccess());
assertEquals(0, status.getAttemptsSinceSuccess());
}
@Test
public void testReturnsStatus() throws Exception {
Transaction txn = new Transaction(null, true);
Settings settings = new Settings();
settings.putLong(SETTINGS_KEY_LAST_ATTEMPT, lastAttempt);
settings.putLong(SETTINGS_KEY_LAST_SUCCESS, lastSuccess);
settings.putInt(SETTINGS_KEY_ATTEMPTS, attempts);
context.checking(new Expectations() {{
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
will(returnValue(settings));
}});
MailboxStatus status = manager.getOwnMailboxStatus(txn);
assertEquals(lastAttempt, status.getTimeOfLastAttempt());
assertEquals(lastSuccess, status.getTimeOfLastSuccess());
assertEquals(attempts, status.getAttemptsSinceSuccess());
}
@Test
public void testRecordsSuccess() throws Exception {
Transaction txn = new Transaction(null, false);
Settings expectedSettings = new Settings();
expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
expectedSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
expectedSettings.putInt(SETTINGS_KEY_ATTEMPTS, 0);
context.checking(new Expectations() {{
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
SETTINGS_NAMESPACE);
}});
manager.recordSuccessfulConnection(txn, now);
}
@Test
public void testRecordsFailureOnFirstAttempt() throws Exception {
testRecordsFailure(new Settings(), 0);
}
@Test
public void testRecordsFailureOnLaterAttempt() throws Exception {
Settings oldSettings = new Settings();
oldSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, lastAttempt);
oldSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, lastSuccess);
oldSettings.putInt(SETTINGS_KEY_ATTEMPTS, attempts);
testRecordsFailure(oldSettings, attempts);
}
private void testRecordsFailure(Settings oldSettings, int oldAttempts)
throws Exception {
Transaction txn = new Transaction(null, false);
Settings expectedSettings = new Settings();
expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
expectedSettings.putInt(SETTINGS_KEY_ATTEMPTS, oldAttempts + 1);
context.checking(new Expectations() {{
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
will(returnValue(oldSettings));
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
SETTINGS_NAMESPACE);
}});
manager.recordFailedConnectionAttempt(txn, now);
}
@Test
public void testGettingPendingUploads() throws Exception {
Transaction txn = new Transaction(null, true);
Settings settings = new Settings();
settings.put(String.valueOf(contactId1.getInt()), onion);
settings.put(String.valueOf(contactId2.getInt()), token);
settings.put(String.valueOf(contactId3.getInt()), "");
context.checking(new Expectations() {{
exactly(4).of(settingsManager)
.getSettings(txn, SETTINGS_UPLOADS_NAMESPACE);
will(returnValue(settings));
}});
String filename1 = manager.getPendingUpload(txn, contactId1);
assertEquals(onion, filename1);
String filename2 = manager.getPendingUpload(txn, contactId2);
assertEquals(token, filename2);
String filename3 = manager.getPendingUpload(txn, contactId3);
assertNull(filename3);
String filename4 =
manager.getPendingUpload(txn, new ContactId(random.nextInt()));
assertNull(filename4);
}
@Test
public void testSettingPendingUploads() throws Exception {
Transaction txn = new Transaction(null, false);
// setting a pending upload stores expected settings
Settings expectedSettings1 = new Settings();
expectedSettings1.put(String.valueOf(contactId1.getInt()), onion);
context.checking(new Expectations() {{
oneOf(settingsManager).mergeSettings(txn, expectedSettings1,
SETTINGS_UPLOADS_NAMESPACE);
}});
manager.setPendingUpload(txn, contactId1, onion);
// nulling a pending upload empties stored settings
Settings expectedSettings2 = new Settings();
expectedSettings2.put(String.valueOf(contactId2.getInt()), "");
context.checking(new Expectations() {{
oneOf(settingsManager).mergeSettings(txn, expectedSettings2,
SETTINGS_UPLOADS_NAMESPACE);
}});
manager.setPendingUpload(txn, contactId2, null);
}
}

View File

@@ -12,10 +12,8 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.lib.concurrent.Synchroniser;
import org.junit.Test;
import java.util.Arrays;
@@ -24,13 +22,10 @@ import java.util.concurrent.Executors;
import static org.briarproject.bramble.test.TestUtils.getTransportId;
public class PluginManagerImplTest extends BrambleTestCase {
public class PluginManagerImplTest extends BrambleMockTestCase {
@Test
public void testStartAndStop() throws Exception {
Mockery context = new Mockery() {{
setThreadingPolicy(new Synchroniser());
}};
Executor ioExecutor = Executors.newSingleThreadExecutor();
EventBus eventBus = context.mock(EventBus.class);
PluginConfig pluginConfig = context.mock(PluginConfig.class);
@@ -116,7 +111,5 @@ public class PluginManagerImplTest extends BrambleTestCase {
// Two plugins should be started and stopped
p.startService();
p.stopService();
context.assertIsSatisfied();
}
}

View File

@@ -25,7 +25,7 @@ import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.ImmediateExecutor;
import org.briarproject.bramble.test.RunAction;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.junit.Before;
import org.junit.Test;
@@ -69,7 +69,7 @@ public class PollerImplTest extends BrambleMockTestCase {
private PollerImpl poller;
public PollerImplTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
random = context.mock(SecureRandom.class);
}

View File

@@ -0,0 +1,68 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import static java.util.Arrays.asList;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BLOCKED;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BRIDGES;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.DEFAULT_OBFS4_BRIDGES;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.MEEK_BRIDGES;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.NON_DEFAULT_OBFS4_BRIDGES;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class CircumventionProviderTest extends BrambleTestCase {
private final CircumventionProvider provider =
new CircumventionProviderImpl();
@Test
public void testInvariants() {
Set<String> blocked = new HashSet<>(asList(BLOCKED));
Set<String> bridges = new HashSet<>(asList(BRIDGES));
Set<String> defaultObfs4Bridges =
new HashSet<>(asList(DEFAULT_OBFS4_BRIDGES));
Set<String> nonDefaultObfs4Bridges =
new HashSet<>(asList(NON_DEFAULT_OBFS4_BRIDGES));
Set<String> meekBridges = new HashSet<>(asList(MEEK_BRIDGES));
// BRIDGES should be a subset of BLOCKED
assertTrue(blocked.containsAll(bridges));
// BRIDGES should be the union of the bridge type sets
Set<String> union = new HashSet<>(defaultObfs4Bridges);
union.addAll(nonDefaultObfs4Bridges);
union.addAll(meekBridges);
assertEquals(bridges, union);
// The bridge type sets should not overlap
assertEmptyIntersection(defaultObfs4Bridges, nonDefaultObfs4Bridges);
assertEmptyIntersection(defaultObfs4Bridges, meekBridges);
assertEmptyIntersection(nonDefaultObfs4Bridges, meekBridges);
}
@Test
public void testGetBestBridgeType() {
for (String country : DEFAULT_OBFS4_BRIDGES) {
assertEquals(DEFAULT_OBFS4, provider.getBestBridgeType(country));
}
for (String country : NON_DEFAULT_OBFS4_BRIDGES) {
assertEquals(NON_DEFAULT_OBFS4,
provider.getBestBridgeType(country));
}
for (String country : MEEK_BRIDGES) {
assertEquals(MEEK, provider.getBestBridgeType(country));
}
assertEquals(DEFAULT_OBFS4, provider.getBestBridgeType("ZZ"));
}
private <T> void assertEmptyIntersection(Set<T> a, Set<T> b) {
Set<T> intersection = new HashSet<>(a);
intersection.retainAll(b);
assertTrue(intersection.isEmpty());
}
}

View File

@@ -24,6 +24,21 @@ public class TestFeatureFlagModule {
public boolean shouldEnableDisappearingMessages() {
return true;
}
@Override
public boolean shouldEnablePrivateGroupsInCore() {
return true;
}
@Override
public boolean shouldEnableForumsInCore() {
return true;
}
@Override
public boolean shouldEnableBlogsInCore() {
return true;
}
};
}
}

View File

@@ -1,19 +1,17 @@
package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.crypto.StreamDecrypter;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.junit.Assert.assertEquals;
public class StreamReaderImplTest extends BrambleTestCase {
public class StreamReaderImplTest extends BrambleMockTestCase {
@Test
public void testEmptyFramesAreSkipped() throws Exception {
Mockery context = new Mockery();
StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
context.checking(new Expectations() {{
oneOf(decrypter).readFrame(with(any(byte[].class)));
@@ -30,13 +28,11 @@ public class StreamReaderImplTest extends BrambleTestCase {
assertEquals(0, r.read()); // Read another byte
assertEquals(-1, r.read()); // Skip the second empty frame, reach EOF
assertEquals(-1, r.read()); // Still at EOF
context.assertIsSatisfied();
r.close();
}
@Test
public void testEmptyFramesAreSkippedWithBuffer() throws Exception {
Mockery context = new Mockery();
StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
context.checking(new Expectations() {{
oneOf(decrypter).readFrame(with(any(byte[].class)));
@@ -56,13 +52,11 @@ public class StreamReaderImplTest extends BrambleTestCase {
assertEquals(-1, r.read(buf));
// Still at EOF
assertEquals(-1, r.read(buf));
context.assertIsSatisfied();
r.close();
}
@Test
public void testMultipleReadsPerFrame() throws Exception {
Mockery context = new Mockery();
StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
context.checking(new Expectations() {{
oneOf(decrypter).readFrame(with(any(byte[].class)));
@@ -78,13 +72,11 @@ public class StreamReaderImplTest extends BrambleTestCase {
assertEquals(MAX_PAYLOAD_LENGTH / 2, r.read(buf));
// Reach EOF
assertEquals(-1, r.read(buf, 0, buf.length));
context.assertIsSatisfied();
r.close();
}
@Test
public void testMultipleReadsPerFrameWithOffsets() throws Exception {
Mockery context = new Mockery();
StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
context.checking(new Expectations() {{
oneOf(decrypter).readFrame(with(any(byte[].class)));
@@ -102,7 +94,6 @@ public class StreamReaderImplTest extends BrambleTestCase {
MAX_PAYLOAD_LENGTH / 2));
// Reach EOF
assertEquals(-1, r.read(buf, 0, buf.length));
context.assertIsSatisfied();
r.close();
}
}

View File

@@ -1,19 +1,17 @@
package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.crypto.StreamEncrypter;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.junit.Assert.assertEquals;
public class StreamWriterImplTest extends BrambleTestCase {
public class StreamWriterImplTest extends BrambleMockTestCase {
@Test
public void testCloseWithoutWritingWritesFinalFrame() throws Exception {
Mockery context = new Mockery();
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
context.checking(new Expectations() {{
// Write an empty final frame
@@ -24,13 +22,11 @@ public class StreamWriterImplTest extends BrambleTestCase {
}});
StreamWriterImpl w = new StreamWriterImpl(encrypter);
w.close();
context.assertIsSatisfied();
}
@Test
public void testFlushWithoutBufferedDataWritesFrameAndFlushes()
throws Exception {
Mockery context = new Mockery();
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
StreamWriterImpl w = new StreamWriterImpl(encrypter);
context.checking(new Expectations() {{
@@ -51,13 +47,11 @@ public class StreamWriterImplTest extends BrambleTestCase {
oneOf(encrypter).flush();
}});
w.close();
context.assertIsSatisfied();
}
@Test
public void testFlushWithBufferedDataWritesFrameAndFlushes()
throws Exception {
Mockery context = new Mockery();
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
StreamWriterImpl w = new StreamWriterImpl(encrypter);
context.checking(new Expectations() {{
@@ -79,12 +73,10 @@ public class StreamWriterImplTest extends BrambleTestCase {
oneOf(encrypter).flush();
}});
w.close();
context.assertIsSatisfied();
}
@Test
public void testSingleByteWritesWriteFullFrame() throws Exception {
Mockery context = new Mockery();
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
StreamWriterImpl w = new StreamWriterImpl(encrypter);
context.checking(new Expectations() {{
@@ -103,12 +95,10 @@ public class StreamWriterImplTest extends BrambleTestCase {
oneOf(encrypter).flush();
}});
w.close();
context.assertIsSatisfied();
}
@Test
public void testMultiByteWritesWriteFullFrames() throws Exception {
Mockery context = new Mockery();
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
StreamWriterImpl w = new StreamWriterImpl(encrypter);
context.checking(new Expectations() {{
@@ -134,12 +124,10 @@ public class StreamWriterImplTest extends BrambleTestCase {
oneOf(encrypter).flush();
}});
w.close();
context.assertIsSatisfied();
}
@Test
public void testLargeMultiByteWriteWritesFullFrames() throws Exception {
Mockery context = new Mockery();
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
StreamWriterImpl w = new StreamWriterImpl(encrypter);
context.checking(new Expectations() {{
@@ -157,6 +145,5 @@ public class StreamWriterImplTest extends BrambleTestCase {
w.write(b);
// There should be one byte left in the buffer
w.close();
context.assertIsSatisfied();
}
}

View File

@@ -393,6 +393,76 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
assertNull(transportKeyManager.getStreamContext(txn, tag));
}
@Test
public void testGetStreamContextOnlyAndMarkTag() throws Exception {
boolean alice = random.nextBoolean();
TransportKeys transportKeys = createTransportKeys(1000, 0, true);
Transaction txn = new Transaction(null, false);
// Keep a copy of the tags
List<byte[]> tags = new ArrayList<>();
context.checking(new Expectations() {{
oneOf(transportCrypto).deriveRotationKeys(transportId, rootKey,
1000, alice, true);
will(returnValue(transportKeys));
// Get the current time (the start of time period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(timePeriodLength * 1000));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(transportCrypto).encodeTag(
with(any(byte[].class)), with(tagKey),
with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction(tags));
}
// Updated the transport keys (the keys are unaffected)
oneOf(transportCrypto).updateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys));
// Save the keys
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
will(returnValue(keySetId));
// Encode a new tag after sliding the window
oneOf(transportCrypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(PROTOCOL_VERSION),
with((long) REORDERING_WINDOW_SIZE));
will(new EncodeTagAction(tags));
// Save the reordering window (previous time period, base 1)
oneOf(db).setReorderingWindow(txn, keySetId, transportId, 999,
1, new byte[REORDERING_WINDOW_SIZE / 8]);
}});
// The timestamp is at the start of time period 1000
long timestamp = timePeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addRotationKeys(
txn, contactId, rootKey, timestamp, alice, true));
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
// Use the first tag (previous time period, stream number 0)
assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size());
byte[] tag = tags.get(0);
// Repeated request should return same stream context
StreamContext ctx = transportKeyManager.getStreamContextOnly(txn, tag);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertEquals(tagKey, ctx.getTagKey());
assertEquals(headerKey, ctx.getHeaderKey());
assertEquals(0L, ctx.getStreamNumber());
ctx = transportKeyManager.getStreamContextOnly(txn, tag);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertEquals(tagKey, ctx.getTagKey());
assertEquals(headerKey, ctx.getHeaderKey());
assertEquals(0L, ctx.getStreamNumber());
// Then mark tag as recognised
transportKeyManager.markTagAsRecognised(txn, tag);
// Another tag should have been encoded
assertEquals(REORDERING_WINDOW_SIZE * 3 + 1, tags.size());
// Finally ensure the used tag is not recognised again
assertNull(transportKeyManager.getStreamContextOnly(txn, tag));
}
@Test
public void testKeysAreUpdatedToCurrentPeriod() throws Exception {
TransportKeys transportKeys = createTransportKeys(1000, 0, true);

View File

@@ -1,6 +1,9 @@
dependencyVerification {
verify = [
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
'com.fasterxml.jackson.core:jackson-annotations:2.13.0:jackson-annotations-2.13.0.jar:81f9724d8843e8b08f8f6c0609e7a2b030d00c34861c4ac7e2099a7235047d6f',
'com.fasterxml.jackson.core:jackson-core:2.13.0:jackson-core-2.13.0.jar:348bc59b348df2e807b356f1d62d2afb41a974073328abc773eb0932b855d2c8',
'com.fasterxml.jackson.core:jackson-databind:2.13.0:jackson-databind-2.13.0.jar:9c826d27176268777adcf97e1c6e2051c7e33a7aaa2c370c2e8c6077fd9da3f4',
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.dagger:dagger-compiler:2.33:dagger-compiler-2.33.jar:aa8a0d8370c578fd6999802d0d90b9829377a46d2c1141e11b8f737970e7155e',
@@ -15,6 +18,11 @@ dependencyVerification {
'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99',
'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6',
'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
'com.squareup.okhttp3:mockwebserver:4.9.3:mockwebserver-4.9.3.jar:9c8c581c29f22f877a35d11380462f75bb24bf1886204fe835ee695594a2784e',
'com.squareup.okhttp3:okhttp:3.12.13:okhttp-3.12.13.jar:508234e024ef7e270ab1a6d5b356f5b98e786511239ca986d684fd1e2cf7bc82',
'com.squareup.okhttp3:okhttp:4.9.3:okhttp-4.9.3.jar:93ecd6cba19d87dccfe566ec848d91aae799e3cf16c00709358ea69bd9227219',
'com.squareup.okio:okio:1.15.0:okio-1.15.0.jar:693fa319a7e8843300602b204023b7674f106ebcb577f2dd5807212b66118bd2',
'com.squareup.okio:okio:2.8.0:okio-jvm-2.8.0.jar:4496b06e73982fcdd8a5393f46e5df2ce2fa4465df5895454cac68a32f09bbc8',
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
@@ -26,7 +34,7 @@ dependencyVerification {
'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.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
'org.bouncycastle:bcprov-jdk15on:1.69:bcprov-jdk15on-1.69.jar:e469bd39f936999f256002631003ff022a22951da9d5bd9789c7abfa9763a292',
'org.bouncycastle:bcprov-jdk15to18:1.70:bcprov-jdk15to18-1.70.jar:7df4c54f29ce2dd616dc3b198ca4db3dfcc79e3cb397c084a0aff97b85c0bf38',
'org.briarproject:jtorctl:0.3:jtorctl-0.3.jar:f2939238a097898998432effe93b0334d97a787972ab3a91a8973a1d309fc864',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
@@ -38,7 +46,11 @@ dependencyVerification {
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
'org.hsqldb:hsqldb:2.3.5:hsqldb-2.3.5.jar:6676a6977ac98997a80f827ddbd3fe8ca1e0853dad1492512135fd1a222ccfad',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10:kotlin-stdlib-common-1.4.10.jar:4681f2d436a68c7523595d84ed5758e1382f9da0f67c91e6a848690d711274fe',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20:kotlin-stdlib-common-1.4.20.jar:a7112c9b3cefee418286c9c9372f7af992bd1e6e030691d52f60cb36dbec8320',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10:kotlin-stdlib-jdk7-1.4.10.jar:f9566380c08722c780ce33ceee23e98ddf765ca98fabd3e2fabae7975c8d232b',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10:kotlin-stdlib-jdk8-1.4.10.jar:39b7a9442d7a3865e0f4a732c56c1d5da0e11ffb3bb82a461d32deb0c0ca7673',
'org.jetbrains.kotlin:kotlin-stdlib:1.4.10:kotlin-stdlib-1.4.10.jar:01ecb09782c042b931c1839acf21a188340b295d05400afd6e3415d4475b8daa',
'org.jetbrains.kotlin:kotlin-stdlib:1.4.20:kotlin-stdlib-1.4.20.jar:b8ab1da5cdc89cb084d41e1f28f20a42bd431538642a5741c52bbfae3fa3e656',
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0:kotlinx-metadata-jvm-0.1.0.jar:9753bb39efef35957c5c15df9a3cb769aabf2cdfa74b47afcb7760e5146be3b5',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',

View File

@@ -17,8 +17,8 @@ dependencies {
def jna_version = '4.5.2'
implementation "net.java.dev.jna:jna:$jna_version"
implementation "net.java.dev.jna:jna-platform:$jna_version"
tor 'org.briarproject:tor:0.3.5.15'
tor 'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a@zip'
tor fileTree(dir: 'libs', include: '*.zip')
tor "org.briarproject:obfs4proxy:$obfs4proxy_version@zip"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
@@ -27,7 +27,6 @@ dependencies {
testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-legacy:$jmock_version"
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
}

Binary file not shown.

View File

@@ -32,13 +32,14 @@ import javax.net.SocketFactory;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.OsUtils.isLinux;
import static org.briarproject.bramble.util.OsUtils.isWindows;
@Immutable
@NotNullByDefault
public class UnixTorPluginFactory implements DuplexPluginFactory {
public class DesktopTorPluginFactory implements DuplexPluginFactory {
private static final Logger LOG =
getLogger(UnixTorPluginFactory.class.getName());
getLogger(DesktopTorPluginFactory.class.getName());
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
@@ -62,7 +63,7 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
private final CryptoComponent crypto;
@Inject
UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
DesktopTorPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
@@ -107,25 +108,33 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
@Override
public DuplexPlugin createPlugin(PluginCallback callback) {
// Check that we have a Tor binary for this architecture
String architecture = null;
if (isLinux()) {
String arch = System.getProperty("os.arch");
if (LOG.isLoggable(INFO)) {
LOG.info("System's os.arch is " + arch);
}
if (arch.equals("amd64")) {
architecture = "linux-x86_64";
return createUnixPlugin(callback, "linux-x86_64");
} else if (arch.equals("aarch64")) {
architecture = "linux-aarch64";
return createUnixPlugin(callback, "linux-aarch64");
} else if (arch.equals("arm")) {
architecture = "linux-armhf";
return createUnixPlugin(callback, "linux-armhf");
}
}
if (architecture == null) {
LOG.info("Tor is not supported on this architecture");
return null;
if (isWindows()) {
String arch = System.getProperty("os.arch");
if (LOG.isLoggable(INFO)) {
LOG.info("System's os.arch is " + arch);
}
if (arch.equals("amd64")) {
return createWindowsPlugin(callback, "windows-x86_64");
}
}
LOG.info("Tor is not supported on this architecture");
return null;
}
private DuplexPlugin createUnixPlugin(PluginCallback callback, String architecture) {
if (LOG.isLoggable(INFO)) {
LOG.info("The selected architecture for Tor is " + architecture);
}
@@ -143,4 +152,21 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
eventBus.addListener(plugin);
return plugin;
}
private DuplexPlugin createWindowsPlugin(PluginCallback callback, String architecture) {
if (LOG.isLoggable(INFO)) {
LOG.info("The selected architecture for Tor is " + architecture);
}
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl();
WindowsTorPlugin plugin = new WindowsTorPlugin(ioExecutor, wakefulIoExecutor,
networkManager, locationUtils, torSocketFactory, clock,
resourceProvider, circumventionProvider, batteryManager,
backoff, torRendezvousCrypto, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort, torControlPort);
eventBus.addListener(plugin);
return plugin;
}
}

View File

@@ -0,0 +1,217 @@
package org.briarproject.bramble.plugin.tor;
import com.sun.jna.Library;
import com.sun.jna.Native;
import net.freehaven.tor.control.TorControlConnection;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import java.io.*;
import java.net.Socket;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.net.SocketFactory;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
@NotNullByDefault
class WindowsTorPlugin extends JavaTorPlugin {
WindowsTorPlugin(Executor ioExecutor,
Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
SocketFactory torSocketFactory,
Clock clock,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback,
String architecture,
long maxLatency,
int maxIdleTime,
File torDirectory,
int torSocksPort,
int torControlPort) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, backoff,
torRendezvousCrypto, callback, architecture,
maxLatency, maxIdleTime, torDirectory, torSocksPort, torControlPort);
}
protected File getTorExecutableFile() {
return new File(torDirectory, "tor.exe");
}
protected InputStream getConfigInputStream() {
StringBuilder strb = new StringBuilder();
append(strb, "ControlPort", torControlPort);
append(strb, "CookieAuthentication", 1);
append(strb, "DisableNetwork", 1);
append(strb, "RunAsDaemon", 1);
append(strb, "SafeSocks", 1);
append(strb, "SocksPort", torSocksPort);
InputStream inputStream = new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("UTF-8")));
InputStream windowsPaths = new ByteArrayInputStream(getTorrcPaths());
inputStream = new SequenceInputStream(inputStream, windowsPaths);
return inputStream;
}
private byte[] getTorrcPaths() {
StringBuilder sb = new StringBuilder();
sb.append("\n");
sb.append("GeoIPFile ");
sb.append(geoIpFile.getAbsolutePath());
sb.append("\n");
sb.append("GeoIPv6File ");
sb.append(geoIpFile.getAbsolutePath());
sb.append("6");
sb.append("\n");
sb.append("DataDirectory ");
sb.append(torDirectory);
sb.append("\\.tor");
return sb.toString().getBytes(StandardCharsets.UTF_8);
}
@Override
public void start() throws PluginException {
/*
TODO:
- properly handle and throw PluginExceptions etc.
- absolute paths in Windows torrc (Linux too?)
- don't do 10 seconds sleep in main thread
*/
if (used.getAndSet(true)) throw new IllegalStateException();
if (!torDirectory.exists()) {
if (!torDirectory.mkdirs()) {
LOG.warning("Could not create Tor directory.");
throw new PluginException();
}
}
// Load the settings
settings = migrateSettings(callback.getSettings());
// Install or update the assets if necessary
if (!assetsAreUpToDate()) installAssets();
if (cookieFile.exists() && !cookieFile.delete())
LOG.warning("Old auth cookie not deleted");
// Start a new Tor process
LOG.info("Starting Tor");
File torFile = getTorExecutableFile();
String torPath = torFile.getAbsolutePath();
String configPath = configFile.getAbsolutePath();
String pid = String.valueOf(getProcessId());
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
Process torProcess;
ProcessBuilder pb =
new ProcessBuilder(torPath, "-f", configPath, OWNER, pid);
pb.redirectErrorStream(true); // logged only first line on Windows otherwise
Map<String, String> env = pb.environment();
env.put("HOME", torDirectory.getAbsolutePath());
pb.directory(torDirectory);
try {
torProcess = pb.start();
// Log the process's standard output until it detaches
if (LOG.isLoggable(INFO)) {
Scanner stdout = new Scanner(torProcess.getInputStream());
while (stdout.hasNextLine()) {
if (stdout.hasNextLine()) {
LOG.info(stdout.nextLine());
}
}
stdout.close();
}
try {
// Wait for the process to detach or exit
int exit = torProcess.waitFor();
if (exit != 0) {
if (LOG.isLoggable(WARNING))
LOG.warning("Tor exited with value " + exit);
}
// Wait for the auth cookie file to be created/updated
long start = clock.currentTimeMillis();
while (cookieFile.length() < 32) {
if (clock.currentTimeMillis() - start > COOKIE_TIMEOUT_MS) {
LOG.warning("Auth cookie not created");
if (LOG.isLoggable(INFO)) listFiles(torDirectory);
}
Thread.sleep(COOKIE_POLLING_INTERVAL_MS);
}
LOG.info("Auth cookie created");
} catch (InterruptedException e) {
LOG.warning("Interrupted while starting Tor");
Thread.currentThread().interrupt();
e.printStackTrace();
}
} catch (SecurityException | IOException e) {
e.printStackTrace();
}
}
});
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
// Open a control connection and authenticate using the cookie file
controlSocket = new Socket("127.0.0.1", torControlPort);
controlConnection = new TorControlConnection(controlSocket);
controlConnection.authenticate(read(cookieFile));
// Tell Tor to exit when the control connection is closed
controlConnection.takeOwnership();
controlConnection.resetConf(singletonList(OWNER));
// Register to receive events from the Tor process
controlConnection.setEventHandler(this);
controlConnection.setEvents(asList(EVENTS));
// Check whether Tor has already bootstrapped
String phase = controlConnection.getInfo("status/bootstrap-phase");
if (phase != null && phase.contains("PROGRESS=100")) {
LOG.info("Tor has already bootstrapped");
state.setBootstrapped();
}
} catch (IOException e) {
throw new PluginException(e);
}
state.setStarted();
// Check whether we're online
updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging());
// Bind a server socket to receive incoming hidden service connections
bind();
}
@Override
protected int getProcessId() {
return CLibrary.INSTANCE._getpid();
}
private interface CLibrary extends Library {
CLibrary INSTANCE = Native.loadLibrary("msvcrt", CLibrary.class);
int _getpid();
}
}

View File

@@ -9,6 +9,7 @@ import dagger.Provides;
import static org.briarproject.bramble.util.OsUtils.isLinux;
import static org.briarproject.bramble.util.OsUtils.isMac;
import static org.briarproject.bramble.util.OsUtils.isWindows;
@Module
public class DesktopSecureRandomModule {
@@ -18,7 +19,8 @@ public class DesktopSecureRandomModule {
SecureRandomProvider provideSecureRandomProvider() {
if (isLinux() || isMac())
return new UnixSecureRandomProvider();
// TODO: Create a secure random provider for Windows
if (isWindows())
return new WindowsSecureRandomProvider();
throw new UnsupportedOperationException();
}
}

View File

@@ -6,12 +6,14 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
import org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType;
import org.briarproject.bramble.test.BrambleJavaIntegrationTestComponent;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.DaggerBrambleJavaIntegrationTestComponent;
@@ -34,11 +36,14 @@ import javax.inject.Inject;
import javax.net.SocketFactory;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_CONTROL_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_SOCKS_PORT;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
@@ -57,14 +62,21 @@ public class BridgeTest extends BrambleTestCase {
.injectEagerSingletons(component);
// Share a failure counter among all the test instances
AtomicInteger failures = new AtomicInteger(0);
List<String> bridges =
component.getCircumventionProvider().getBridges(false);
List<Params> states = new ArrayList<>(bridges.size());
for (String bridge : bridges) states.add(new Params(bridge, failures));
CircumventionProvider provider = component.getCircumventionProvider();
List<Params> states = new ArrayList<>();
for (String bridge : provider.getBridges(DEFAULT_OBFS4)) {
states.add(new Params(bridge, DEFAULT_OBFS4, failures, false));
}
for (String bridge : provider.getBridges(NON_DEFAULT_OBFS4)) {
states.add(new Params(bridge, NON_DEFAULT_OBFS4, failures, false));
}
for (String bridge : provider.getBridges(MEEK)) {
states.add(new Params(bridge, MEEK, failures, true));
}
return states;
}
private final static long TIMEOUT = SECONDS.toMillis(60);
private final static long TIMEOUT = MINUTES.toMillis(5);
private final static int NUM_FAILURES_ALLOWED = 1;
private final static Logger LOG = getLogger(BridgeTest.class.getName());
@@ -93,14 +105,12 @@ public class BridgeTest extends BrambleTestCase {
CryptoComponent crypto;
private final File torDir = getTestDirectory();
private final String bridge;
private final AtomicInteger failures;
private final Params params;
private UnixTorPluginFactory factory;
private DesktopTorPluginFactory factory;
public BridgeTest(Params params) {
bridge = params.bridge;
failures = params.failures;
this.params = params;
}
@Before
@@ -120,6 +130,7 @@ public class BridgeTest extends BrambleTestCase {
LocationUtils locationUtils = () -> "US";
SocketFactory torSocketFactory = SocketFactory.getDefault();
@NotNullByDefault
CircumventionProvider bridgeProvider = new CircumventionProvider() {
@Override
public boolean isTorProbablyBlocked(String countryCode) {
@@ -132,16 +143,16 @@ public class BridgeTest extends BrambleTestCase {
}
@Override
public boolean needsMeek(String countryCode) {
return false;
public BridgeType getBestBridgeType(String countryCode) {
return params.bridgeType;
}
@Override
public List<String> getBridges(boolean useMeek) {
return singletonList(bridge);
public List<String> getBridges(BridgeType bridgeType) {
return singletonList(params.bridge);
}
};
factory = new UnixTorPluginFactory(ioExecutor, wakefulIoExecutor,
factory = new DesktopTorPluginFactory(ioExecutor, wakefulIoExecutor,
networkManager, locationUtils, eventBus, torSocketFactory,
backoffFactory, resourceProvider, bridgeProvider,
batteryManager, clock, torDir, DEFAULT_SOCKS_PORT,
@@ -160,7 +171,7 @@ public class BridgeTest extends BrambleTestCase {
assertNotNull(duplexPlugin);
UnixTorPlugin plugin = (UnixTorPlugin) duplexPlugin;
LOG.warning("Testing " + bridge);
LOG.warning("Testing " + params.bridge);
try {
plugin.start();
long start = clock.currentTimeMillis();
@@ -170,8 +181,11 @@ public class BridgeTest extends BrambleTestCase {
}
if (plugin.getState() != ACTIVE) {
LOG.warning("Could not connect to Tor within timeout");
if (failures.incrementAndGet() > NUM_FAILURES_ALLOWED) {
fail(failures.get() + " bridges are unreachable");
if (params.failures.incrementAndGet() > NUM_FAILURES_ALLOWED) {
fail(params.failures.get() + " bridges are unreachable");
}
if (params.mustSucceed) {
fail("essential bridge is unreachable");
}
}
} finally {
@@ -182,11 +196,16 @@ public class BridgeTest extends BrambleTestCase {
private static class Params {
private final String bridge;
private final BridgeType bridgeType;
private final AtomicInteger failures;
private final boolean mustSucceed;
private Params(String bridge, AtomicInteger failures) {
private Params(String bridge, BridgeType bridgeType,
AtomicInteger failures, boolean mustSucceed) {
this.bridge = bridge;
this.bridgeType = bridgeType;
this.failures = failures;
this.mustSucceed = mustSucceed;
}
}
}

View File

@@ -25,7 +25,6 @@ dependencyVerification {
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a:obfs4proxy-0.0.12-dev-40245c4a.zip:172029e7058b3a83ac93ac4991a44bf76e16ce8d46f558f5836d57da3cb3a766',
'org.briarproject:tor:0.3.5.15:tor-0.3.5.15.jar:2ff5b5a3b5eaa97d699629ad24ba9584b3199d0ffdb1ea7d8a02de3016b80e7a',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',

View File

@@ -26,8 +26,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
versionCode 10401
versionName "1.4.1"
versionCode 10404
versionName "1.4.4"
applicationId "org.briarproject.briar.android"
vectorDrawables.useSupportLibrary = true
@@ -131,17 +131,17 @@ dependencies {
def espressoVersion = '3.3.0'
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation project(path: ':bramble-core', configuration: 'testOutput')
testImplementation 'androidx.test:runner:1.3.0'
testImplementation 'androidx.test.ext:junit:1.1.2'
testImplementation 'androidx.fragment:fragment-testing:1.3.4'
testImplementation 'androidx.test:runner:1.4.0'
testImplementation 'androidx.test.ext:junit:1.1.3'
testImplementation 'androidx.fragment:fragment-testing:1.4.0'
testImplementation "androidx.arch.core:core-testing:2.1.0"
testImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
testImplementation 'org.robolectric:robolectric:4.3.1'
testImplementation 'org.robolectric:robolectric:4.4'
testImplementation 'org.mockito:mockito-core:3.9.0'
testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-legacy:$jmock_version"
testImplementation "org.jmock:jmock-imposters:$jmock_version"
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput')

View File

@@ -3,6 +3,8 @@
-dontobfuscate
-keepattributes SourceFile, LineNumberTable, *Annotation*, Signature, InnerClasses, EnclosingMethod
-keep,includedescriptorclasses class org.briarproject.briar.android.**,org.briarproject.briar.api.android.** { *; }
# QR codes
-keep class com.google.zxing.Result
-keepclassmembers enum * {

View File

@@ -99,6 +99,10 @@
android:name="org.briarproject.briar.android.splash.ExpiredActivity"
android:label="@string/app_name" />
<activity
android:name="org.briarproject.briar.android.splash.ExpiredOldAndroidActivity"
android:label="@string/app_name" />
<activity
android:name="org.briarproject.briar.android.login.StartupActivity"
android:label="@string/app_name" />

View File

@@ -56,6 +56,7 @@ import org.briarproject.briar.android.viewmodel.ViewModelModule;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager;
import org.briarproject.briar.api.android.NetworkUsageMetrics;
import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.test.TestAvatarCreator;
@@ -114,7 +115,7 @@ public class AppModule {
@Inject
ScreenFilterMonitor screenFilterMonitor;
@Inject
NetworkUsageLogger networkUsageLogger;
NetworkUsageMetrics networkUsageMetrics;
@Inject
DozeWatchdog dozeWatchdog;
@Inject
@@ -287,11 +288,12 @@ public class AppModule {
}
@Provides
NetworkUsageLogger provideNetworkUsageLogger(
@Singleton
NetworkUsageMetrics provideNetworkUsageMetrics(
LifecycleManager lifecycleManager) {
NetworkUsageLogger networkUsageLogger = new NetworkUsageLogger();
lifecycleManager.registerService(networkUsageLogger);
return networkUsageLogger;
NetworkUsageMetrics networkUsageMetrics = new NetworkUsageMetricsImpl();
lifecycleManager.registerService(networkUsageMetrics);
return networkUsageMetrics;
}
@Provides
@@ -337,6 +339,21 @@ public class AppModule {
public boolean shouldEnableDisappearingMessages() {
return true;
}
@Override
public boolean shouldEnablePrivateGroupsInCore() {
return true;
}
@Override
public boolean shouldEnableForumsInCore() {
return true;
}
@Override
public boolean shouldEnableBlogsInCore() {
return true;
}
};
}
}

View File

@@ -1,40 +0,0 @@
package org.briarproject.briar.android;
import android.net.TrafficStats;
import android.os.Process;
import org.briarproject.bramble.api.lifecycle.Service;
import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.util.LogUtils.now;
class NetworkUsageLogger implements Service {
private static final Logger LOG =
Logger.getLogger(NetworkUsageLogger.class.getName());
private volatile long startTime, rxBytes, txBytes;
@Override
public void startService() {
startTime = now();
int uid = Process.myUid();
rxBytes = TrafficStats.getUidRxBytes(uid);
txBytes = TrafficStats.getUidTxBytes(uid);
}
@Override
public void stopService() {
if (LOG.isLoggable(INFO)) {
long sessionDuration = now() - startTime;
int uid = Process.myUid();
long rx = TrafficStats.getUidRxBytes(uid) - rxBytes;
long tx = TrafficStats.getUidTxBytes(uid) - txBytes;
LOG.info("Duration " + (sessionDuration / 1000) + " seconds");
LOG.info("Received " + rx + " bytes");
LOG.info("Sent " + tx + " bytes");
}
}
}

View File

@@ -0,0 +1,49 @@
package org.briarproject.briar.android;
import android.net.TrafficStats;
import android.os.Process;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.android.NetworkUsageMetrics;
import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.util.LogUtils.now;
@NotNullByDefault
class NetworkUsageMetricsImpl implements NetworkUsageMetrics {
private static final Logger LOG =
Logger.getLogger(NetworkUsageMetricsImpl.class.getName());
private volatile long startTime, rxBytes, txBytes;
@Override
public void startService() {
startTime = now();
int uid = Process.myUid();
rxBytes = TrafficStats.getUidRxBytes(uid);
txBytes = TrafficStats.getUidTxBytes(uid);
}
@Override
public void stopService() {
if (LOG.isLoggable(INFO)) {
Metrics metrics = getMetrics();
LOG.info("Duration " + (metrics.getSessionDurationMs() / 1000)
+ " seconds");
LOG.info("Received " + metrics.getRxBytes() + " bytes");
LOG.info("Sent " + metrics.getTxBytes() + " bytes");
}
}
@Override
public Metrics getMetrics() {
long sessionDurationMs = now() - startTime;
int uid = Process.myUid();
long rx = TrafficStats.getUidRxBytes(uid) - rxBytes;
long tx = TrafficStats.getUidTxBytes(uid) - txBytes;
return new Metrics(sessionDurationMs, rx, tx);
}
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android;
import org.briarproject.briar.BuildConfig;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.concurrent.TimeUnit.DAYS;
public interface TestingConstants {
@@ -19,10 +20,15 @@ public interface TestingConstants {
*/
boolean PREVENT_SCREENSHOTS = !IS_DEBUG_BUILD;
boolean IS_OLD_ANDROID = SDK_INT <= 19;
long OLD_ANDROID_WARN_DATE = 1659225600_000L; // 2022-07-31
long OLD_ANDROID_EXPIRY_DATE = 1675123200_000L; // 2023-01-31
/**
* Debug builds expire after 90 days. Release builds expire after 292
* million years.
* Debug builds expire after 90 days. Release builds running on Android 4
* expire at a set date, otherwise they expire after 292 million years.
*/
long EXPIRY_DATE = IS_DEBUG_BUILD ?
BuildConfig.BuildTimestamp + DAYS.toMillis(90) : Long.MAX_VALUE;
BuildConfig.BuildTimestamp + DAYS.toMillis(90)
: (IS_OLD_ANDROID ? OLD_ANDROID_EXPIRY_DATE : Long.MAX_VALUE);
}

View File

@@ -80,6 +80,7 @@ import org.briarproject.briar.android.sharing.ShareBlogFragment;
import org.briarproject.briar.android.sharing.ShareForumActivity;
import org.briarproject.briar.android.sharing.ShareForumFragment;
import org.briarproject.briar.android.sharing.SharingModule;
import org.briarproject.briar.android.splash.ExpiredOldAndroidActivity;
import org.briarproject.briar.android.splash.SplashScreenActivity;
import org.briarproject.briar.android.test.TestDataActivity;
@@ -182,6 +183,8 @@ public interface ActivityComponent {
void inject(RemovableDriveActivity activity);
void inject(ExpiredOldAndroidActivity activity);
// Fragments
void inject(SetupFragment fragment);

View File

@@ -40,10 +40,10 @@ public class ContactListItem extends ContactItem
item.unread, item.timestamp);
}
ContactListItem(ContactListItem item, ConversationMessageHeader h) {
ContactListItem(ContactListItem item, long timestamp, boolean read) {
this(item.getContact(), item.getAuthorInfo(), item.isConnected(), false,
h.isRead() ? item.unread : item.unread + 1,
Math.max(h.getTimestamp(), item.timestamp));
read ? item.unread : item.unread + 1,
Math.max(timestamp, item.timestamp));
}
/**

View File

@@ -28,6 +28,7 @@ import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
import org.briarproject.briar.api.conversation.event.ConversationMessageTrackedEvent;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager;
@@ -131,13 +132,14 @@ public class ContactsViewModel extends DbViewModel implements EventListener {
} else if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed, removing item");
removeItem(((ContactRemovedEvent) e).getContactId());
} else if (e instanceof ConversationMessageReceivedEvent) {
LOG.info("Conversation message received, updating item");
ConversationMessageReceivedEvent<?> p =
(ConversationMessageReceivedEvent<?>) e;
ConversationMessageHeader h = p.getMessageHeader();
updateItem(p.getContactId(), item -> new ContactListItem(item, h),
true);
} else if (e instanceof ConversationMessageTrackedEvent) {
LOG.info("Conversation message tracked, updating item");
ConversationMessageTrackedEvent p =
(ConversationMessageTrackedEvent) e;
long timestamp = p.getTimestamp();
boolean read = p.getRead();
updateItem(p.getContactId(),
item -> new ContactListItem(item, timestamp, read), true);
} else if (e instanceof AvatarUpdatedEvent) {
AvatarUpdatedEvent a = (AvatarUpdatedEvent) e;
updateItem(a.getContactId(), item -> new ContactListItem(item,

View File

@@ -312,12 +312,19 @@ class HotspotManager {
}
GroupInfoListener groupListener = group -> {
boolean valid = isGroupValid(group);
// If the group is valid, set the hotspot to started. If we don't
// have any attempts left, we try what we got
if (valid || attempt >= MAX_GROUP_INFO_ATTEMPTS) {
if (valid) {
// the group is valid, set the hotspot to started.
onHotspotStarted(group);
} else if (attempt < MAX_GROUP_INFO_ATTEMPTS) {
// we have attempts left, try again
retryRequestingGroupInfo(attempt);
} else if (group != null) {
// no attempts left, but group is not null, try what we got
onHotspotStarted(group);
} else {
retryRequestingGroupInfo(attempt);
// no attempts left and group is null, fail
releaseHotspotWithError(ctx.getString(
R.string.hotspot_error_start_callback_no_group_info));
}
};
try {
@@ -366,13 +373,8 @@ class HotspotManager {
private void retryRequestingGroupInfo(int attempt) {
LOG.info("retrying to request group info");
// On some devices we need to wait for the group info to become available
if (attempt < MAX_GROUP_INFO_ATTEMPTS) {
handler.postDelayed(() -> requestGroupInfo(attempt + 1),
RETRY_DELAY_MILLIS);
} else {
releaseHotspotWithError(ctx.getString(
R.string.hotspot_error_start_callback_no_group_info));
}
handler.postDelayed(() -> requestGroupInfo(attempt + 1),
RETRY_DELAY_MILLIS);
}
@UiThread

View File

@@ -75,12 +75,15 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED;
import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT;
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
import static org.briarproject.briar.android.navdrawer.IntentRouter.handleExternalIntent;
import static org.briarproject.briar.android.util.UiUtils.formatDateFull;
import static org.briarproject.briar.android.util.UiUtils.getDaysUntilExpiry;
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
import static org.briarproject.briar.android.util.UiUtils.shouldWarnOldAndroidExpiry;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -137,9 +140,11 @@ public class NavDrawerActivity extends BriarActivity implements
setContentView(R.layout.activity_nav_drawer);
BriarApplication app = (BriarApplication) getApplication();
if (IS_DEBUG_BUILD && !app.isInstrumentationTest()) {
navDrawerViewModel.showExpiryWarning()
.observe(this, this::showExpiryWarning);
if (!app.isInstrumentationTest()) {
if (IS_DEBUG_BUILD || shouldWarnOldAndroidExpiry()) {
navDrawerViewModel.showExpiryWarning()
.observe(this, this::showExpiryWarning);
}
}
navDrawerViewModel.shouldAskForDozeWhitelisting().observe(this, ask -> {
if (ask) showDozeDialog(getString(R.string.setup_doze_intro));
@@ -207,7 +212,9 @@ public class NavDrawerActivity extends BriarActivity implements
public void onStart() {
super.onStart();
lockManager.checkIfLockable();
if (IS_DEBUG_BUILD) navDrawerViewModel.checkExpiryWarning();
if (IS_DEBUG_BUILD || shouldWarnOldAndroidExpiry()) {
navDrawerViewModel.checkExpiryWarning();
}
}
@Override
@@ -377,14 +384,23 @@ public class NavDrawerActivity extends BriarActivity implements
return;
}
String text;
if (IS_DEBUG_BUILD) {
text = getResources().getQuantityString(
R.plurals.expiry_warning, (int) daysUntilExpiry,
(int) daysUntilExpiry);
} else {
text = getResources().getQuantityString(
R.plurals.old_android_expiry_warning, (int) daysUntilExpiry,
formatDateFull(this, EXPIRY_DATE),
(int) daysUntilExpiry);
}
ViewGroup expiryWarning = findViewById(R.id.expiryWarning);
if (show) {
// show expiry warning text
TextView expiryWarningText =
expiryWarning.findViewById(R.id.expiryWarningText);
String text = getResources().getQuantityString(
R.plurals.expiry_warning, (int) daysUntilExpiry,
(int) daysUntilExpiry);
expiryWarningText.setText(text);
// make close button functional
ImageView expiryWarningClose =

View File

@@ -28,6 +28,8 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
import org.briarproject.briar.android.reporting.ReportData.ReportItem;
import org.briarproject.briar.android.reporting.ReportData.SingleReportInfo;
import org.briarproject.briar.api.android.NetworkUsageMetrics;
import org.briarproject.briar.api.android.NetworkUsageMetrics.Metrics;
import java.io.File;
import java.io.PrintWriter;
@@ -65,9 +67,11 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
class BriarReportCollector {
private final Context ctx;
private final NetworkUsageMetrics networkUsageMetrics;
BriarReportCollector(Context ctx) {
BriarReportCollector(Context ctx, NetworkUsageMetrics networkUsageMetrics) {
this.ctx = ctx;
this.networkUsageMetrics = networkUsageMetrics;
}
ReportData collectReportData(@Nullable Throwable t, long appStartTime,
@@ -81,6 +85,7 @@ class BriarReportCollector {
.add(getMemory())
.add(getStorage())
.add(getConnectivity())
.add(getNetworkUsage())
.add(getBuildConfig())
.add(getLogcat(logs))
.add(getDeviceFeatures());
@@ -298,6 +303,16 @@ class BriarReportCollector {
connectivityInfo);
}
private ReportItem getNetworkUsage() {
Metrics metrics = networkUsageMetrics.getMetrics();
MultiReportInfo networkUsage = new MultiReportInfo()
.add("SessionDuration", metrics.getSessionDurationMs())
.add("BytesReceived", metrics.getRxBytes())
.add("BytesSent", metrics.getTxBytes());
return new ReportItem("NetworkUsage", R.string.dev_report_network_usage,
networkUsage);
}
private ReportItem getBuildConfig() {
MultiReportInfo buildConfig = new MultiReportInfo()
.add("GitHash", BuildConfig.GitHash)

View File

@@ -18,6 +18,7 @@ import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
import org.briarproject.briar.android.reporting.ReportData.ReportItem;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.android.NetworkUsageMetrics;
import org.json.JSONException;
import java.io.File;
@@ -69,12 +70,13 @@ class ReportViewModel extends AndroidViewModel {
@Inject
ReportViewModel(@NonNull Application application,
NetworkUsageMetrics networkUsageMetrics,
CachingLogHandler logHandler,
LogDecrypter logDecrypter,
DevReporter reporter,
PluginManager pluginManager) {
super(application);
collector = new BriarReportCollector(application);
collector = new BriarReportCollector(application, networkUsageMetrics);
this.logHandler = logHandler;
this.logDecrypter = logDecrypter;
this.reporter = reporter;

View File

@@ -0,0 +1,57 @@
package org.briarproject.briar.android.splash;
import android.content.Intent;
import android.os.Bundle;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.controller.BriarController;
import org.briarproject.briar.android.logout.ExitActivity;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ExpiredOldAndroidActivity extends BaseActivity {
@Inject
BriarController briarController;
@Inject
AndroidWakeLockManager wakeLockManager;
@Override
public void onCreate(@Nullable Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_expired_old_android);
findViewById(R.id.delete_account_button).setOnClickListener(v -> {
// Hold a wake lock to ensure we exit before the device goes to sleep
wakeLockManager.runWakefully(() -> {
// we're not signed in, just go ahead and delete
briarController.deleteAccount();
// remove from recent apps
Intent i = new Intent(this, ExitActivity.class);
i.addFlags(FLAG_ACTIVITY_NEW_TASK
| FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| FLAG_ACTIVITY_NO_ANIMATION
| FLAG_ACTIVITY_CLEAR_TASK);
startActivity(i);
}, "DeleteAccount");
});
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
}

View File

@@ -27,6 +27,7 @@ import static java.lang.System.currentTimeMillis;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -65,8 +66,13 @@ public class SplashScreenActivity extends BaseActivity {
getResources().getInteger(R.integer.splashScreenDuration);
new Handler().postDelayed(() -> {
if (currentTimeMillis() >= EXPIRY_DATE) {
LOG.info("Expired");
startNextActivity(ExpiredActivity.class);
if (IS_DEBUG_BUILD) {
LOG.info("Expired: debug build");
startNextActivity(ExpiredActivity.class);
} else {
LOG.info("Expired: running on old Android");
startNextActivity(ExpiredOldAndroidActivity.class);
}
} else {
startNextActivity(ENTRY_ACTIVITY);
}

View File

@@ -10,12 +10,14 @@ import androidx.activity.result.contract.ActivityResultContract;
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument;
import androidx.activity.result.contract.ActivityResultContracts.GetContent;
import androidx.activity.result.contract.ActivityResultContracts.GetMultipleContents;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import static android.app.Activity.RESULT_CANCELED;
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
import static android.content.Intent.EXTRA_MIME_TYPES;
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import static android.os.Build.VERSION.SDK_INT;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
@@ -23,6 +25,7 @@ import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageConten
public class ActivityLaunchers {
public static class CreateDocumentAdvanced extends CreateDocument {
@NonNull
@Override
public Intent createIntent(Context context, String input) {
Intent i = super.createIntent(context, input);
@@ -32,20 +35,24 @@ public class ActivityLaunchers {
}
public static class GetContentAdvanced extends GetContent {
@NonNull
@Override
public Intent createIntent(Context context, String input) {
Intent i = super.createIntent(context, input);
putShowAdvancedExtra(i);
i.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
return i;
}
}
public static class GetImageAdvanced extends GetContent {
@NonNull
@Override
public Intent createIntent(Context context, String input) {
Intent i = super.createIntent(context, input);
putShowAdvancedExtra(i);
i.setType("image/*");
i.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
if (SDK_INT >= 19)
i.putExtra(EXTRA_MIME_TYPES, getSupportedImageContentTypes());
return i;
@@ -54,11 +61,13 @@ public class ActivityLaunchers {
@TargetApi(18)
public static class GetMultipleImagesAdvanced extends GetMultipleContents {
@NonNull
@Override
public Intent createIntent(Context context, String input) {
Intent i = super.createIntent(context, input);
putShowAdvancedExtra(i);
i.setType("image/*");
i.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
if (SDK_INT >= 19)
i.putExtra(EXTRA_MIME_TYPES, getSupportedImageContentTypes());
return i;
@@ -67,6 +76,7 @@ public class ActivityLaunchers {
public static class RequestBluetoothDiscoverable
extends ActivityResultContract<Integer, Boolean> {
@NonNull
@Override
public Intent createIntent(Context context, Integer duration) {
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);

View File

@@ -110,6 +110,8 @@ import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
import static org.briarproject.briar.android.TestingConstants.IS_OLD_ANDROID;
import static org.briarproject.briar.android.TestingConstants.OLD_ANDROID_WARN_DATE;
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_LOGCAT;
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_START_TIME;
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_INITIAL_COMMENT;
@@ -197,6 +199,11 @@ public class UiUtils {
return DateUtils.formatDateTime(ctx, time, flags);
}
public static String formatDateFull(Context ctx, long time) {
return DateUtils.formatDateTime(ctx, time,
FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_ABBREV_ALL);
}
/**
* Returns the given duration in a human-friendly format. For example,
* "7 days" or "1 hour 3 minutes".
@@ -232,6 +239,11 @@ public class UiUtils {
return (EXPIRY_DATE - now) / DAYS.toMillis(1);
}
public static boolean shouldWarnOldAndroidExpiry() {
return IS_OLD_ANDROID &&
System.currentTimeMillis() >= OLD_ANDROID_WARN_DATE;
}
public static SpannableStringBuilder getTeaser(Context ctx, Spanned text) {
if (text.length() < TEASER_LENGTH)
throw new IllegalArgumentException(

View File

@@ -93,7 +93,7 @@ public class AuthorView extends ConstraintLayout {
if (authorInfo.getStatus() == OURSELVES) {
authorName.setTypeface(authorNameTypeface, BOLD);
} else {
authorName.setTypeface(authorNameTypeface, NORMAL);
authorName.setTypeface(authorNameTypeface, Typeface.NORMAL);
}
invalidate();

View File

@@ -0,0 +1,34 @@
package org.briarproject.briar.api.android;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface NetworkUsageMetrics extends Service {
Metrics getMetrics();
class Metrics {
private final long sessionDurationMs, rxBytes, txBytes;
public Metrics(long sessionDurationMs, long rxBytes,
long txBytes) {
this.sessionDurationMs = sessionDurationMs;
this.rxBytes = rxBytes;
this.txBytes = txBytes;
}
public long getSessionDurationMs() {
return sessionDurationMs;
}
public long getRxBytes() {
return rxBytes;
}
public long getTxBytes() {
return txBytes;
}
}
}

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_large"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_medium"
android:gravity="center"
android:text="@string/old_android_expiry_date_reached"
android:textSize="@dimen/text_size_large" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_medium"
android:gravity="center"
android:text="@string/create_new_account"
android:textSize="@dimen/text_size_large" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_medium"
android:gravity="center"
android:text="@string/old_android_delete_account"
android:textSize="@dimen/text_size_large" />
<Button
android:id="@+id/delete_account_button"
style="@style/BriarButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_medium"
android:text="@string/delete_account_button" />
</LinearLayout>
</ScrollView>

View File

@@ -54,6 +54,7 @@
<string name="download_briar">За да продължите да използвате Briar изтеглете последното издание.</string>
<string name="create_new_account">Ще трябва да създадете нов профил, но ще можете да използвате същия прякор.</string>
<string name="download_briar_button">Изтегляне</string>
<string name="delete_account_button">Премахване на профил</string>
<string name="startup_open_database">Хранилището се дешифрира…</string>
<string name="startup_migrate_database">Хранилището се обновява…</string>
<string name="startup_compact_database">Хранилището се уплътнява…</string>
@@ -280,7 +281,7 @@
<string name="duplicate_link_dialog_text_1">Вече има чакаща заявка за контакт с тази препратка: %s</string>
<string name="duplicate_link_dialog_text_1_contact">Вече има контакт с тази препратка: %s</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">%s и %s един и същи човек ли са?</string>
<string name="duplicate_link_dialog_text_2">%1$s и %2$s един и същи човек ли са?</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
string will be used in a dialog button, so if the translation of this string is longer than 20
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
@@ -289,7 +290,7 @@
will be used in a dialog button, so if the translation of this string longer than 20 characters,
please use "No" instead, and use "Yes" for the "Same Person" button-->
<string name="different_person_button">Не</string>
<string name="duplicate_link_dialog_text_3">%s и %s са изпратили еднакви препратки.\n\nЕдиния от двамата вероятно се опитва да разбере кои са контактите ви.\n\nНе им споделяйте, че сте получили същата препратка от друг човек.</string>
<string name="duplicate_link_dialog_text_3">%1$s и %2$s са изпратили еднакви препратки.\n\nЕдиният от двамата вероятно се опитва да разбере кои са контактите ви.\n\nНе им споделяйте, че сте получили същата препратка от друг човек.</string>
<string name="pending_contact_updated_toast">Обновена чакаща заявка за контакт</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Запознаване на контакти</string>
@@ -510,11 +511,11 @@
<!--Settings Security and Panic-->
<string name="security_settings_title">Сигурност</string>
<string name="pref_lock_title">Заключване на приложението</string>
<string name="pref_lock_summary">Заключва се екрана, за да предпази Briar докато сте вписани</string>
<string name="pref_lock_summary">Екранът се заключва, за да се предпази Briar докато сте вписани</string>
<string name="pref_lock_disabled_summary">За да се възползвате от тази възможност, настройте заключване на екрана</string>
<string name="pref_lock_timeout_title">Заключване при бездействие</string>
<!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour-->
<string name="pref_lock_timeout_summary">При неактивност Briar автоматично се заключав след %s</string>
<string name="pref_lock_timeout_summary">При неактивност Briar автоматично се заключва след %s</string>
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
<string name="pref_lock_timeout_1">1 минута</string>
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
@@ -534,7 +535,7 @@
<string name="password_changed">Паролата е променена.</string>
<string name="panic_setting">Настройка на бутон за паника</string>
<string name="panic_setting_title">Бутон за паника</string>
<string name="panic_setting_hint">Настройва се реакцията на Briar при използване на приложение за бутон за паника</string>
<string name="panic_setting_hint">Настройва се действието на Briar при използване на приложение за бутон за паника</string>
<string name="panic_app_setting_title">Приложение бутон за паника</string>
<string name="unknown_app">непознато приложение</string>
<string name="panic_app_setting_summary">Няма зададено приложение</string>
@@ -605,6 +606,7 @@
<string name="dev_report_memory">Памет</string>
<string name="dev_report_storage">Хранилище</string>
<string name="dev_report_connectivity">Свързаност</string>
<string name="dev_report_network_usage">Използване на мрежа</string>
<string name="dev_report_build_config">Настройка на изданието</string>
<string name="dev_report_logcat">Журнал на приложението</string>
<string name="dev_report_device_features">Характеристики</string>
@@ -647,8 +649,8 @@
<string name="transports_help_text">Briar може да се свърже с контактите ви през интернет, Wi-Fi или Bluetooth.\n\nЗа повече поверителност цялата връзка към интернет се пренасочва през мрежата на Tor.\n\nАко даден контакт може да бъде достъпен чрез няколко метода Briar ги използва успоредно.</string>
<!--Share app offline-->
<string name="hotspot_title">Споделяне на приложението извън мрежа</string>
<string name="hotspot_intro">Споделете приложението с някого около вас без достъп до интернет с използване на Wi-Fi на устройствата.
\n\nВашето устройство създава безжична точка за достъп. Хората около вас биха могли да се свържат към нея и да изтеглят Briar от вашето устройство.</string>
<string name="hotspot_intro">Споделете приложението с някого наблизо без използване на интернет, а с през Wi-Fi на устройствата.
\n\nВашето устройство ще създаде безжична точка за достъп. Хората наблизо биха могли да се свържат към нея и да изтеглят Briar от вашето устройство.</string>
<string name="hotspot_button_start_sharing">Включване на безжична точка</string>
<string name="hotspot_button_stop_sharing">Спиране на безжична точка</string>
<string name="hotspot_progress_text_start">Настройване на безжична точка…</string>
@@ -683,9 +685,21 @@
<string name="website_download_outro">След като файлът се изтегли, отворете го и го инсталирайте.</string>
<string name="website_troubleshooting_title">Отстраняване на неизправности</string>
<string name="website_troubleshooting_1">Ако не можете да изтеглите приложението пробвайте с друг мрежов четец.</string>
<string name="hotspot_help_wifi_title">Проблеми при свързване чрез Wi-Fi:</string>
<string name="hotspot_help_site_title">Проблеми при посещаване на страницата:</string>
<string name="hotspot_help_fallback_title">Нищо не става?</string>
<string name="hotspot_help_fallback_intro">Запазете приложението като файл на APK и го споделете по друг начин. След като бъде получен от другото устройство може да бъде използван за инсталиране на Briar.
\n\nЗабележка: За да го споделите чрез Bluetooth може да се наложи първо да го преименувате, така че да завършва на .zip.</string>
<string name="hotspot_help_fallback_button">Запазване на приложение</string>
<!--error handling-->
<string name="hotspot_error_intro">Нещо се обърка при споделяне на приложението чрез Wi-Fi:</string>
<string name="hotspot_error_no_wifi_direct">Устройството не поддържа Wi-Fi Direct.</string>
<string name="hotspot_error_start_callback_failed">Безжичната точка не може да стартира: грешката е %s</string>
<string name="hotspot_error_start_callback_failed_unknown">Безжичната точка не може да стартира поради неизвестна грешка: причината е %d</string>
<string name="hotspot_error_start_callback_no_group_info">Безжичната точка не може да стартира: няма информация за група</string>
<string name="hotspot_error_web_server_start">Грешка при стартиране на уеб сървър</string>
<!--Transfer Data via Removable Drives-->
<string name="removable_drive_menu_title">Свързване чрез преносим диск</string>
<string name="removable_drive_title_send">Изпращане на сведения</string>
<string name="removable_drive_title_receive">Получаване на сведения</string>
<string name="removable_drive_send_intro">Докоснете бутона по-долу, за да бъде създаден файл, който ще съдържа шифрованите съобщения. Можете да изберете къде да бъде запазен този файл.\n\nАко желаете да го запазите на преносим диск го включете сега.</string>

View File

@@ -49,6 +49,7 @@
<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="download_briar_button">Descarrega la darrera versió</string>
<string name="delete_account_button">Esborreu el compte</string>
<string name="startup_open_database">S\'està desxifrant la base de dades...</string>
<string name="startup_migrate_database">S\'està actualitzant la base de dades...</string>
<string name="startup_compact_database">S\'està compactant la base de dades...</string>
@@ -276,7 +277,6 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
<string name="duplicate_link_dialog_text_1">Teniu una sol·licitud de contacte pendent amb l\'enllaç %s</string>
<string name="duplicate_link_dialog_text_1_contact">Ja teniu un contacte amb aquest enllaç: %s</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">%s i %s són la mateixa persona?</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
string will be used in a dialog button, so if the translation of this string is longer than 20
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
@@ -285,7 +285,6 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
will be used in a dialog button, so if the translation of this string longer than 20 characters,
please use "No" instead, and use "Yes" for the "Same Person" button-->
<string name="different_person_button">Persones diferents</string>
<string name="duplicate_link_dialog_text_3">%s i %s us han enviat el mateix enllaç.\n\nUn d\'ells pot estar provant de descobrir quins són els vostres contactes.\n\nNo els hi digueu que heu rebut el mateix enllaç d\'algú altre.</string>
<string name="pending_contact_updated_toast">S\'ha actualitzat la sol·licitud de contacte pendent</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Presenteu als vostres contactes</string>

View File

@@ -42,21 +42,28 @@
<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_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_db_error">Briar konnte die Datenbank, die dein Konto, deine Kontakte und deine Nachrichten enthält, 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. Du 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 aktuelle Version 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_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_service_error">Briar konnte eine erforderliche Komponente nicht starten.\n\nBitte aktualisiere auf die neueste Version der App und versuche es erneut.</string>
<plurals name="expiry_warning">
<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>
</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="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 Benutzernamen verwenden kannst.</string>
<string name="download_briar_button">Aktuelle Version herunterladen</string>
<string name="old_android_expiry_date_reached">Briar wird auf Android 4 nicht mehr unterstützt.\nBitte installiere Briar auf einem neueren Gerät.</string>
<string name="old_android_delete_account">Du kannst auf die Schaltfläche unten tippen, um dein Konto von diesem Gerät zu löschen.</string>
<string name="delete_account_button">Konto löschen</string>
<string name="startup_open_database">Datenbank wird entschlüsselt...</string>
<string name="startup_migrate_database">Datenbank wird aktualisiert...</string>
<string name="startup_compact_database">Datenbank wird komprimiert ...</string>
<string name="startup_compact_database">Datenbank wird komprimiert</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">Navigationsleiste öffnen</string>
<string name="nav_drawer_close_description">Navigationsleiste schliessen</string>
@@ -261,7 +268,7 @@
<string name="adding_contact_failed">Hinzufügen von Kontakt ist fehlgeschlagen</string>
<string name="dialog_title_remove_pending_contact">Entfernung bestätigen</string>
<string name="dialog_message_remove_pending_contact">Dieser Kontakt befindet sich noch beim Hinzufügen. Wenn er jetzt entfernt wird, wird das Hinzufügen abgebrochen.</string>
<string name="own_link_error">Gebe den Link deines Kontakts ein, nicht deinen eigenen.</string>
<string name="own_link_error">Gib den Link deines Kontakts ein, nicht deinen eigenen</string>
<string name="nickname_missing">Bitte gib einen Spitznamen an</string>
<string name="invalid_link">Ungültiger Link</string>
<string name="unsupported_link">Dieser Link kommt von einer neueren Version von Briar. Bitte führe eine Aktualisierung auf die aktuelle Version durch und versuche es dann nochmal. </string>
@@ -276,11 +283,11 @@
<item quantity="other">%d neue Kontakte hinzugefügt.</item>
</plurals>
<string name="offline_state">Keine Internetverbindung</string>
<string name="duplicate_link_dialog_title">Link duplizieren</string>
<string name="duplicate_link_dialog_title">Gleicher Link</string>
<string name="duplicate_link_dialog_text_1">Du hast bereits einen Kontakt mit diesem Link ausstehend: %s</string>
<string name="duplicate_link_dialog_text_1_contact">Du hast bereits einen Kontakt mit diesem Link: %s</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">Sind %s und %s dieselbe Person?</string>
<string name="duplicate_link_dialog_text_2">Sind %1$s und %2$s dieselbe Person?</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
string will be used in a dialog button, so if the translation of this string is longer than 20
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
@@ -289,7 +296,7 @@
will be used in a dialog button, so if the translation of this string longer than 20 characters,
please use "No" instead, and use "Yes" for the "Same Person" button-->
<string name="different_person_button">Andere Person</string>
<string name="duplicate_link_dialog_text_3">%s und %s haben Dir denselben Link geschickt.\n\nMöglicherweise versucht einer der beiden mehr über deine Kontakte zu erfahren.\n\nDu solltest deswegen niemandem sagen, dass du diesen Link auch von jemand anderem erhalten hast.</string>
<string name="duplicate_link_dialog_text_3">%1$s und %2$s haben dir denselben Link geschickt.\n\nMöglicherweise versucht einer der beiden mehr über deine Kontakte zu erfahren.\n\nDu solltest deswegen niemandem sagen, dass du diesen Link auch von jemand anderem erhalten hast.</string>
<string name="pending_contact_updated_toast">Ausstehender Kontakt aktualisiert</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Mache deine Kontakte untereinander bekannt</string>
@@ -307,7 +314,7 @@
<string name="introduction_request_exists_received">%1$s schlägt vor, dich als Kontakt an %2$s zu empfehlen. %2$s ist allerdings bereits in deiner Kontaktliste. Da %1$s das vielleicht nicht weiss, kannst du trotzdem antworten:</string>
<string name="introduction_request_answered_received">%1$s schlägt vor, dich als Kontakt an %2$s zu empfehlen. </string>
<string name="introduction_response_accepted_sent">Du hast die Kontaktempfehlung von %1$s angenommen.</string>
<string name="introduction_response_accepted_sent_info">Bevor %1$s zu deinen Kontakten hinzugefügt werden, müssen sie die Kontaktempfehlung ebenfalls akzeptieren. Dies kann eine Weile dauern.</string>
<string name="introduction_response_accepted_sent_info">Bevor %1$s zu deinen Kontakten hinzugefügt wird, muss die Kontaktempfehlung ebenfalls akzeptieren werden. Dies kann eine Weile dauern.</string>
<string name="introduction_response_declined_sent">Du hast die Kontaktempfehlung von %1$s abgelehnt.</string>
<string name="introduction_response_declined_auto">Die Kontaktempfehlung mit %1$s wurde automatisch abgelehnt.</string>
<string name="introduction_response_accepted_received">%1$s hat die Kontaktempfehlung für %2$s angenommen.</string>
@@ -326,9 +333,9 @@
<!--Private Groups-->
<string name="groups_list_empty">Keine Gruppen vorhanden</string>
<string name="groups_list_empty_action">Tippe auf das + Symbol, um eine Gruppe anzulegen oder frage deine Kontakte, Gruppen mit dir zu teilen</string>
<string name="groups_created_by">Erstellt durch %s</string>
<string name="groups_created_by">Erstellt von %s</string>
<plurals name="messages">
<item quantity="one">%d Nachrichten</item>
<item quantity="one">%d Nachricht</item>
<item quantity="other">%d Nachrichten</item>
</plurals>
<string name="groups_group_is_empty">Diese Gruppe ist leer</string>
@@ -364,7 +371,7 @@
<item quantity="one">%d offene Gruppeneinladung</item>
<item quantity="other">%d offene Gruppeneinladungen</item>
</plurals>
<string name="groups_invitations_response_accepted_sent">Gruppeneinladung von %s angenommen.</string>
<string name="groups_invitations_response_accepted_sent">Du hast die Gruppeneinladung von %s angenommen.</string>
<string name="groups_invitations_response_declined_sent">Du hast die Gruppeneinladung abgelehnt von %s abgelehnt.</string>
<string name="groups_invitations_response_declined_auto">Die Gruppeneinladung von %s wurde automatisch abgelehnt.</string>
<string name="groups_invitations_response_accepted_received">%s hat die Gruppeneinladung angenommen.</string>
@@ -415,7 +422,7 @@
<string name="forum_declined_toast">Einladung abgelehnt</string>
<string name="shared_by_format">Geteilt durch %s</string>
<string name="forum_invitation_already_sharing">Bereits geteilt.</string>
<string name="forum_invitation_response_accepted_sent">Du hast die Forumseinladung von %s angenommen.</string>
<string name="forum_invitation_response_accepted_sent">Du hast die Forumeinladung von %s angenommen.</string>
<string name="forum_invitation_response_declined_sent">Du hast die Forumeinladung von %s abgelehnt.</string>
<string name="forum_invitation_response_declined_auto">Die Forumeinladung von %s wurde automatisch abgelehnt.</string>
<string name="forum_invitation_response_accepted_received">%s hat die Forumeinladung angenommen.</string>
@@ -456,7 +463,7 @@
<string name="blogs_sharing_response_accepted_received">%s hat die Blogeinladung angenommen.</string>
<string name="blogs_sharing_response_declined_received">%s hat die Blogeinladung abgelehnt.</string>
<string name="blogs_sharing_invitation_received">%1$shat den Blog \"%2$s\" mit dir geteilt.</string>
<string name="blogs_sharing_invitation_sent">Du teilst den Blog \"%1$s\" mit %2$s.</string>
<string name="blogs_sharing_invitation_sent">Du hast den Blog \"%1$s\" mit %2$s geteilt.</string>
<string name="blogs_sharing_invitations_title">Blogeinladungen</string>
<string name="blogs_sharing_joined_toast">Blog abonniert</string>
<string name="blogs_sharing_declined_toast">Einladung abgelehnt</string>
@@ -609,6 +616,7 @@
<string name="dev_report_memory">Speicher</string>
<string name="dev_report_storage">Speicher</string>
<string name="dev_report_connectivity">Konnektivität</string>
<string name="dev_report_network_usage">Netzwerknutzung</string>
<string name="dev_report_build_config">Buildkonfiguration</string>
<string name="dev_report_logcat">App-Log</string>
<string name="dev_report_device_features">Geräteeigenschaften</string>
@@ -692,6 +700,7 @@
<string name="hotspot_help_wifi_title">Probleme bei der WLAN-Verbindung:</string>
<string name="hotspot_help_wifi_1">Versuche, WLAN auf beiden Telefonen zu deaktivieren und wieder zu aktivieren, und versuche es dann erneut.</string>
<string name="hotspot_help_wifi_2">Wenn dein Telefon meldet, dass das WLAN kein Internet hat, sag ihm, dass du trotzdem verbunden bleiben willst.</string>
<string name="hotspot_help_wifi_3">Telefon neu starten, auf dem der WLAN-Hotspot läuft, danach Briar starten und erneut teilen.</string>
<string name="hotspot_help_site_title">Probleme beim Besuch der lokalen Webseite:</string>
<string name="hotspot_help_site_1">Überprüfe unbedingt, ob du die Adresse genau so wie angezeigt eingegeben hast. Ein kleiner Fehler kann dazu führen, dass der Versuch fehlschlägt.</string>
<string name="hotspot_help_site_2">Vergewissere dich, dass dein Telefon immer noch mit dem richtigen WLAN verbunden ist (siehe oben), wenn du die Webseite aufrufen willst.</string>

View File

@@ -54,6 +54,7 @@
<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="download_briar_button">Descargar la última versión.</string>
<string name="delete_account_button">Borrar cuenta</string>
<string name="startup_open_database">Descifrando la base de datos...</string>
<string name="startup_migrate_database">Actualizando la base de datos...</string>
<string name="startup_compact_database">Compactando base de datos...</string>
@@ -280,7 +281,7 @@
<string name="duplicate_link_dialog_text_1">Ya tiene un contacto pendiente con este enlace: %s</string>
<string name="duplicate_link_dialog_text_1_contact">Ya tienes un contacto con este enlace: %s</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">¿Son %s y %s la misma persona?</string>
<string name="duplicate_link_dialog_text_2">¿Son %1$s y %2$s la misma persona?</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
string will be used in a dialog button, so if the translation of this string is longer than 20
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
@@ -289,7 +290,7 @@
will be used in a dialog button, so if the translation of this string longer than 20 characters,
please use "No" instead, and use "Yes" for the "Same Person" button-->
<string name="different_person_button">Diferente Persona</string>
<string name="duplicate_link_dialog_text_3">%s y %s le envió el mismo enlace.\n\nUno de ellos puede estar tratando de descubrir quiénes son sus contactos.\n\nNo les diga que recibió el mismo enlace de otra persona.</string>
<string name="duplicate_link_dialog_text_3">%1$s y %2$s te enviaron el mismo enlace.\n\nUno de ellos puede estar tratando de descubrir quiénes son tus contactos.\n\nNo les digas que recibiste el mismo enlace de otra persona.</string>
<string name="pending_contact_updated_toast">Contacto pendiente actualizado</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Presenta a tus contactos</string>
@@ -609,6 +610,7 @@
<string name="dev_report_memory">Memoria</string>
<string name="dev_report_storage">Almacenamiento</string>
<string name="dev_report_connectivity">Conectividad</string>
<string name="dev_report_network_usage">Uso de red</string>
<string name="dev_report_build_config">Configuración de compilación</string>
<string name="dev_report_logcat">Registro de la aplicación</string>
<string name="dev_report_device_features">Características del dispositivo</string>
@@ -651,7 +653,7 @@
<string name="transports_help_text">Briar puede conectar a tus contactos vía Internet, Wi-Fi o Bluetooth.\n\nTodas las conexiones a Internet van a través de la red Tor por privacidad.\n\nSi un contacto puede ser alcanzado por múltiples métodos, Briar los usa en paralelo.</string>
<!--Share app offline-->
<string name="hotspot_title">Comparte esta aplicación fuera de línea</string>
<string name="hotspot_intro">Comparte esta aplicación con alguien que esté cerca tuyo sin conexión a Internet, usando el Wi-Fi de tu teléfono.
<string name="hotspot_intro">Comparte esta aplicación con alguien que esté cerca tuyo sin una conexión a Internet, usando el Wi-Fi de tu teléfono.
\n\nTu teléfono iniciará un punto de acceso Wi-Fi. Las personas cercanas a tí pueden conectarse al punto de acceso y descargar la aplicación Briar desde tu teléfono.</string>
<string name="hotspot_button_start_sharing">Iniciar punto de acceso</string>
<string name="hotspot_button_stop_sharing">Detener punto de acceso</string>

View File

@@ -45,16 +45,28 @@
اخطار: هویت های شما، مخاطبان شما و پیام های شما برای همیشه از بین خواهند رفت.</string>
<string name="startup_failed_activity_title">خطا در شروع Briar (برایر)</string>
<string name="startup_failed_clock_error">Briar به دلیل این که ساعت دستگاه شما غلط است، اجرا نشد.\n\n لطفا ساعت دستگاه خود را تنظیم کرده و دوباره تلاش کنید.</string>
<string name="startup_failed_db_error">Briar قادر به باز کردن پایگاه داده حاوی حساب کاربری، مخاطبین و پیام‌های شما نشد.\n\n لطفا برنامه را به آخرین نسخه ارتقا داده، و یا هنگام وارد کردن رمز عبور، با انتخاب گزینه «رمز خود را فراموش کرده‌ام» حساب جدیدی ایجاد کنید.</string>
<string name="startup_failed_data_too_old_error">حساب کاربری شما با نسخه قدیمی این برنامه ساخته شده و در این نسخه قابل باز شدن نیست.\n\n شما یا باید نسخه قبلی را دوباره نصب کنید و یا هنگام وارد کردن رمز عبور، با انتخاب «رمز خود را فراموش کرده‌ام» حساب جدیدی ایجاد کنید.</string>
<string name="startup_failed_data_too_new_error">حساب کاربری شما با نسخه جدیدتر این برنامه ساخته شده و در این نسخه قابل باز شدن نیست.\n\n لطفا به آخرین نسخه ارتقا داده و دوباره امتحان کنید.</string>
<string name="startup_failed_service_error">Briar قادر به آغاز اجزای مورد نیاز نیست.\n\n لطفا به آخرین نسخه برنامه ارتقا داده و دوباره امتحان کنید.</string>
<plurals name="expiry_warning">
<item quantity="one">این یک نسخه آزمایشی از Briar (برایر) می باشد. حساب کاربری شما در %d روز آینده به پایان می رسد و امکان تمدید آن وجود نخواهد داشت.</item>
<item quantity="other">این یک نسخه آزمایشی از Briar (برایر) می باشد. حساب کاربری شما در %d روز آینده به پایان می رسد و امکان تمدید آن وجود نخواهد داشت.</item>
</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>
<string name="download_briar">برای ادامه استفاده از Briar، لطفا آخرین نسخه را دانلود کنید.</string>
<string name="create_new_account">لازم است تا یک حساب کاربری جدید ایجاد کنید، ولی می توانید از همان نام مستعار استفاده کنید.</string>
<string name="download_briar_button">دانلود آخرین نسخه</string>
<string name="old_android_expiry_date_reached">Briar دیگر روی اندروید 4 اجرا نمی‌شود.\nلطفا Briar را روی دستگاه جدیدتر نصب کنید. </string>
<string name="old_android_delete_account">برای حذف حساب کاربری خود از این دستگاه می‌توانید روی دکمه زیر ضربه بزنید.</string>
<string name="delete_account_button">حذف حساب کاربری</string>
<string name="startup_open_database">در حال رمزگشایی سیستم ...</string>
<string name="startup_migrate_database">در حال ارتقا سیستم ...</string>
<string name="startup_compact_database">درحال فشرده‌ سازی پایگاه داده...</string>
@@ -289,7 +301,7 @@
<string name="duplicate_link_dialog_text_1">شما هم اکنون یک مخاطب معلق با این پیوند دارید: %s</string>
<string name="duplicate_link_dialog_text_1_contact">شما هم اکنون یک مخاطب با این پیوند دارید: %s</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">آیا %s و %s یک شخص می باشند؟</string>
<string name="duplicate_link_dialog_text_2">آیا %1$s و %2$s یک شخص هستند؟</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
string will be used in a dialog button, so if the translation of this string is longer than 20
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
@@ -298,11 +310,7 @@
will be used in a dialog button, so if the translation of this string longer than 20 characters,
please use "No" instead, and use "Yes" for the "Same Person" button-->
<string name="different_person_button">شخص متفاوت</string>
<string name="duplicate_link_dialog_text_3">%s و %s پیوند یکسانی را به شما ارسال کردند.
یکی از آن ها ممکن است سعی در شناسایی مخاطبان شما داشته باشد.
به آن ها نگویید که همان لینک را از یک شخص دیگر دریافت کرده اید.</string>
<string name="duplicate_link_dialog_text_3">%1$s و %2$s یک پیوند یکسان را به شما ارسال کردند.\n\nشاید یکی از آن‌ها قصد شناسایی مخاطبین شما را دارد.\n\nبه آن‌ها نگویید که همان لینک را از فرد دیگری نیز دریافت کرده‌اید.</string>
<string name="pending_contact_updated_toast">مخاطب معلق به روز رسانی شد</string>
<!--Introductions-->
<string name="introduction_onboarding_title">معرفی مخاطبان</string>
@@ -329,9 +337,13 @@
<!--Connect via Bluetooth-->
<string name="menu_item_connect_via_bluetooth">اتصال از طریق بلوتوث</string>
<string name="connect_via_bluetooth_title">اتصال از طریق بلوتوث</string>
<string name="connect_via_bluetooth_intro">در صورتی که ارتباطات بلوتوث به صورت خودکار کار نکرد، می‌توانید از این صفحه برای ارتباط دستی استفاده کنید.\n\n برای کار کردن این امکان، مخاطب شما باید نزدیک باشد.\n\n شما و مخاطبتان باید هر دو گزینه «آغاز» را همزمان بفشارید.</string>
<string name="connect_via_bluetooth_already_discovering">در حال تلاش برای اتصال با بلوتوث. لطفا به زودی دوباره امتحان کنید.</string>
<string name="connect_via_bluetooth_no_location_permission">امکان ادامه بدون اجازه مکان‌یابی وجود ندارد</string>
<string name="connect_via_bluetooth_start">در حال اتصال از طریق بلوتوث</string>
<string name="connect_via_bluetooth_success">ارتباط از طریق بلوتوث با موفقیت انجام شد</string>
<string name="connect_via_bluetooth_error">اتصال از طریق بلوتوث امکان پذیر نیست.</string>
<string name="connect_via_bluetooth_error_not_supported">بلوتوث توسط دستگاه پشتیبانی نمی‌شود.</string>
<!--Private Groups-->
<string name="groups_list_empty">هیچ گروهی برای نمایش وجود ندارد</string>
<string name="groups_list_empty_action">برای ایجاد یک گروه روی آیکون + کلیک کنید، یا از مخاطبان خود بخواهید تا گروه های خود را با شما به اشتراک بگذارند</string>
@@ -622,6 +634,7 @@
<string name="link_warning_open_link">باز کردن پیوند</string>
<!--Crash Reporter-->
<string name="crash_report_title">گزارش خطای Briar (برایر)</string>
<string name="briar_crashed">متاسفانه Briar از کار افتاد</string>
<string name="not_your_fault">این تقصیر شما نیست.</string>
<string name="please_send_report">لطفا با فرستادن گزارش خطا به ما کمک کنید تا Briar (برایر) را بهتر کنیم.</string>
<string name="report_is_encrypted">به شما اطمینان می دهیم که گزارش شما رمزنگاری شده و به صورت امن فرستاده می شود.</string>
@@ -639,6 +652,7 @@
<string name="dev_report_memory">حافظه</string>
<string name="dev_report_storage">حافظه</string>
<string name="dev_report_connectivity">اتصال</string>
<string name="dev_report_network_usage">استفاده از شبکه</string>
<string name="dev_report_build_config">پیکربندی ساخت</string>
<string name="dev_report_logcat">لاگ برنامه</string>
<string name="dev_report_device_features">ویژگی‌های دستگاه</string>
@@ -692,18 +706,89 @@ Briar (برایر) موقعیت شما را ذخیره نمی‌کند و آن
<!--Connections Screen-->
<string name="transports_help_text">Briar (برایر) می‌تواند از طریق اینترنت، Wi-Fi و یا بلوتوث به مخاطبین شما متصل گردد.\n\nارتباط با اینترنت از طریق شبکه‌ی تور صورت می‌پذیرد.\n\nاگر دسترسی به مخاطب شما از روش‌های مختلفی ممکن باشد، Briar (برایر) به صورت موازی از آن‌ها استفاده خواهد کرد.</string>
<!--Share app offline-->
<string name="hotspot_title">این برنامه را به صورت آفلاین به اشتراک بگذارید</string>
<string name="hotspot_intro">این برنامه را بدو ارتباط با اینترنت و فقط با Wi-Fi تلفن خود با فردی در نزدیکی خود به اشتراک بگذارید.
\n\n تلفن شما با Wi-Fi hotspot آغاز می‌شود. افراد در نزدیکی شما می‌توانند به hotspot متصل شده و برنامه Briar را از تلفن شما دانلود کنند.</string>
<string name="hotspot_button_start_sharing">آغاز hotspot</string>
<string name="hotspot_button_stop_sharing">توقف hotspot</string>
<string name="hotspot_progress_text_start">در حال راه اندازی hotspot...</string>
<string name="hotspot_notification_channel_title">Wi-Fi hotspot</string>
<string name="hotspot_notification_title">اشتراک‌گذاری آفلاین Briar</string>
<string name="hotspot_button_connected">بعد</string>
<string name="permission_hotspot_location_request_body">برای ایجاد Wi-Fi hotspot، Briar به مجوز دسترسی مکانی شما نیاز دارد.\n\n Briar اطلاعات مکانی شما را ذخیره نکرده و با کسی به اشتراک نمی‌گذارد.</string>
<string name="permission_hotspot_location_denied_body">شما دسترسی به مکان خود را رد کرده‌اید، در صورتی که Briar برای ایجاد Wi-Fi hotspot به این مجوز نیاز دارد.\n\n لطفا اجازه دسترسی را در نظر بگیرید.</string>
<string name="wifi_settings_title">تنظیمات Wi-Fi</string>
<string name="wifi_settings_request_enable_body">برای ایجاد Wi-Fi hotspot، Briar نیاز به استفاده از Wi-Fi دارد. لطفا آن را فعال کنید.</string>
<string name="hotspot_tab_manual">دستی</string>
<!--The placeholder to be inserted into the string 'hotspot_manual_wifi': People can connect by %s-->
<string name="hotspot_scanning_a_qr_code">کد QR را اسکن کنید</string>
<!--Wi-Fi setup-->
<!--The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code'-->
<string name="hotspot_manual_wifi">تلفن شما Wi-Fi hotspot ارائه می‌کند. کسانی که قصد دانلود Briar را دارند، می‌توانند با افزودن hotspot در تنظیمات Wi-Fi دستگاه خود، به آن متصل شده و یا %s. هنگامی که به hotspot متصل شدند، دکمه «بعد» را بفشارند.</string>
<string name="hotspot_manual_wifi_ssid">نام شبکه</string>
<string name="hotspot_qr_wifi">تلفن شما Wi-Fi hotspot ارائه می‌کند. کسانی که قصد دانلود Briar را دارند، می‌توانند با اسکن این کد QR به hotspot متصل شوند. هنگامی که به hotspot متصل شدند، دکمه «بعد» را بفشارند.</string>
<string name="hotspot_no_peers_connected">هیچ دستگاهی متصل نیست</string>
<plurals name="hotspot_peers_connected">
<item quantity="one">%s دستگاه متصل است</item>
<item quantity="other">%s دستگاه متصل است</item>
</plurals>
<!--Download link-->
<!--The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code'-->
<string name="hotspot_manual_site">تلفن شما Wi-Fi hotspot ارائه می‌کند. کسانی که به hotspot متصل هستند، می‌توانند Briar را با تایپ لینک زیر در مرورگر خود دانلود کنند و یا %s.</string>
<string name="hotspot_manual_site_address">آدرس (URL)</string>
<string name="hotspot_qr_site">تلفن شما Wi-Fi hotspot ارائه می‌کند. کسانی که به hotspot متصل هستند، می‌توانند Briar را با اسکن این کد QR دانلود کنند.</string>
<!--e.g. Download Briar 1.2.20-->
<string name="website_download_title">دانلود %s</string>
<string name="website_download_intro">فردی در نزدیکی %s را با شما به اشتراک گذاشته است.</string>
<string name="website_download_outro">پس از تکمیل دانلود، فایل دانلود شده را باز کرده و نصب کنید.</string>
<string name="website_troubleshooting_title">◾️ عیب یابی</string>
<string name="website_troubleshooting_1">اگر قادر به دانلود کردن برنامه نیستید، با مرورگر دیگری امتحان کنید.</string>
<string name="website_troubleshooting_2_old">برای نصب برنامه دانلود شده، باید اجازه نصب برنامه از «منابع ناشناخته» را در تنظیمات سیستم بدهید. سپس ممکن است نیاز به دانلود دوباره برنامه داشته باشید. ما توصیه می‌کنیم که پس از نصب این برنامه، اجازه «منابع ناشناخته» را لغو کنید.</string>
<string name="website_troubleshooting_2_new">برای نصب برنامه دانلود شده، ممکن است نیاز به ایجاد مجوز نصب برنامه‌های ناشناخته در مرورگر را داشته باشید. پس از نصب این برنامه، توصیه می‌کنیم مجوز نصب برنامه‌های ناشناخته را لغو کنید.</string>
<string name="hotspot_help_wifi_title">مشکلات اتصال به Wi-Fi:</string>
<string name="hotspot_help_wifi_1">Wi-Fi را در هر دو تلفن غیرفعال و دوباره فعال کنید.</string>
<string name="hotspot_help_wifi_2">اگر تلفن پیامی مبنی بر عدم دسترسی Wi-Fi به اینترنت داد، از گزینه مربوط به متصل ماندن استفاده کنید.</string>
<string name="hotspot_help_wifi_3">تلفنی که میزبان Wi-Fi hotspot است را دوباره راه‌اندازی کنید، سپس Briar را باز کرده و دوباره به اشتراک بگذارید.</string>
<string name="hotspot_help_site_title">مشکلات بازدید از وبسایت محلی:</string>
<string name="hotspot_help_site_1">مطمئن شوید که آدرسی که وارد می‌کنید، دقیقا آدرسی باشد که نمایش یافته. خطای کوچک منجر به ناموفق بودن پروسه خواهد شد.</string>
<string name="hotspot_help_site_2">مطمئن شوید هنگامی که قصد دسترسی به سایت را دارید، تلفن شما هنوز به Wi-Fi صحیح متصل باشد (بالا را ببینید).</string>
<string name="hotspot_help_site_3">اگر از برنامه فایروال استفاده می‌کنید، مطمئن باشید که دسترسی را مسدود نکرده است.</string>
<string name="hotspot_help_site_4">اگر می‌توانید سایت را باز کنید، ولی نمی‌توانید برنامه Briar را دانلود کنید، از یک مرورگر دیگر استفاده کنید.</string>
<string name="hotspot_help_fallback_title">هیچ چیز کار نمی‌کند؟</string>
<string name="hotspot_help_fallback_intro">شما می‌توانید با راه‌هایی، برنامه را به صورت فایل .apk ذخیره و به اشتراک بگذارید. هنگامی که فایل به دستگاه دیگری انتقال یافت، می‌تواند برای نصب Briar مورد استفاده قرار گیرد.
\n\nنکته: برای اشتراک‌گذاری از طریق بلوتوث، ممکن است نیاز به تغییر انتهای نام فایل به .zip داشته باشید.</string>
<string name="hotspot_help_fallback_button">ذخیره برنامه</string>
<!--error handling-->
<string name="hotspot_error_intro">هنگام تلاش برای اشتراک‌گذاری برنامه از طریق Wi-Fi مشکلی رخ داد:</string>
<string name="hotspot_error_no_wifi_direct">دستگاه از Wi-Fi Direct پشتیبانی نمی‌کند</string>
<string name="hotspot_error_start_callback_failed">Hotspot قادر به راه‌اندازی نبود: خطای %s</string>
<string name="hotspot_error_start_callback_failed_unknown">Hotspot با خطایی ناشناخته قادر به راه‌اندازی نبود، علت %d</string>
<string name="hotspot_error_start_callback_no_group_info">Hotspot قادر به راه‌اندازی نبود: بدون اطلاعات گروه</string>
<string name="hotspot_error_web_server_start">خطا در راه‌اندازی سرور وب</string>
<string name="hotspot_error_web_server_serve">خطا در ارائه وبسایت.\n\n لطفا در صورت ادامه مشکل، بازخورد خود را (به همراه داده‌های ناشناس) به برنامه Briar ارسال کنید.</string>
<string name="hotspot_flag_test">هشدار: این برنامه با Android Studio نصب شده و قادر به نصب بر روی دستگاه دیگری نیست.</string>
<string name="hotspot_error_framework_busy">مشکل در راه‌اندازی hotspot.\n\n اگر hotspot دیگری در دستگاه شما فعال است و اینترنت شما را با اتصال Wi-Fi به اشتراک می‌گذارد، لطفا آن را متوقف کرده و دوباره امتحان کنید.</string>
<!--Transfer Data via Removable Drives-->
<string name="removable_drive_menu_title">اتصال ار طریق حافظه قابل جابجایی</string>
<string name="removable_drive_intro">اگر از طریق اینترنت، Wi-Fi و یا بلوتوث قادر به ارتباط با مخاطب خود نیستید، Briar قادر به جابجایی پیام‌ها از طریق درایو قابل جابجایی مانند حافظه قلش و یا کارت SD است.</string>
<string name="removable_drive_explanation">اگر از طریق اینترنت، Wi-Fi و یا بلوتوث قادر به ارتباط با مخاطب خود نیستید، Briar قادر به جابجایی پیام‌ها از طریق درایو قابل جابجایی مانند حافظه قلش و یا کارت SD است.\n\nهنگامی که از گزینه «ارسال داده» استفاده می‌کنید، هر داده‌ای که منتظر ارسال به مخاطب شماست، بر روی حافظه قابل جابجایی ذخیره خواهد شد. این داده‌ها شامل پیام‌ها، ضمائم، بلاگ‌ها، تالار‌ها و گروه‌های خصوصی است.\n\nهمه داده‌ها قبل از ذخیره در حافظه قابل جابجایی، رمزگذاری خواهند شد.\n\nهنگامی که مخاطب شما حافظه قابل جابجایی را دریافت می‌کند، می‌تواند با استفاده از دکمه «دریافت داده» تمامی پیام‌ها را به Briar وارد کند.</string>
<string name="removable_drive_title_send">ارسال داده</string>
<string name="removable_drive_title_receive">دریافت داده</string>
<string name="removable_drive_send_intro">دکمه زیر را برای ایجاد فایل جدید حاوی پیام‌های رمزگذاری شده بفشارید. شما می‌توانید محل ذخیره فایل را تعیین کنید.\n\n اگر تمایل به ذخیره فایل بر روی حافظه قابل جابجایی دارید، آن را الآن متصل کنید.</string>
<string name="removable_drive_send_no_data">در حال حاظر هیچ پیامی در انتظار ارسال به مخاطب نیست.</string>
<string name="removable_drive_send_not_supported">این مخاطب از نسخه قدیمی Briar و یا از دستگاه قدیمی که این قابلیت را پشتیبانی نمی‌کند، استفاده می‌کند.</string>
<string name="removable_drive_send_button">فایل را برای گرفتن خروجی انتخاب کنید</string>
<string name="removable_drive_ongoing">لطفا تا تکمیل کار فعلی منتظر بمانید</string>
<string name="removable_drive_receive_intro">برای انتخاب فایلی که مخاطب شما ارسال کرده است، دکمه زیر را بفشارید.\n\nاگر فایل بر روی حافظه قابل جابجایی است، آن را الآن متصل کنید.</string>
<string name="removable_drive_receive_button">فایل را برای وارد کردن انتخاب کنید</string>
<string name="removable_drive_success_send_title">خروجی با موفقیت گرفته شد</string>
<string name="removable_drive_success_send_text">از داده‌ها با موفقیت خروجی گرفته شد. شما الآن ۲۸ روز تا انتقال فایل به مخاطب خود فرصت دارید.\n\nاگر فایل بر روی حافظه قابل جابجایی است، از پیام در نوار اعلانات برای خارج کردن آن استفاده کنید.</string>
<string name="removable_drive_success_receive_title">درون برد موفق بود</string>
<string name="removable_drive_success_receive_text">تمامی پیام‌های رمزگذاری‌شده در این فایل دریافت شدند.</string>
<string name="removable_drive_error_send_title">خطا حین گرفتن خروجی از داده</string>
<string name="removable_drive_error_send_text">خطایی هنگام نوشتن داده‌ها در فایل رخ داد.\n\nاگر از حافظه قابل جابجایی استفاده می‌کنید، از اتصال صحیح آن مطمئن شوید.\n\nاگر خطا ادامه یافت، لطفا بازخورد خود را برای اطلاع تیم Briar از مشکل، به ما ارسال کنید.</string>
<string name="removable_drive_error_receive_title">خطا حین وارد کردن داده</string>
<string name="removable_drive_error_receive_text">فایل انتخاب شده چیز قابل شناسایی توسط Briar نداشت.\n\nلطفا از انتخاب فایل صحبح مطمئن شوید.\n\nاگر مخاطب شما فایل را بیش از ۲۸ روز قبل ساخته است، Briar قادر به شناسایی آن نخواهد بود.</string>
<!--Screenshots-->
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
<string name="screenshot_alice">آلیس</string>

View File

@@ -41,6 +41,11 @@
<string name="dialog_title_lost_password">Mot de passe oublié</string>
<string name="dialog_message_lost_password">Votre compte Briar est enregistré chiffré sur votre appareil, pas dans le nuage, et nous ne pouvons donc pas réinitialiser votre mot de passe. Voulez-vous supprimer votre compte et recommencer?\n\nAttention : vos identités, contacts et messages seront perdus irrémédiablement.</string>
<string name="startup_failed_activity_title">Échec de démarrage de Briar</string>
<string name="startup_failed_clock_error">Briar na pas pu démarrer, car lhorloge de votre appareil nest pas à lheure.\n\nVeuillez mettre lhorloge de votre appareil à lheure et réessayer.</string>
<string name="startup_failed_db_error">Briar na pas pu ouvrir la base de données qui comprend votre compte, vos contacts et messages.\n\nVeuillez mettre lappli à jour vers la version la plus récente et réessayer, ou créer un nouveau compte en choisissant « Jai oublié mon mot de passe » à linvite de mot de passe.</string>
<string name="startup_failed_data_too_old_error">Votre compte a été créé avec une ancienne version de cette appli et ne peut pas être ouvert avec cette version.\n\nVous devez soit réinstaller lancienne version, soit créer un nouveau compte en choisissant « Jai oublié mon mot de passe » à linvite de mot de passe.</string>
<string name="startup_failed_data_too_new_error">Votre compte a été créé avec une version plus récente de cette appli et ne peut être ouvert avec cette version.\n\nVeuillez mettre lappli à jour vers la version la plus récente et réessayer.</string>
<string name="startup_failed_service_error">Briar n\'a pas pu lancer un composant essentiel.\n\nVeuillez mettre lappli à jour vers la version la plus récente et réessayer.</string>
<plurals name="expiry_warning">
<item quantity="one">Ceci est une version de test de Briar. Votre compte arrivera à expiration dans %d jour et ne peut pas être renouvelé.</item>
<item quantity="other">Ceci est une version de test de Briar. Votre compte arrivera à expiration dans %d jours et ne pourra pas être renouvelé.</item>
@@ -49,6 +54,7 @@
<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="download_briar_button">Télécharger la dernière version</string>
<string name="delete_account_button">Supprimer le compte</string>
<string name="startup_open_database">Déchiffrement de la base de données…</string>
<string name="startup_migrate_database">Mise à niveau de la base de données…</string>
<string name="startup_compact_database">Compactage de la base de données…</string>
@@ -275,7 +281,6 @@
<string name="duplicate_link_dialog_text_1">Vous avez déjà un contact en attente avec ce lien : %s</string>
<string name="duplicate_link_dialog_text_1_contact">Vous avez déjà un contact avec ce lien : %s</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">%s et %s sont-elles la même personne?</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
string will be used in a dialog button, so if the translation of this string is longer than 20
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
@@ -284,7 +289,6 @@
will be used in a dialog button, so if the translation of this string longer than 20 characters,
please use "No" instead, and use "Yes" for the "Same Person" button-->
<string name="different_person_button">Une personne différente</string>
<string name="duplicate_link_dialog_text_3">%s et %s vous ont envoyé le même lien.\n\nL\'une delle pourrait tenter de découvrir qui sont vos contacts.\n\nNe lui dites pas que vous avez reçu le même lien de quelquun dautre.</string>
<string name="pending_contact_updated_toast">Le contact en attente a été mis à jour</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Présenter vos contacts</string>
@@ -311,9 +315,13 @@
<!--Connect via Bluetooth-->
<string name="menu_item_connect_via_bluetooth">Se connecter par Bluetooth</string>
<string name="connect_via_bluetooth_title">Se connecter par Bluetooth</string>
<string name="connect_via_bluetooth_no_location_permission">Impossible de poursuivre sans la permission de position</string>
<string name="connect_via_bluetooth_intro">Au cas où les connexions Bluetooth ne fonctionneraient pas automatiquement, vous pouvez utiliser cet écran pour vous connecter manuellement.\n\nPour que cela fonctionne, votre contact doit être à proximité.\n\nVotre contact et vous devriez appuyer sur « Commencer » en même temps.</string>
<string name="connect_via_bluetooth_already_discovering">Une tentative de connexion par Bluetooth est en cours. Veuillez réessayer dans un moment.</string>
<string name="connect_via_bluetooth_no_location_permission">Impossible de poursuivre sans autoriser la position.</string>
<string name="connect_via_bluetooth_start">Connexion par Bluetooth…</string>
<string name="connect_via_bluetooth_success">Connectée par Bluetooth avec succès</string>
<string name="connect_via_bluetooth_success">La connexion par Bluetooth est réussie</string>
<string name="connect_via_bluetooth_error">Impossible de se connecter par Bluetooth.</string>
<string name="connect_via_bluetooth_error_not_supported">Bluetooth nest pas pris en charge par lappareil.</string>
<!--Private Groups-->
<string name="groups_list_empty">Aucun groupe à afficher</string>
<string name="groups_list_empty_action">Touchez licône + pour créer un groupe ou pour demander à vos contacts de partager des groupes avec vous</string>
@@ -584,6 +592,7 @@ copies des messages que vous envoyez.
<string name="link_warning_open_link">Ouvrir le lien</string>
<!--Crash Reporter-->
<string name="crash_report_title">Rapport de plantage de Briar</string>
<string name="briar_crashed">Désolé, Briar a planté</string>
<string name="not_your_fault">Vous ny êtes pour rien.</string>
<string name="please_send_report">Veuillez nous aider à améliorer Briar en nous envoyant un rapport de plantage.</string>
<string name="report_is_encrypted">Nous promettons que le rapport est chiffré et envoyé en toute sécurité. </string>
@@ -642,10 +651,22 @@ copies des messages que vous envoyez.
<!--Connections Screen-->
<string name="transports_help_text">Briar peut se connecter à vos contacts par Internet, Wi-Fi ou Bluetooth.\n\nToutes les connections Internet passent par le réseau Tor afin de protéger les données.\n\nSi un contact peut être joint par plusieurs moyens, Briar les utilisera simultanément.</string>
<!--Share app offline-->
<string name="hotspot_title">Partager cette appli hors ligne.</string>
<string name="hotspot_intro">Partagez cette appli avec une personne à proximité sans connexion Internet en utilisant le Wi-Fi de votre téléphone.
\n\nVotre téléphone démarrera un point daccès Wi-Fi. Les personnes à proximité peuvent se connecter au point daccès sans fil et télécharger lappli Briar de votre téléphone.</string>
<string name="hotspot_button_start_sharing">Démarrer le point daccès sans fil</string>
<string name="hotspot_button_stop_sharing">Arrêter le point daccès sans fil</string>
<string name="hotspot_progress_text_start">Configuration du point daccès sans fil…</string>
<string name="hotspot_notification_channel_title">Point daccès Wi-Fi</string>
<string name="hotspot_notification_title">Partage hors ligne de Briar</string>
<string name="hotspot_button_connected">Suivant</string>
<string name="permission_hotspot_location_request_body">Pour créer un point daccès Wi-Fi, Briar a besoin de lautorisation Position.\n\nBriar ne stocke pas votre position et ne la partage avec personne.</string>
<string name="permission_hotspot_location_denied_body">Vous avez refusé laccès à votre position, mais Briar en a besoin pour créer un point daccès Wi-Fi.\n\nNous vous invitons à y accorder laccès.</string>
<string name="wifi_settings_title">Paramètre Wi-Fi</string>
<string name="wifi_settings_request_enable_body">Pour créer un point daccès Wi-Fi, Briar a besoin dutiliser le Wi-Fi. Veuillez lactiver.</string>
<string name="hotspot_tab_manual">Manuel</string>
<!--The placeholder to be inserted into the string 'hotspot_manual_wifi': People can connect by %s-->
<string name="hotspot_scanning_a_qr_code">balayant un code QR</string>
<!--Wi-Fi setup-->
<!--The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code'-->
<!--Download link-->

View File

@@ -42,6 +42,7 @@
<string name="download_briar">Para seguir utilizando Briar, descarga por favor a última versión.</string>
<string name="create_new_account">Precisas crear unha nova conta, pero podes utilizar o mesmo alcume.</string>
<string name="download_briar_button">Descargar Última Versión</string>
<string name="delete_account_button">Eliminar conta</string>
<string name="startup_open_database">Descifrando a Base de datos...</string>
<string name="startup_migrate_database">Actualizando a Base de datos...</string>
<string name="startup_compact_database">Compactando a base de datos...</string>
@@ -268,7 +269,6 @@
<string name="duplicate_link_dialog_text_1">Xa tes un contacto pendente con esta ligazón: %s</string>
<string name="duplicate_link_dialog_text_1_contact">Xa tes un contacto con esta ligazón: %s</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">Son %s e %s a mesma persoa?</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
string will be used in a dialog button, so if the translation of this string is longer than 20
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
@@ -277,7 +277,6 @@
will be used in a dialog button, so if the translation of this string longer than 20 characters,
please use "No" instead, and use "Yes" for the "Same Person" button-->
<string name="different_person_button">Diferente Persoa</string>
<string name="duplicate_link_dialog_text_3">%s e %s enviáronche a mesma ligazón.\n\nUnha delas podería estar a intentar descubrir os teus contactos.\n\nNon lles digas que recibiches a mesma ligazón de alguén máis.</string>
<string name="pending_contact_updated_toast">Contacto pendente actualizado</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Presenta aos seus contactos</string>
@@ -643,6 +642,7 @@
<string name="website_troubleshooting_title">Solución de problemas</string>
<!--error handling-->
<!--Transfer Data via Removable Drives-->
<string name="removable_drive_menu_title">Conectar vía Disco Extraíble</string>
<string name="removable_drive_success_receive_title">Importación exitosa</string>
<!--Screenshots-->
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->

View File

@@ -44,6 +44,7 @@
<string name="download_briar">כדי להמשיך להשתמש ב־Briar, אנא הורד את השחרור האחרון.</string>
<string name="create_new_account">יהיה צריך ליצור חשבון חדש, אבל אפשר להשתמש באותו הכינוי.</string>
<string name="download_briar_button">הורד שחרור אחרון</string>
<string name="delete_account_button">מחק חשבון</string>
<string name="startup_open_database">מפענח מסד נתונים…</string>
<string name="startup_migrate_database">משדרג מסד נתונים…</string>
<string name="startup_compact_database">מצופף מסד נתונים…</string>
@@ -256,7 +257,6 @@
<string name="duplicate_link_dialog_text_1">יש לך כבר איש קשר ממתין עם קישור זה: %s</string>
<string name="duplicate_link_dialog_text_1_contact">יש לך כבר איש קשר עם קישור זה: %s</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">האם %s ו־%s הם אותו איש?</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
string will be used in a dialog button, so if the translation of this string is longer than 20
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
@@ -265,7 +265,6 @@
will be used in a dialog button, so if the translation of this string longer than 20 characters,
please use "No" instead, and use "Yes" for the "Same Person" button-->
<string name="different_person_button">איש שונה</string>
<string name="duplicate_link_dialog_text_3">%s ו־%s שלחו לך את אותו הקישור.\n\nייתכן שאחד מהם מנסה לגלות מי הם אנשי הקשר שלך.\n\nאל תגיד לו שקיבלת את אותו הקישור ממישהו אחר.</string>
<string name="pending_contact_updated_toast">איש קשר ממתין עודכן</string>
<!--Introductions-->
<string name="introduction_onboarding_title">הכר בין אנשי הקשר שלך</string>

View File

@@ -49,6 +49,7 @@
<string name="download_briar">A Briar használatának folytatásához kérjük töltse le a legújabb verziót.</string>
<string name="create_new_account">Új fiókot kell létrehoznia, de használhatja ugyanazt a becenevet.</string>
<string name="download_briar_button">Legújabb verzió letöltése</string>
<string name="delete_account_button">Fiók törlése</string>
<string name="startup_open_database">Adatbázis dekódolása...</string>
<string name="startup_migrate_database">Adatbázis frissítése</string>
<string name="startup_compact_database">Adatbázis tömörítése...</string>
@@ -278,7 +279,6 @@ Kérjük frissítsen a legutolsó verzióra és próbálja újra.</string>
<string name="duplicate_link_dialog_text_1">Önnek már van várakozó kapcsolata ezzel a linkkel: %s</string>
<string name="duplicate_link_dialog_text_1_contact">Önnek már van kapcsolata ezzel a linkkel: %s</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">%s és %s ugyanaz a személy?</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
string will be used in a dialog button, so if the translation of this string is longer than 20
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
@@ -287,7 +287,6 @@ Kérjük frissítsen a legutolsó verzióra és próbálja újra.</string>
will be used in a dialog button, so if the translation of this string longer than 20 characters,
please use "No" instead, and use "Yes" for the "Same Person" button-->
<string name="different_person_button">Másik személy</string>
<string name="duplicate_link_dialog_text_3">%s és %s ugyanazt a linket küldte.\n\nEgyikük lehet, hogy megpróbálja kikutatni, hogy kik a kapcsolatai.\n\nNe árulja el nekik, hogy ugyanazt a linket már megkapta mástól.</string>
<string name="pending_contact_updated_toast">Várakozó kapcsolat frissítve</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Kapcsolatai bemutatása</string>
@@ -661,6 +660,7 @@ Vigyázat: Ez végleg törli az identitásait, kapcsolatait és üzeneteit</stri
<string name="website_troubleshooting_title">Hibaelhárítás</string>
<!--error handling-->
<!--Transfer Data via Removable Drives-->
<string name="removable_drive_menu_title">Csatlakozás eltávolítható adathordozón keresztül</string>
<string name="removable_drive_success_receive_title">Sikeres importálás</string>
<!--Screenshots-->
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->

View File

@@ -42,16 +42,25 @@
<string name="dialog_message_lost_password">Notandaaðgangur þinn í Briar er geymdur dulritaður á tækinu þínu, ekki í tölvuskýi, þannig að við getum ekki endurstillt lykilorðið þitt. Myndirðu vilja eyða notandaaðgangnum þínum og byrja aftur?\n\nVarúð: Auðkennin þín, tengiliðir og skilaboð munu tapast óendurkræft.</string>
<string name="startup_failed_activity_title">Bilun í ræsingu Briar</string>
<string name="startup_failed_clock_error">Briar gat ekki farið af stað því klukka tækisins þíns er ekki rétt stillt.\n\nLeiðréttu klukku tækisins og prófaðu síðan aftur.</string>
<string name="startup_failed_db_error">Briar tókst ekki að opna gagnagrunninn sem inniheldur aðganginn þinn, tengiliðina þína og skilaboð.\n\nAthugaðu hvort Briar sé þegar keyrandi á þessu tæki. Annars skaltu uppfæra í nýjustu útgáfuna og prófa síðan aftur, eða setja upp nýjan aðgang með því að velja \'Ég hef gleymt lykilorðinu mínu\' þar sem spurt er um lykilorð.</string>
<string name="startup_failed_data_too_old_error">Notandaaðgangurinn þinn var útbúinn með eldri útgáfu forritsins og er ekki hægt að opna hann með þessari útgáfu.\n\Þú þarft annað hvort að setja gömlu útgáfuna upp aftur, eða setja upp nýjan notandaaðgang með því að velja \'Ég hef gleymt lykilorðinu mínu\' þegar beðið er um lykilorð.</string>
<string name="startup_failed_data_too_new_error">Notandaaðgangurinn þinn var útbúinn með nýrri útgáfu forritsins og er ekki hægt að opna hann með þessari útgáfu.\n\Þú þarft að setja upp nýjustu útgáfuna og prófa svo aftur.</string>
<string name="startup_failed_service_error">Briar tókst ekki að ræsa nauðsynlega einingu.\n\nUppfærðu í nýjustu útgáfuna og prófaðu síðan aftur.</string>
<plurals name="expiry_warning">
<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>
</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="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="download_briar_button">Ná í nýjustu útgáfu</string>
<string name="old_android_expiry_date_reached">Briar keyrir ekki lengur á Android 4.\nSettu Briar upp á nýrra tæki.</string>
<string name="old_android_delete_account">Þú getur ýtt á hnappinn hér fyrir neðan til að eyða aðgangnum þínum af þessu tæki.</string>
<string name="delete_account_button">Eyða notandaaðgangi</string>
<string name="startup_open_database">Afkóða gagnagrunn…</string>
<string name="startup_migrate_database">Uppfæri gagnagrunn…</string>
<string name="startup_compact_database">Þjappa gagnagrunni…</string>
@@ -278,7 +287,7 @@
<string name="duplicate_link_dialog_text_1">Þú ert þegar með tengilið í bið sem er með þessum tengli: %s</string>
<string name="duplicate_link_dialog_text_1_contact">Þú ert þegar með tengilið með þessum tengli: %s</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">Eru %s og %s sami einstaklingurinn?</string>
<string name="duplicate_link_dialog_text_2">Eru %1$s og %2$s sami einstaklingurinn?</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
string will be used in a dialog button, so if the translation of this string is longer than 20
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
@@ -287,7 +296,7 @@
will be used in a dialog button, so if the translation of this string longer than 20 characters,
please use "No" instead, and use "Yes" for the "Same Person" button-->
<string name="different_person_button">Annar einstaklingur</string>
<string name="duplicate_link_dialog_text_3">%s og %s sendu þér sama tengilinn.\n\nAnnar þeirra gæti verið að reyna að finna út hverjir tengiliðirnir þínir eru.\n\nEkki segja þeim að þú hafir fengið sama tengil frá einhverjum öðrum.</string>
<string name="duplicate_link_dialog_text_3">%1$s og %2$s sendu þér sama tengilinn.\n\nAnnar þeirra gæti verið að reyna að finna út hverjir tengiliðirnir þínir eru.\n\nEkki segja þeim að þú hafir fengið sama tengil frá einhverjum öðrum.</string>
<string name="pending_contact_updated_toast">Tengiliður í bið uppfærður</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Kynntu tengiliðina þína</string>
@@ -314,6 +323,7 @@
<!--Connect via Bluetooth-->
<string name="menu_item_connect_via_bluetooth">Tengjast í gegnum Bluetooth</string>
<string name="connect_via_bluetooth_title">Tengjast í gegnum Bluetooth</string>
<string name="connect_via_bluetooth_intro">Í þeim tilfellum þegar Bluetooth-tengingar virka ekki sjálfkrafa, geturðu notað þennan skjá til að tengjast handvirkt.\n\nTengiliðurinn þinn þarf að vera nálægt svo þetta virkri.\n\nÞið ættuð báðir/bæði að ýta á \"Byrja\" á sama tíma.</string>
<string name="connect_via_bluetooth_already_discovering">Er þegar að reyna að tengjast með Bluetooth. Prófaðu aftur eftir dálitla stund.</string>
<string name="connect_via_bluetooth_no_location_permission">Get ekki haldið áfram án heimildar til að nota staðsetningu</string>
<string name="connect_via_bluetooth_start">Tengist í gegnum Bluetooth…</string>
@@ -606,6 +616,7 @@
<string name="dev_report_memory">Minni</string>
<string name="dev_report_storage">Geymslurými</string>
<string name="dev_report_connectivity">Tengingar</string>
<string name="dev_report_network_usage">Notkun netkerfis</string>
<string name="dev_report_build_config">Byggingaruppsetning</string>
<string name="dev_report_logcat">Atvikaskrá forrits</string>
<string name="dev_report_device_features">Eiginleikar tækis</string>
@@ -648,22 +659,26 @@
<string name="transports_help_text">Briar getur tengst við tengiliðina þína í gegnum internet, Wi-Fi eða Bluetooth.\n\nAllar internettengingar fara í gegnum Tor-netkerfið til að gæta gagnaleyndar.\n\nEf hægt er að nálgast tengilið með mörgum leiðum, notar Briar þær samhliða.</string>
<!--Share app offline-->
<string name="hotspot_title">Deila þessu forriti án nettengingar</string>
<string name="hotspot_button_start_sharing">Ræsa tengipunkt</string>
<string name="hotspot_button_stop_sharing">Stöðva tengipunkt</string>
<string name="hotspot_progress_text_start">Set upp tengipunkt…</string>
<string name="hotspot_notification_channel_title">Þráðlaus Wi-Fi tengipunktur</string>
<string name="hotspot_intro">Deildu þessu forriti til einhvers í næsta nágrenni án internet-tengingar með því að nota Wi-Fi kerfi símans.
\n\nSnjallsíminn þinn mun setja í gang Wi-Fi aðgangsstað. Fólk í nágrenninu getur tengst aðgangsstaðnum og sótt Briar-forritið frá símanum þínum.</string>
<string name="hotspot_button_start_sharing">Ræsa aðgangsstað</string>
<string name="hotspot_button_stop_sharing">Stöðva aðgangsstað</string>
<string name="hotspot_progress_text_start">Set upp aðgangsstað…</string>
<string name="hotspot_notification_channel_title">Þráðlaus Wi-Fi aðgangsstaður</string>
<string name="hotspot_notification_title">Deiling Briar án nettengingar</string>
<string name="hotspot_button_connected">Næsta</string>
<string name="permission_hotspot_location_request_body">Til að útbúa Wi-Fi-aðgangsstað, þarf Briar heimild til að nota staðsetningu þína.\n\nBriar geymir ekki staðsetningar eða deilir þeim með neinum.</string>
<string name="permission_hotspot_location_request_body">Til að útbúa Wi-Fi aðgangsstað, þarf Briar heimild til að nota staðsetningu þína.\n\nBriar geymir ekki staðsetningar eða deilir þeim með neinum.</string>
<string name="permission_hotspot_location_denied_body">Þú hefur hafnað aðgangi að staðsetningu, en Briar þarf þessa heimild til að geta útbúið Wi-Fi-aðgangsstað.\n\nÍhugaðu að veita þennan aðgang.</string>
<string name="wifi_settings_title">Wi-Fi-stillingar</string>
<string name="wifi_settings_request_enable_body">Til að útbúa Wi-Fi aðgangsstað þarf Briar að nota Wi-Fi. Virkjaðu það.</string>
<string name="hotspot_tab_manual">Handvirk</string>
<string name="hotspot_tab_manual">Handvirkt</string>
<!--The placeholder to be inserted into the string 'hotspot_manual_wifi': People can connect by %s-->
<string name="hotspot_scanning_a_qr_code">skönnun QR-kóða</string>
<string name="hotspot_scanning_a_qr_code">skanna QR-kóða</string>
<!--Wi-Fi setup-->
<!--The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code'-->
<string name="hotspot_manual_wifi">Síminn þinn er að reka Wi-Fi aðgangsstað. Fólk sem vill sækja Briar getur tengst aðgangsstaðnum með því að bæta honum við í Wi-Fi stillingum tækisins síns með upplýsingunum hér fyrir neðan eða með því að %s. Þegar þau hafa tengst aðgangsstaðnum er ýtt á \'Næsta\'.</string>
<string name="hotspot_manual_wifi_ssid">Heiti netkerfis</string>
<string name="hotspot_qr_wifi">Síminn þinn er að reka Wi-Fi aðgangsstað. Fólk sem vill sækja Briar getur tengst aðgangsstaðnum með því að skanna þennan QR-kóða. Þegar þau hafa tengst aðgangsstaðnum er ýtt á \'Næsta\'.</string>
<string name="hotspot_no_peers_connected">Engin tæki tengd</string>
<plurals name="hotspot_peers_connected">
<item quantity="one">%s tæki tengt</item>
@@ -671,40 +686,61 @@
</plurals>
<!--Download link-->
<!--The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code'-->
<string name="hotspot_manual_site">Síminn þinn er að reka Wi-Fi aðgangsstað. Fólk sem er tengt aðgangsstaðnum getur sótt Briar með því að skrifa eftirfarandi slóð inn í vafra eða %s.</string>
<string name="hotspot_manual_site_address">Vistfang (URL)</string>
<string name="hotspot_qr_site">Síminn þinn er að reka Wi-Fi aðgangsstað. Fólk sem er tengt aðgangsstaðnum getur sótt Briar með því að skanna þennan QR-kóða.</string>
<!--e.g. Download Briar 1.2.20-->
<string name="website_download_title">Sækja %s</string>
<string name="website_download_intro">Einhver í nágrenninu hefur deilt %s með þér.</string>
<string name="website_download_outro">Eftir að niðurhalinu er lokið, skaltu opna sóttu skrána og setja hana upp.</string>
<string name="website_troubleshooting_title">Lausn á vandamálum</string>
<string name="website_troubleshooting_1">Ef þú getur ekki sótt forritið, ættirðu að prófa það með öðrum vafra.</string>
<string name="website_troubleshooting_2_old">Til að setja upp forritið sem þú sóttir, gætirðu þurft að leyfa uppsetningar á forritum af \"Óþekktum uppruna - Unknown sources\" í kerfisstillingunum. Síðan gætirðu þurft að ná aftur í forritið. Við mælum með að stillingin \"Óþekktur uppruni - Unknown sources\" sé gert óvirkt aftur eftir að forritið hefur verið sett upp.</string>
<string name="website_troubleshooting_2_new">Til að setja upp forritið sem þú sóttir, gætirðu þurft að leyfa vafranum uppsetningar á forritum af óþekktum uppruna. Við mælum með að heimild vafrans til uppsetningar frá óþekktum uppruna sé gert óvirk aftur eftir að forritið hefur verið sett upp.</string>
<string name="hotspot_help_wifi_title">Vandamál við að tengjast við Wi-Fi:</string>
<string name="hotspot_help_wifi_1">Prófaðu að gera Wi-Fi óvirkt og virkja það aftur á báðum símunum og reyndu svo aftur.</string>
<string name="hotspot_help_wifi_2">Ef síminn þinn kvartar yfir að Wi-Fi kerfið sé ekki með neina internettengingu, skaltu segjast vilja samt halda tengingunni.</string>
<string name="hotspot_help_wifi_3">Endurræstu símann sem er að keyra Wi-Fi aðgangsstaðinn, ræstu síðan Briar og prófaðu aftur að deila.</string>
<string name="hotspot_help_site_title">Vandamál við að skoða staðvært vefsvæði:</string>
<string name="hotspot_help_site_1">Gakktu úr skugga um að þú hafir sett vistfangið inn nákvæmlega eins og sýnt er. Smávægileg frávik valta því að þetta virkar ekki.</string>
<string name="hotspot_help_site_2">Gakktu úr skugga um að síminn þinn sé ennþá tengdur við rétta Wi-Fi netið (sjá fyrir ofan) þegar þú reynir að tengjast vefsvæðinu.</string>
<string name="hotspot_help_site_3">Ef þú ert með eldveggjarhugbúnað í gangi, skaltu vera viss um að það sé ekki að loka fyrir aðgang.</string>
<string name="hotspot_help_site_4">Ef þú getur skoðað vefsvæðið en ekki sótt Briar-forritið, ættirðu að prófa það með öðrum vafra.</string>
<string name="hotspot_help_fallback_title">Virkar ekkert?</string>
<string name="hotspot_help_fallback_intro">Þú getur reynt að vista forritið sem .apk skrá til að deila því á einhvern annan máta. Þegar skráin hefur verið færð yfir á hitt tækið, má nota hana til að setja upp Briar.
\n\nÁbending: Til að deila í gegnum Bluetooth, gætirðu fyrst þurft að endurnefna hana þannig að nafnið endi á .zip.</string>
<string name="hotspot_help_fallback_button">Vista forrit</string>
<!--error handling-->
<string name="hotspot_error_intro">Eitthvað fór úrskeiðis þegar reynt var að deila forritinu með Wi-Fi:</string>
<string name="hotspot_error_no_wifi_direct">Þetta tæki styður ekki beint Wi-Fi samband.</string>
<string name="hotspot_error_start_callback_failed">Tengipunktur ræstist ekki: villa %s</string>
<string name="hotspot_error_start_callback_failed_unknown">Tengipunktur ræstist ekki vegna óþekktrar villu, ástæðan er %d</string>
<string name="hotspot_error_start_callback_no_group_info">Tengipunktur ræstist ekki: engar hópupplýsingar</string>
<string name="hotspot_error_start_callback_failed">Aðgangsstaður ræstist ekki: villa %s</string>
<string name="hotspot_error_start_callback_failed_unknown">Aðgangsstaður ræstist ekki vegna óþekktrar villu, ástæðan er %d</string>
<string name="hotspot_error_start_callback_no_group_info">Aðgangsstaður ræstist ekki: engar hópupplýsingar</string>
<string name="hotspot_error_web_server_start">Villa við að ræsa vefþjón</string>
<string name="hotspot_error_web_server_serve">Villa við að birta vefsvæði.\n\nSendu umsögn (með naflausum gögnum) í gegnum Briar forritið ef vandamálið er viðvarandi.</string>
<string name="hotspot_flag_test">Aðvörun: Þetta forrit var sett upp með Android Studio og er EKKI hægt að setja upp á öðru tæki.</string>
<string name="hotspot_error_framework_busy">Tekst ekki að ræsa aðgangsstaðinn.\n\nEf þú ert að keyra annan aðgangsstað eða ert að deila internettengingunni þinni í gegnum Wi-Fi, skaltu slökkva á því og prófa síðan aftur.</string>
<!--Transfer Data via Removable Drives-->
<string name="removable_drive_menu_title">Tengjast í gegnum útskiptanlegt drif</string>
<string name="removable_drive_intro">Ef þú nærð ekki að tengjast tengiliðnum þínum í gegnum internet, Wi-Fi eða Bluetooth, getur Briar einnig flutt skilaboð á útskiptanleg drif á borð við USB-minnislykil eða SD-minniskort.</string>
<string name="removable_drive_explanation">Ef þú nærð ekki að tengjast tengiliðnum þínum í gegnum internet, Wi-Fi eða Bluetooth, getur Briar einnig flutt skilaboð á útskiptanleg drif á borð við USB-minnislykil eða SD-minniskort.\n\nÞegar þú sérð hnappinn \"Senda gögn\", verða öll gögn sem bíða eftir að verða send til tengiliðarins skrifuð á útskiptanlega drifið. Þar má telja einkaskilaboð, viðhengi, blogg, spjallsvæði og einkahópar.\n\nAlltsaman verður dulritað áður en það er skrifað á útskiptanlega drifið.\n\nÞegar tengiliðurinn fær svo útskiptanlega drifið í hendurnar, getur hann notað hnappinn \"Taka við gögnum\" til að flytja öll skilaboðin inn í Briar.</string>
<string name="removable_drive_title_send">Senda gögn</string>
<string name="removable_drive_title_receive">Taka við gögnum</string>
<string name="removable_drive_send_intro">Ýttu á hnappinn hér fyrir neðan til að útbúa nýja skrá sem inniheldur dulrituðu skilaboðin. Þú getur valið hvar skráin verður vistuð.\n\nEf þú ætlar að vista skrána á útskiptanlegt drif, skaltu setja það í samband núna.</string>
<string name="removable_drive_send_no_data">Það eru engin skilaboð sem bíða eftir að vera send til þessa tengiliðar.</string>
<string name="removable_drive_send_not_supported">Þessi tengiliður er að nota gamla útgáfu af Briar eða gamalt tæki sem styður ekki þennan eiginleika.</string>
<string name="removable_drive_send_button">Veldu skrá til að flytja út</string>
<string name="removable_drive_ongoing">Bíddu eftir að verk sem er í gangi klárist</string>
<string name="removable_drive_receive_intro">Ýttu á hnappinn hér fyrir neðan til að velja skrána sem tengiliðurinn sendi þér.\n\nEf skráin er á útskiptanlegu drifi, skaltu tengja það núna.</string>
<string name="removable_drive_receive_button">Veldu skrá til að flytja inn</string>
<string name="removable_drive_success_send_title">Útflutningur tókst</string>
<string name="removable_drive_success_send_text">Tókst að flytja út gögn. Þú hefur núna 28 daga til að koma skránni til tengiliðarins þíns.\n\nEf skráin er á útskiptanlegu drifi, skaltu nota táknið í stöðustikunni til að spýta út drifinu áður en þú aftengir það.</string>
<string name="removable_drive_success_receive_title">Innflutningur tókst</string>
<string name="removable_drive_success_receive_text">Öll dulrituð skilaboð inni í þessari skrá hafa verið móttekin.</string>
<string name="removable_drive_error_send_title">Villa við útflutning gagna</string>
<string name="removable_drive_error_send_text">Villa kom upp við að skrifa gögn í skrána.\n\nEf þú ert að nota útskiptanlegt drif, gakkti þá úr skugga um að það sé rétt tengt og prófaðu svo aftur.\n\nEf villan heldur áfram að birtast, skaltu endilega senda Briar-teyminu línu og láta þau vita af vandamálinu.</string>
<string name="removable_drive_error_receive_title">Villa í innflutningi gagna</string>
<string name="removable_drive_error_receive_text">Valda skráin innihélt ekkert sem Briar gat lagt kennsl á.\n\nVertu viss um að þú hafir valið rétta skrá.\n\nEf tengiliðurinn þinn útbjó skrána fyrir meira en 28 dögum síðan, mun Briar ekki geta þekkt hana.</string>
<!--Screenshots-->
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
<string name="screenshot_alice">Lísa</string>

View File

@@ -50,10 +50,17 @@
<item quantity="one">Questa è una versione di prova di Briar. Il tuo account scadrà fra %d giorno e non può essere rinnovato.</item>
<item quantity="other">Questa è una versione di prova di Briar. Il tuo account scadrà fra %d giorni e non può essere rinnovato.</item>
</plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Android 4 non è più supportato. Briar smetterà di funzionare il %s (tra %d giorno). Installa Briar in un dispositivo più recente e crea un nuovo account.</item>
<item quantity="other">Android 4 non è più supportato. Briar smetterà di funzionare il %s (tra %d giorni). Installa Briar in un dispositivo più recente e crea un nuovo account.</item>
</plurals>
<string name="expiry_date_reached">Questo software è scaduto.\nGrazie per il test!</string>
<string name="download_briar">Per continuare a utilizzare Briar, scarica l\'ultima versione.</string>
<string name="create_new_account">Avrai bisogno di creare un nuovo account, ma puoi usare lo stesso nickname.</string>
<string name="download_briar_button">Scarica l\'ultima versione</string>
<string name="old_android_expiry_date_reached">Briar non funziona più su Android 4.\nInstallalo in un dispositivo più recente.</string>
<string name="old_android_delete_account">Puoi toccare il pulsante sottostante per eliminare il tuo account da questo dispositivo.</string>
<string name="delete_account_button">Cancella Account</string>
<string name="startup_open_database">Decrittazione del database...</string>
<string name="startup_migrate_database">Aggiornamento del database...</string>
<string name="startup_compact_database">Compattazione del database…</string>
@@ -280,7 +287,7 @@
<string name="duplicate_link_dialog_text_1">Hai già un contatto in attesa con questo link: %s</string>
<string name="duplicate_link_dialog_text_1_contact">Hai già un contatto con questo link: %s</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">%s e %s sono la stessa persona?</string>
<string name="duplicate_link_dialog_text_2">Sono %1$s e %2$s la stessa persona?</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
string will be used in a dialog button, so if the translation of this string is longer than 20
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
@@ -289,30 +296,30 @@
will be used in a dialog button, so if the translation of this string longer than 20 characters,
please use "No" instead, and use "Yes" for the "Same Person" button-->
<string name="different_person_button">Persone diverse</string>
<string name="duplicate_link_dialog_text_3">%s e %s ti hanno inviato lo stesso link.\n\nUno dei due potrebbe tentare di scoprire chi sono i tuoi contatti.\n\nNon dirgli che hai ricevuto lo stesso link da qualcun altro.</string>
<string name="duplicate_link_dialog_text_3">%1$s e %2$s ti hanno inviato lo stesso link.\n\nUno dei due potrebbe tentare di scoprire chi sono i tuoi contatti.\n\nNon dirgli che hai ricevuto lo stesso link da qualcun altro.</string>
<string name="pending_contact_updated_toast">Contatto in attesa aggiornato</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Introduzione tuoi contatti</string>
<string name="introduction_onboarding_title">Presenta i tuoi contatti</string>
<string name="introduction_onboarding_text">Puoi presentare i tuoi contatti fra di loro, così non hanno bisogno di incontrarsi di persona per connettersi a Briar.</string>
<string name="introduction_menu_item">Crea l\'introduzione</string>
<string name="introduction_activity_title">Seleziona Contatto</string>
<string name="introduction_not_possible">Hai già un introduzione in corso con questi contatti. Si prega di consentire che prima questo finisca. Se tu o i tuoi contatti sono raramente online, questo potrebbe richiedere un po\' di tempo.</string>
<string name="introduction_message_title">Introduzione Contatti</string>
<string name="introduction_not_possible">Ti stai già presentando con questi contatti. Prima attendi che finisca. Se tu o i tuoi contatti siete raramente online, potrebbe volerci un po\' di tempo.</string>
<string name="introduction_message_title">Presenta i contatti</string>
<string name="introduction_message_hint">Aggiungi un messaggio (facoltativo)</string>
<string name="introduction_button">Crea l\'introduzione</string>
<string name="introduction_sent">La tua introduzione è stata inviata.</string>
<string name="introduction_sent">La tua presentazione è stata inviata.</string>
<string name="introduction_error">C\'è stato un errore nella creazione dell\'introduzione</string>
<string name="introduction_request_sent">Hai richiesto di introdurre %1$s a %2$s.</string>
<string name="introduction_request_received">%1$s ha chiesto di introdurti in %2$s. Vuoi aggiungere %2$s alla tua lista contatti?</string>
<string name="introduction_request_exists_received">%1$s ha chiesto di introdurti in %2$s, ma %2$s è già nella tua lista contatti. Dato che %1$s p non saperlo, puoi comunque rispondere:</string>
<string name="introduction_request_answered_received">%1$s ha richiesto di introdurti a %2$s.</string>
<string name="introduction_response_accepted_sent">Hai accettato l\'introduzione a %1$s.</string>
<string name="introduction_request_sent">Hai chiesto di presentare %1$s a %2$s.</string>
<string name="introduction_request_received">%1$s ha chiesto di presentarti a %2$s. Vuoi aggiungere %2$s alla tua lista contatti?</string>
<string name="introduction_request_exists_received">%1$s ha chiesto di presentarti a %2$s, ma %2$s è già nella tua lista contatti. Dato che %1$s potrebbe non saperlo, puoi comunque rispondere:</string>
<string name="introduction_request_answered_received">%1$s ha chiesto di presentarti a %2$s.</string>
<string name="introduction_response_accepted_sent">Hai accettato di presentarti a %1$s.</string>
<string name="introduction_response_accepted_sent_info">Prima che %1$s venga aggiunto ai tuoi contatti, dovranno anche loro accettare l\'introduzione. Questo potrebbe richiedere un po\' di tempo.</string>
<string name="introduction_response_declined_sent">Hai declinato l\'introduzione a %1$s.</string>
<string name="introduction_response_declined_sent">Hai rifiutato di presentarti a %1$s.</string>
<string name="introduction_response_declined_auto">L\'introduzione a %1$s è stata rifiutata automaticamente.</string>
<string name="introduction_response_accepted_received">%1$s ha accettato l\'introduzione a %2$s.</string>
<string name="introduction_response_declined_received">%1$s ha declinato l\'introduzione a %2$s.</string>
<string name="introduction_response_declined_received_by_introducee">%1$s dice che %2$s ha declinato l\'introduzione.</string>
<string name="introduction_response_accepted_received">%1$s ha accettato di presentarsi a %2$s.</string>
<string name="introduction_response_declined_received">%1$s ha rifiutato di presentarsi a %2$s.</string>
<string name="introduction_response_declined_received_by_introducee">%1$s dice che %2$s ha rifiutato di presentarsi.</string>
<!--Connect via Bluetooth-->
<string name="menu_item_connect_via_bluetooth">Connessione attraverso Bluetooth</string>
<string name="connect_via_bluetooth_title">Connessione attraverso Bluetooth</string>
@@ -609,6 +616,7 @@
<string name="dev_report_memory">Memoria</string>
<string name="dev_report_storage">Spazio</string>
<string name="dev_report_connectivity">Connettività</string>
<string name="dev_report_network_usage">Utilizzo della rete</string>
<string name="dev_report_build_config">Configurazione build</string>
<string name="dev_report_logcat">Registro app</string>
<string name="dev_report_device_features">Caratteristiche dispositivo</string>

View File

@@ -47,6 +47,7 @@
<string name="download_briar">Briarの使用を続けるならば、最新リリースをダウンロードしてください。</string>
<string name="create_new_account">新しいアカウントを作成する必要があります。同じニックネームも使用できます。</string>
<string name="download_briar_button">最新版をダウンロード</string>
<string name="delete_account_button">アカウントを削除</string>
<string name="startup_open_database">データベースの復号化中…</string>
<string name="startup_migrate_database">データベースをアップグレード中…</string>
<string name="startup_compact_database">データベースの圧縮中…</string>
@@ -258,7 +259,6 @@
<string name="duplicate_link_dialog_text_1">既に保留中の連絡先があります。リンク:%s</string>
<string name="duplicate_link_dialog_text_1_contact">既に連絡先があります。リンク:%s</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">%sと%sは同じ人ですか</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
string will be used in a dialog button, so if the translation of this string is longer than 20
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
@@ -267,7 +267,6 @@
will be used in a dialog button, so if the translation of this string longer than 20 characters,
please use "No" instead, and use "Yes" for the "Same Person" button-->
<string name="different_person_button">別の人</string>
<string name="duplicate_link_dialog_text_3">%sと%sから同じリンクを受信しました。\n\nどちらかがあなたの連絡先の内容を知ろうとしている可能性があります。\n\n他の人から同じリンクを受け取ったことを伝えないでください。</string>
<string name="pending_contact_updated_toast">保留中の連絡先が更新されました</string>
<!--Introductions-->
<string name="introduction_onboarding_title">連絡先を紹介</string>
@@ -613,8 +612,6 @@
<string name="transports_help_text">Briarは、インターネット、Wi-Fi、Bluetoothを介して連絡先に接続することができます。\n\nすべてのインターネット接続は、プライバシー保護のためにTorネットワークを経由します。\n\n複数の方法で連絡が取れる場合、Briarはそれらを並行して使用します。</string>
<!--Share app offline-->
<string name="hotspot_title">このアプリをオフラインで共有</string>
<string name="hotspot_intro">あなたの電話機のWi-Fiを使用して、インターネット接続なしで、近くの誰かとこのアプリを共有します。
\n\nあなたの電話機はWi-Fiホットスポットになります。近くの人はホットスポットへ接続し、あなたの電話機からBriarアプリをダウンロードできます。</string>
<string name="hotspot_button_start_sharing">ホットスポットを開始</string>
<string name="hotspot_button_stop_sharing">ホットスポットを停止</string>
<string name="hotspot_progress_text_start">ホットスポットを設定する…</string>
@@ -652,7 +649,8 @@
<string name="website_troubleshooting_2_new">ダウンロードしたアプリをインストールするには、ブラウザに不明なアプリのインストールを許可する必要がある場合があります。アプリをインストールした後は、ブラウザの不明なアプリのインストール許可を解除することをお勧めします。</string>
<string name="hotspot_help_wifi_title">W-Fi接続の問題:</string>
<string name="hotspot_help_wifi_1">双方の電話機でWi-Fiを無効にして、再び有効にするのを試してください。</string>
<string name="hotspot_help_wifi_2">もし携帯電話が「Wi-Fiにはインターネットがない」と不満を訴えてきたら、「とにかく接続していたい」と伝えてください。</string>
<string name="hotspot_help_wifi_2">もし電話が「Wi-Fiにはインターネットがない」と不満を訴えてきたら、「とにかく接続していたい」と伝えてください。</string>
<string name="hotspot_help_wifi_3">Wi-Fiホットスポットが実行中の電話機を再起動して、Briarの起動と共有を再び試してください。</string>
<string name="hotspot_help_site_title">ローカルのウェブサイト訪問の問題:</string>
<string name="hotspot_help_site_1">表示されている通りにアドレスを入力したかどうかを再確認してください。小さなミスで失敗することがあります。</string>
<string name="hotspot_help_site_2">サイトにアクセスする際に、電話機が正しいWi-Fi上記を参照に接続されていることを確認してください。</string>

View File

@@ -5,7 +5,7 @@
<string name="setup_name_explanation">Jūsų slapyvardis bus rodomas šalia bet kokio jūsų skelbiamo turinio. Sukūrę paskyrą, slapyvardžio pakeisti nebegalėsite.</string>
<string name="setup_next">Kitas</string>
<string name="setup_password_intro">Pasirinkite slaptažodį</string>
<string name="setup_password_explanation">Jūsų Briar paskyra yra saugoma šifruotu pavidalu jūsų įrenginyje, o ne debesijoje. Jei pamiršite savo slaptažodį ar pašalinsite Briar programėlę, daugiau nebegalėsite atkurti savo paskyros.\n\nPasirinkite ilgą slaptažodį, kurį sunku atspėti, kaip pavyzdžiui, keturis atsitiktinius žodžius ar dešimt atsitiktinių raidžių, skaitmenų ir simbolių.</string>
<string name="setup_password_explanation">Jūsų Briar paskyra yra saugoma šifruotu pavidalu jūsų įrenginyje, o ne debesijoje. Jei pamiršite savo slaptažodį ar pašalinsite Briar programėlę, daugiau nebegalėsite atkurti savo paskyros.\n\nPasirinkite ilgą slaptažodį, kurį būtų sunku atspėti, pavyzdžiui, keturis atsitiktinius žodžius ar dešimt atsitiktinių raidžių, skaitmenų ir simbolių.</string>
<string name="setup_doze_title">Foniniai ryšiai</string>
<string name="setup_doze_intro">Norint gauti žinutes, Briar turi išlikti fone prisijungusi.</string>
<string name="setup_doze_explanation">Norint gauti žinutes, Briar turi išlikti fone prisijungusi. Išjunkite akumuliatoriaus naudojimo optimizavimą, kad Briar galėtų išlikti prisijungusi.</string>
@@ -19,7 +19,7 @@
<string name="create_account_button">Sukurti paskyrą</string>
<string name="more_info">Daugiau informacijos</string>
<string name="don_t_ask_again">Daugiau nebeklausti</string>
<string name="setup_huawei_text">Bakstelėkite žemiau esantį mygtuką ir įsitikinkite, kad \"Apsaugotų programėlių\" rodinyje Briar yra apsaugota.</string>
<string name="setup_huawei_text">Bakstelėkite žemiau esantį mygtuką ir įsitikinkite, kad Apsaugotų programėlių rodinyje Briar yra apsaugota.</string>
<string name="setup_huawei_button">Apsaugoti Briar</string>
<string name="setup_huawei_help">Jei Briar nebus pridėta į apsaugotų programėlių sąrašą, ji negalės veikti fone.</string>
<string name="setup_huawei_app_launch_text">Bakstelėkite mygtuką žemiau, atverkite langą „Programų paleidimas (angl. App launch)“ ir įsitikinkite, kad Briar yra nustatyta į „Tvarkyti rankiniu būdu (angl. Manage manually)“.</string>
@@ -39,7 +39,7 @@
<string name="sign_in_button">Prisijungti</string>
<string name="forgotten_password">Aš pamiršau savo slaptažodį</string>
<string name="dialog_title_lost_password">Prarastas slaptažodis</string>
<string name="dialog_message_lost_password">Jūsų Briar paskyra yra saugoma šifruotu pavidalu jūsų įrenginyje, o ne debesijoje, taigi, negalime atstatyti jūsų slaptažodžio. Ar norėtumėte ištrinti savo paskyrą ir pradėti iš naujo?\n\nDėmesio: Jūsų tapatybės, žinutės ir adresatai bus prarasti visiems laikams.</string>
<string name="dialog_message_lost_password">Jūsų Briar paskyra yra saugoma šifruotu pavidalu jūsų įrenginyje, o ne debesijoje, todėl negalime atstatyti jūsų slaptažodžio. Ar norėtumėte ištrinti savo paskyrą ir pradėti iš naujo?\n\nDėmesio: Jūsų tapatybės, žinutės ir adresatai bus prarasti visiems laikams.</string>
<string name="startup_failed_activity_title">Briar paleidimo nesėkmė</string>
<string name="startup_failed_clock_error">Briar nepavyko pasileisti, nes jūsų įrenginio laikrodis rodo neteisingą laiką.\n\nNustatykite savo įrenginyje teisingą laiką ir bandykite dar kartą.</string>
<string name="startup_failed_db_error">Briar nepavyko atverti duomenų bazės su jūsų paskyra, adresatais ir žinutėmis.\n\nAtnaujinkite programėlę į naujausią versiją ir bandykite dar kartą arba nusistatykite naują paskyrą, slaptažodžio užklausos ekrane pasirinkę „Aš pamiršau savo slaptažodį“.</string>
@@ -52,10 +52,19 @@
<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>
</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="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="download_briar_button">Atsisiųsti naujausią laidą</string>
<string name="old_android_expiry_date_reached">Briar daugiau nebeveikia įrenginiuose su „Android“ 4.\nĮsidiekite Briar naujesniame įrenginyje.</string>
<string name="old_android_delete_account">Galite bakstelėti ant žemiau esančio mygtuko, norėdami ištrinti paskyrą iš šio įrenginio.</string>
<string name="delete_account_button">Ištrinti paskyrą</string>
<string name="startup_open_database">Iššifruojama duomenų bazė…</string>
<string name="startup_migrate_database">Naujinama duomenų bazė…</string>
<string name="startup_compact_database">Suspaudžiama duomenų bazė…</string>
@@ -161,7 +170,7 @@
<string name="error">Klaida</string>
<!--Contacts and Private Conversations-->
<string name="no_contacts">Nėra rodytinų adresatų</string>
<string name="no_contacts_action">Norėdami pridėti adresatą, bakstelėkite + piktogramą</string>
<string name="no_contacts_action">Norėdami pridėti adresatą, bakstelėkite „+“ piktogramą</string>
<string name="date_no_private_messages">Žinučių nėra.</string>
<string name="no_private_messages">Nėra rodytinų žinučių</string>
<string name="message_hint">Nauja žinutė</string>
@@ -298,7 +307,7 @@
<string name="duplicate_link_dialog_text_1">Jūs jau turite laukiančią adresato užklausą su šia nuoroda: %s</string>
<string name="duplicate_link_dialog_text_1_contact">Jau turite adresatą su šia nuoroda: %s</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">Ar %s ir %s yra tas pats asmuo?</string>
<string name="duplicate_link_dialog_text_2">Ar %1$s ir %2$s yra tas pats asmuo?</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
string will be used in a dialog button, so if the translation of this string is longer than 20
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
@@ -307,7 +316,7 @@
will be used in a dialog button, so if the translation of this string longer than 20 characters,
please use "No" instead, and use "Yes" for the "Same Person" button-->
<string name="different_person_button">Kitas asmuo</string>
<string name="duplicate_link_dialog_text_3">%s ir %s išsiuntė jums tą pačią nuorodą.\n\nGali būti, kad vienas iš šių asmenų bando sužinoti kas yra jūsų adresatų sąraše.\n\nNesakykite šiems asmenims, kad gavote tokią pačią nuorodą iš kito asmens.</string>
<string name="duplicate_link_dialog_text_3">%1$s ir %2$s išsiuntė jums tą pačią nuorodą.\n\nGali būti, kad vienas iš šių asmenų bando sužinoti kas yra jūsų adresatų sąraše.\n\nNesakykite šiems asmenims, kad gavote tokią pačią nuorodą iš kito asmens.</string>
<string name="pending_contact_updated_toast">Laukiantis adresatas atnaujintas</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Supažindinkite savo adresatus</string>
@@ -343,7 +352,7 @@
<string name="connect_via_bluetooth_error_not_supported">Įrenginys nepalaiko Bluetooth.</string>
<!--Private Groups-->
<string name="groups_list_empty">Nėra rodytinų grupių</string>
<string name="groups_list_empty_action">Norėdami sukurti grupę ar paprašyti savo adresatų pradėti su jumis bendrinti grupes, bakstelėkite + piktogramą</string>
<string name="groups_list_empty_action">Norėdami sukurti grupę ar paprašyti savo adresatų pradėti su jumis bendrinti grupes, bakstelėkite „+“ piktogramą</string>
<string name="groups_created_by">Sukūrė %s</string>
<plurals name="messages">
<item quantity="one">%d žinutė</item>
@@ -370,14 +379,14 @@
<string name="groups_leave_dialog_message">Ar tikrai norite išeiti iš šios grupės?</string>
<string name="groups_dissolve">Išformuoti grupę</string>
<string name="groups_dissolve_dialog_title">Patvirtinkite grupės išformavimą</string>
<string name="groups_dissolve_dialog_message">Ar tikrai norite išformuoti šią grupę?\n\nVisi kiti nariai negalės tęsti savo pokalbio ir gali nebegauti naujausių žinučių.</string>
<string name="groups_dissolve_dialog_message">Ar tikrai norite išformuoti šią grupę?\n\nVisi kiti nariai nebegalės tęsti savo pokalbio ir gali nebegauti naujausių žinučių.</string>
<string name="groups_dissolve_button">Išformuoti</string>
<string name="groups_dissolved_dialog_title">Grupė išformuota</string>
<string name="groups_dissolved_dialog_message">Šios grupės įkūrėjas ją išformavo.\n\nJūs daugiau nebegalite rašyti žinučių į šią grupę ir galite nebegauti visų parašytų įrašų.</string>
<!--Private Group Invitations-->
<string name="groups_invitations_title">Pakvietimai į grupę</string>
<string name="groups_invitations_invitation_sent">Jūs pakvietėte, kad %1$s prisijungtų prie grupės \"%2$s\".</string>
<string name="groups_invitations_invitation_received">%1$s pakvietė jus prisijungti prie grupės \"%2$s\".</string>
<string name="groups_invitations_invitation_sent">Jūs pakvietėte, kad %1$s prisijungtų prie grupės %2$s.</string>
<string name="groups_invitations_invitation_received">%1$s pakvietė jus prisijungti prie grupės %2$s.</string>
<string name="groups_invitations_joined">Prisijungta prie grupės</string>
<string name="groups_invitations_declined">Pakvietimas į grupę atmestas</string>
<plurals name="groups_invitations_open">
@@ -401,7 +410,7 @@
<string name="groups_reveal_invisible">Adresatų sąryšiai nėra matomi grupei</string>
<!--Forums-->
<string name="no_forums">Nėra rodytinų forumų</string>
<string name="no_forums_action">Norėdami sukurti forumą ar paprašyti savo adresatų pradėti su jumis bendrinti forumus, bakstelėkite + piktogramą</string>
<string name="no_forums_action">Norėdami sukurti forumą ar paprašyti savo adresatų pradėti su jumis bendrinti forumus, bakstelėkite „+“ piktogramą</string>
<string name="create_forum_title">Sukurkite forumą</string>
<string name="choose_forum_hint">Pasirinkite savo forumo pavadinimą</string>
<string name="create_forum_button">Sukurti forumą</string>
@@ -431,14 +440,14 @@
<string name="forum_shared_snackbar">Forumas bendrinamas su pasirinktais adresatais</string>
<string name="forum_share_message">Pridėkite žinutę (nebūtina)</string>
<string name="forum_share_error">Bendrinant šį forumą, įvyko klaida.</string>
<string name="forum_invitation_received">%1$s pradėjo bendrinti su jumis forumą \"%2$s\".</string>
<string name="forum_invitation_sent">Jūs pradėjote bendrinti su %2$s forumą \"%1$s\".</string>
<string name="forum_invitation_received">%1$s pradėjo bendrinti su jumis forumą %2$s.</string>
<string name="forum_invitation_sent">Jūs pradėjote bendrinti su %2$s forumą %1$s.</string>
<string name="forum_invitations_title">Pakvietimai į forumą</string>
<string name="forum_invitation_exists">Jūs jau priėmėte pakvietimą į šį forumą.\n\nPriėmus daugiau pakvietimų, jūsų ryšys su forumu taps greitesnis ir patikimesnis.</string>
<string name="forum_joined_toast">Prisijungta prie forumo</string>
<string name="forum_declined_toast">Pakvietimas atmestas</string>
<string name="shared_by_format">Bendrina %s</string>
<string name="forum_invitation_already_sharing">Jau bendrinamas</string>
<string name="forum_invitation_already_sharing">Jau bendrinama</string>
<string name="forum_invitation_response_accepted_sent">Jūs priėmėte pakvietimą į forumą iš %s.</string>
<string name="forum_invitation_response_declined_sent">Jūs atmetėte pakvietimą į forumą iš %s.</string>
<string name="forum_invitation_response_declined_auto">Pakvietimas į forumą nuo %s buvo automatiškai atmestas.</string>
@@ -481,8 +490,8 @@
<string name="blogs_sharing_response_declined_auto">Pakvietimas į tinklaraštį nuo %s buvo automatiškai atmestas.</string>
<string name="blogs_sharing_response_accepted_received">%s priėmė pakvietimą į tinklaraštį.</string>
<string name="blogs_sharing_response_declined_received">%s atmetė pakvietimą į tinklaraštį.</string>
<string name="blogs_sharing_invitation_received">%1$s pradėjo bendrinti su jumis tinklaraštį \"%2$s\".</string>
<string name="blogs_sharing_invitation_sent">Jūs pradėjote bendrinti su %2$s tinklaraštį \"%1$s\".</string>
<string name="blogs_sharing_invitation_received">%1$s pradėjo bendrinti su jumis tinklaraštį %2$s.</string>
<string name="blogs_sharing_invitation_sent">Jūs pradėjote bendrinti su %2$s tinklaraštį %1$s.</string>
<string name="blogs_sharing_invitations_title">Pakvietimai į tinklaraštį</string>
<string name="blogs_sharing_joined_toast">Tinklaraštis užprenumeruotas</string>
<string name="blogs_sharing_declined_toast">Pakvietimas atmestas</string>
@@ -500,7 +509,7 @@
<string name="blogs_rss_remove_feed">Šalinti kanalą</string>
<string name="blogs_rss_remove_feed_dialog_message">Ar tikrai norite pašalinti šį kanalą?\n\nĮrašai bus pašalinti iš jūsų įrenginio, tačiau liks kitų žmonių įrenginiuose.\n\nBet kokie adresatai, su kuriais bendrinote šį kanalą, gali nustoti gauti atnaujinimus.</string>
<string name="blogs_rss_remove_feed_ok">Šalinti</string>
<string name="blogs_rss_feeds_manage_empty_state">Nėra rodytinų RSS kanalų\n\nNorėdami importuoti kanalą, bakstelėkite + piktogramą</string>
<string name="blogs_rss_feeds_manage_empty_state">Nėra rodytinų RSS kanalų\n\nNorėdami importuoti kanalą, bakstelėkite „+“ piktogramą</string>
<string name="blogs_rss_feeds_manage_error">Įkeliant jūsų kanalus, atsirado problemų. Vėliau bandykite dar kartą.</string>
<!--Settings Profile Picture-->
<string name="change_profile_picture">Bakstelėkite norėdami pasikeisti profilio paveikslą</string>
@@ -635,6 +644,7 @@
<string name="dev_report_memory">Atmintis</string>
<string name="dev_report_storage">Saugykla</string>
<string name="dev_report_connectivity">Jungiamumas</string>
<string name="dev_report_network_usage">Tinklo naudojimas</string>
<string name="dev_report_build_config">Darinio konfigūracija</string>
<string name="dev_report_logcat">Programėlės žurnalas</string>
<string name="dev_report_device_features">Įrenginio ypatybės</string>

View File

@@ -53,6 +53,7 @@
<string name="download_briar">Briar ကို ဆက်လက်အသုံးပြုရန် နောက်ဆုံးထွက်ထားသည်ကို ဒေါင်းလုဒ်လုပ်ပါ။</string>
<string name="create_new_account">အကောင့်အသစ်ဖွင့်ရန် လိုအပ်သော်လည်း သုံးလက်စနာမည်ပြောင်ကို ဆက်သုံးနိုင်ပါသည်။</string>
<string name="download_briar_button">နောက်ဆုံးထွက်ထားသည်ကို ဒေါင်းလုဒ်လုပ်မည်</string>
<string name="delete_account_button">အကောင့် ဖျက်သိမ်းမယ်</string>
<string name="startup_open_database">အချက်အလက်အစုကို ပြန်ဖြည်နေသည်…</string>
<string name="startup_migrate_database">အချက်အလက်အစုကို အဆင့်မြှင့်နေသည်…</string>
<string name="startup_compact_database">အချက်အလက်အစုကို သိပ်သည်းစေရန်လုပ်နေသည်…</string>
@@ -207,7 +208,7 @@
<string name="dialog_title_image_support">ရုပ်ပုံများကို ဤအဆက်အသွယ်လိပ်စာသို့ ပို့လို့ရပါပြီ</string>
<string name="dialog_message_image_support">ဤအိုင်ကွန်ကို နှိပ်ပြီး ရုပ်ပုံများကို ပူးတွဲပါ</string>
<string name="messaging_too_many_attachments_toast">ပထမဆုံး %d ရုပ်ပုံများသာလျှင် ပို့ပါမည်</string>
<string name="menu_contact">ဆက်သွယ်</string>
<string name="menu_contact">ဆက်သွယ်ရန်</string>
<!--Adding Contacts-->
<string name="add_contact_title">အနီးနားရှိ အဆက်အသွယ်အား ထည့်သွင်းမယ်</string>
<string name="face_to_face">သင်သည် ဤလူပုဂ္ဂိုလ်အား အပြင်မှာတွေ့ရှိမှသာလျှင် ၎င်း၏အဆက်အသွယ်ကို ပေါင်းထည့်လို့ရပါမည်။ \n\n ဒါမှသာလျှင် နောင်တွင် အခြားလူများ သင့်အား အယောင်ဆောင်ခြင်း သို့မဟုတ် သင့်မက်ဆေ့ချ်များဖတ်ရှုခြင်း တို့ကို တားဆီးနိုင်ပါမည်။</string>
@@ -270,7 +271,6 @@
<string name="duplicate_link_dialog_text_1">သင့်မှာ ဒီလင့်ခ်နဲ့ ပတ်သက်ပြီး ဆိုင်းငံ့ထားသော အဆက်အသွယ်လိပ်စာ ရှိပြီးဖြစ်သည်: %s</string>
<string name="duplicate_link_dialog_text_1_contact">သင့်မှာ ဒီလင့်ခ်နဲ့ အဆက်အသွယ်လိပ်စာ ရှိပြီးဖြစ်သည်: %s</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">%s နှင့် %s သည် တစ်ဦးတည်း ဖြစ်ပါသလား?</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
string will be used in a dialog button, so if the translation of this string is longer than 20
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
@@ -279,7 +279,6 @@
will be used in a dialog button, so if the translation of this string longer than 20 characters,
please use "No" instead, and use "Yes" for the "Same Person" button-->
<string name="different_person_button">နောက်တစ်ယောက် </string>
<string name="duplicate_link_dialog_text_3">%s နှင့် %s သည် သင့်စီသို့ တူညီသောလင့်ခ်တစ်ခု မျှဝေခဲ့ပါသည်။ \n\n သူတို့ထဲမှတစ်ဦးသည် သင့်အဆက်အသွယ်များကို ရှာဖွေနေနိုင်ပါသည်။ \n\n သူတို့အား နောက်တစ်ဦးစီမှ တူညီသောလင့်ခ်ရခဲ့သည်ကို မပြောပြပါနှင့်။</string>
<string name="pending_contact_updated_toast">ဆိုင်းငံ့ထားသော အဆက်အသွယ်ကို အပ်ဒိတ်လုပ်ပြီး</string>
<!--Introductions-->
<string name="introduction_onboarding_title">သင့်ရဲ့ အဆက်အသွယ်လိပ်စာများကို မိတ်ဆက်ပါ</string>
@@ -305,8 +304,8 @@
<string name="introduction_response_declined_received_by_introducee">%2$s မှ မိတ်ဆက်ခြင်းအား ငြင်းဆိုခဲ့သည်ကို %1$s မှ ပြောပါသည်။</string>
<!--Connect via Bluetooth-->
<string name="menu_item_connect_via_bluetooth">ဘလူးတုသ် နှင့် ချိတ်မယ်</string>
<string name="connect_via_bluetooth_title">ဘလူးတုသ် နှင့် ချိတ်မ</string>
<string name="connect_via_bluetooth_no_location_permission">တည်နေရာသုံးခွင့် မပါဘဲ ဆက်လက်မလုပ်ဆောင်နိုင်ပါ</string>
<string name="connect_via_bluetooth_title">ဘလူးတုသ်မှတစ်ဆင့် ချိတ်မ</string>
<string name="connect_via_bluetooth_no_location_permission">တည်နေရာ သုံးခွင့်မပါဘဲ ဆက်လက် မလုပ်ဆောင်နိုင်ပါ</string>
<string name="connect_via_bluetooth_start">ဘလူးတုသ် နှင့် ချိတ်ဆက်နေသည်...</string>
<string name="connect_via_bluetooth_success">ဘလူးတုသ် နှင့် အောင်မြင်စွာ ချိတ်ဆက်ပြီး</string>
<!--Private Groups-->
@@ -564,7 +563,7 @@
<string name="learn_more">ထပ်မံလေ့လာမယ်</string>
<string name="disappearing_messages_summary">၎င်းစကားပြောဆိုမှုထဲရှိ နောင်မက်ဆေ့ချ်များကို 7\u00A0 ရက်အကြာတွင် အလိုအလျောက် ဖျောက်ကွယ်မယ်။</string>
<!--Settings Actions-->
<string name="pref_category_actions">ဆောင်ရွက်ချက်များ</string>
<string name="pref_category_actions">လုပ်ဆောင်မှုများ</string>
<string name="send_feedback">တုံ့ပြန်ချက် ပေးပို့မယ်</string>
<!--Link Warning-->
<string name="link_warning_title">လင့်ခ် သတိပေးချက်</string>
@@ -631,6 +630,7 @@
<!--Connections Screen-->
<string name="transports_help_text">Briar သည် သင့်အဆက်အသွယ်များကို အင်တာနက်၊ ဝိုင်ဖိုင်၊ သို့မဟုတ် ဘလူးတုသ် မှတစ်ဆင့် ချိတ်ဆက်နိုင်ပါသည်။ \n\n အင်တာနက်ချိတ်ဆက်မှုအားလုံးသည် လုံခြုံရေးအတွက် Tor ကွန်ယက်မှတစ်ဆင့် ဖြတ်သန်းပါသည်။ \n\n အဆက်အသွယ်တစ်ဦးကို နည်းမျိုးစုံနှင့် ဆက်သွယ်နိုင်ခဲ့လျှင် Briar သည် နည်းမျိုးစုံအား တစ်ပြိုင်နက်တည်း အသုံးပြုနေပါသည်။</string>
<!--Share app offline-->
<string name="hotspot_title">ဤအက်ပ်ကို အော့ဖ်လိုင်းမျှဝေမည်</string>
<string name="hotspot_notification_channel_title">ဝိုင်ဖိုင် ဟော့စပေါ့</string>
<string name="hotspot_button_connected">ရှေ့သို့</string>
<!--The placeholder to be inserted into the string 'hotspot_manual_wifi': People can connect by %s-->
@@ -639,7 +639,7 @@
<!--Download link-->
<!--The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code'-->
<!--e.g. Download Briar 1.2.20-->
<string name="website_troubleshooting_title">အကူအညီ</string>
<string name="website_troubleshooting_title">ပြဿနာရှာဖွေပြင်ဆင်ခြင်း</string>
<!--error handling-->
<!--Transfer Data via Removable Drives-->
<string name="removable_drive_success_receive_text">ဤဖိုင်တွင်ပါဝင်သည့် လျှို့ဝှက်ကုဒ်ပြောင်းမက်ဆေ့ချ်အားလုံးကို လက်ခံရရှိပါပြီ။</string>

View File

@@ -42,6 +42,7 @@
<string name="download_briar">Download de laatste versie om Briar te kunnen blijven gebruiken.</string>
<string name="create_new_account">Je moet een nieuw account aanmaken. Je kan dezelfde bijnaam gebruiken.</string>
<string name="download_briar_button">Download de laatste versie</string>
<string name="delete_account_button">Verwijder account</string>
<string name="startup_open_database">Database aan het ontsleutelen…</string>
<string name="startup_migrate_database">Database aan het upgraden…</string>
<string name="startup_compact_database">Database comprimeren...</string>
@@ -244,7 +245,6 @@
<string name="duplicate_link_dialog_text_1">Je hebt al en wachtend contact met deze link: %s</string>
<string name="duplicate_link_dialog_text_1_contact">Je hebt al een contact met deze link: %s</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">Zijn %s en %s dezelfde persoon?</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
string will be used in a dialog button, so if the translation of this string is longer than 20
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
@@ -253,7 +253,6 @@
will be used in a dialog button, so if the translation of this string longer than 20 characters,
please use "No" instead, and use "Yes" for the "Same Person" button-->
<string name="different_person_button">Ander persoon</string>
<string name="duplicate_link_dialog_text_3">%s en %s hebben je dezelfde link gestuurd.\n\nEen van hen kan proberen te ontdekken wie jouw contacten zijn.\n\nVertel ze niet dat je dezelfde link al van een ander persoon hebt ontvangen.</string>
<string name="pending_contact_updated_toast">Wachtend contact is bijgewerkt</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Introduceer je contacten</string>

Some files were not shown because too many files have changed in this diff Show More