Compare commits

...

132 Commits

Author SHA1 Message Date
akwizgran
d311557f09 Check whether first visible message ID is null before storing. 2017-05-29 10:05:49 +01:00
akwizgran
3449677b24 Bumped version number and expiry date. 2017-05-19 12:07:29 +01:00
akwizgran
1ad3a6646e Merge branch '941-store-correct-parent-id' into 'master'
Store correct original parent ID when rewrapping blog posts

See merge request !534
2017-05-12 09:53:27 +00:00
akwizgran
2d10f6b2bd Merge branch '884-emoji-text-view-layout-bug' into 'master'
Remove ellipsizing support from EmojiTextView

Closes #884

See merge request !533
2017-05-12 09:35:33 +00:00
akwizgran
5b05424d83 Merge branch 'master' into '941-store-correct-parent-id'
# Conflicts:
#   briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java
2017-05-12 09:34:24 +00:00
akwizgran
0826022d82 Merge branch 'bring_annotations_in_line' into 'master'
Bring nullable annotation imports in line

See merge request !536
2017-05-12 09:33:00 +00:00
akwizgran
a901bfb9cb Merge branch '948-vector-crash' into 'master'
Remove scientific notation from vector drawables to prevent crashes

Closes #948

See merge request !537
2017-05-12 09:28:59 +00:00
akwizgran
03cdce122a Merge branch '947-bluetooth-address-crash' into 'master'
Don't crash on empty bluetooth addresses

See merge request !538
2017-05-12 09:26:57 +00:00
goapunk
f2e0e16969 Bring nullable annotation imports in line
Signed-off-by: goapunk <noobie@goapunks.net>
2017-05-12 10:06:56 +02:00
Torsten Grote
0c441e2ff3 Don't crash on empty bluetooth addresses 2017-05-10 15:06:09 -03:00
Torsten Grote
21302304a5 Remove scientific notation from vector drawables to prevent crashes
Details: http://stackoverflow.com/a/40829348
2017-05-10 14:56:59 -03:00
Torsten Grote
6839d8b844 Merge branch 'wifi-manager-memory-leak' into 'master'
Use application context to get WifiManager

See merge request !535
2017-05-10 17:01:52 +00:00
Torsten Grote
aee65a716c Merge branch '798-remove-contact-blogs' into 'master'
Allow to remove pre-shared blogs of our contacts

Closes #798

See merge request !529
2017-05-10 16:58:38 +00:00
Torsten Grote
6a07d8f2c9 Allow to remove pre-shared blogs of our contacts 2017-05-10 13:50:07 -03:00
Ernir Erlingsson
3c1ea81cd0 Merge branch '853-disabled-menu-items' into 'master'
Remove theme default color override

Closes #853

See merge request !527
2017-05-06 20:26:15 +00:00
Ernir Erlingsson
025f417bc7 Merge branch '894-list-position-restore' into 'master'
save and restore list position for threaded lists

Closes #894 and #946

See merge request !528
2017-05-06 19:37:02 +00:00
Ernir Erlingsson
c9dcd906c9 final pre-merge fixes 2017-05-06 21:36:25 +02:00
Ernir Erlingsson
7024e04d15 fixed final akwizgran comments 2017-05-06 21:31:53 +02:00
akwizgran
0b8ac947db Use application context to get WifiManager. 2017-05-05 15:43:27 +01:00
Ernir Erlingsson
948410a064 fixed unread buttons for threaded lists and akwizgran's comments 2017-05-05 14:49:53 +02:00
akwizgran
2841339cac Merge branch '468-ci' into 'master'
Set up basic CI

Closes #468

See merge request !530
2017-05-05 09:11:06 +00:00
Torsten Grote
e8e82bd805 Update Translations 2017-05-04 10:26:49 -03:00
Ernir Erlingsson
6876f40a0e Merge branch 'fix_groupname_validation' into 'master'
Fix groupname validation

See merge request !531
2017-05-04 07:26:55 +00:00
Ernir Erlingsson
5f4e1ecdfd improvements after code review #1
fix
2017-05-02 11:42:55 +02:00
Ernir Erlingsson
044719432a list position save and restore now implemented for threaded lists 2017-05-02 11:42:55 +02:00
Ernir Erlingsson
d1a929da85 bumped expire date 2017-05-02 11:42:15 +02:00
goapunk
2a8978a60d fix group name validation
Signed-off-by: goapunk <noobie@goapunks.net>
2017-04-29 16:49:37 +02:00
Torsten Grote
c0afad7a26 Set up basic CI 2017-04-28 13:24:41 -03:00
akwizgran
37281c6c23 Remove ellipsizing support from EmojiTextView.
This is a workaround for a layout bug.
2017-04-28 15:39:24 +01:00
Ernir Erlingsson
6de539a62d Merge branch '791-permanent-input' into 'master'
Show text input permanently in threaded conversations

Closes #791

See merge request !526
2017-04-27 10:38:58 +00:00
Ernir Erlingsson
34704ec04d Merge branch '874-tree-indicator' into 'master'
Darken thread indicator

Closes #874

See merge request !525
2017-04-26 08:38:52 +00:00
akwizgran
9fd6d46583 Merge branch '871-increase-socket-timeout' into 'master'
Increase socket timeout for Tor sockets

See merge request !519
2017-04-19 16:53:59 +00:00
akwizgran
76a5e25656 Added tests for wrapping and rewrapping blog posts. 2017-04-19 12:16:18 +01:00
akwizgran
3575b74837 Store correct original parent ID when rewrapping blog posts. 2017-04-19 12:15:34 +01:00
Torsten Grote
f1c7996960 Remove theme default color override 2017-04-18 09:14:00 -03:00
Torsten Grote
920f3581fa Show text input permanently in threaded conversations 2017-04-17 16:22:24 -03:00
Torsten Grote
45e7af31fe Darken thread indicator 2017-04-17 16:14:26 -03:00
Torsten Grote
67d5d8cdf1 Merge branch '941-reblogged-rss-post-has-wrong-icon' into 'master'
Store RSS flag for wrapped blog posts

Closes #941

See merge request !524
2017-04-17 18:23:41 +00:00
Torsten Grote
9d8cadb7a9 Merge branch 'use-original-timestamp-for-rss-posts' into 'master'
Use original timestamp for RSS posts, if available

See merge request !523
2017-04-17 18:22:10 +00:00
Torsten Grote
6425c49d04 Merge branch 'remove-single-top-flag' into 'master'
Don't use single top and clear top flags together

See merge request !522
2017-04-17 18:20:46 +00:00
Torsten Grote
68d98b50f2 Merge branch '938-ignore-play-services-overlay-permission' into 'master'
When checking for overlay apps, ignore Play Services

Closes #938

See merge request !521
2017-04-17 18:19:46 +00:00
akwizgran
84986d393f Added a test for #941, fixed some broken tests. 2017-04-13 17:28:45 +01:00
akwizgran
115d488bc3 Clamp the imported timestamp within reasonable limits. 2017-04-13 16:21:00 +01:00
akwizgran
2eeb2213e3 Store RSS flag for wrapped blog posts. 2017-04-13 15:23:08 +01:00
akwizgran
1b48d661e8 Use original timestamp for RSS posts, if available. 2017-04-13 14:43:43 +01:00
akwizgran
49ba66dee9 Don't use single top and clear top flags together. 2017-04-13 13:56:20 +01:00
akwizgran
46920f3bce Merge branch '892-separate-rss-blog' into 'master'
Separate RSS posts from personal blog posts

Closes #892

See merge request !520
2017-04-13 10:15:00 +00:00
Torsten Grote
4b955809f7 Address review comments 2017-04-12 15:18:27 -03:00
akwizgran
57d4d6546a When checking for overlay apps, ignore Play Services. 2017-04-12 14:24:37 +01:00
Torsten Grote
9bfb58a764 Show blog posts from RSS feeds with a dedicated icon
This adds a field to the post headers and some more tests.
2017-04-12 08:43:24 -03:00
Torsten Grote
0256ec0b8c Show reblog icon only for reblogged posts 2017-04-12 08:43:23 -03:00
Torsten Grote
b0b4a85d15 Add integration test for FeedManager
Attention: This factors out a DnsModule to be able to make actual
non-Tor DNS lookups for testing.
2017-04-12 08:43:23 -03:00
Torsten Grote
d40a058ef5 Change blog descriptor format to include RSS feed flag
This now also handles the case where an RSS blog is deleted via the blog
deletion option and not the feed management.
2017-04-12 08:43:22 -03:00
Torsten Grote
58b9efb24c Open feed's blog when clicking it in 'manage activity' 2017-04-12 08:43:22 -03:00
Torsten Grote
17de785c12 Remove blog as well when removing RSS feed
This also adds a confirmation dialog to the removal process.
2017-04-12 08:43:21 -03:00
Torsten Grote
c7ff1ba974 Store RSS feeds in a separate dedicated blog
A fake LocalAuthor is created for this new blog and stored in the feed's metadata.
2017-04-12 08:43:21 -03:00
akwizgran
d17669f131 Increase socket timeout for Tor sockets. 2017-04-11 14:53:03 +01:00
akwizgran
9755cd9ab4 Merge branch '891-messages-not-acked' into 'master'
Fix MessageId calculation for deprecated MessageQueue

Closes #891

See merge request !514
2017-04-11 12:49:44 +00:00
akwizgran
6d2b18facc Merge branch '799-explain-content-visibility' into 'master'
Show explanation about visibility in member lists

Closes #799

See merge request !516
2017-04-07 14:54:41 +00:00
Torsten Grote
f8cf7034db Show explanation about visibility in member lists 2017-04-07 11:38:33 -03:00
akwizgran
a1e65c9fa7 Merge branch '893-double-introduction-accept' into 'master'
Prevent conversation actions from being executed twice

Closes #893

See merge request !512
2017-04-07 14:03:40 +00:00
Torsten Grote
499d2fe677 Prevent conversation actions from being executed twice 2017-04-07 10:00:55 -03:00
Torsten Grote
fe963edd9d Merge branch '829-new-launcher-icon' into 'master'
Use the new launcher icon

Closes #829

See merge request !515
2017-04-07 12:59:35 +00:00
akwizgran
96f006068f Use the new launcher icon. 2017-04-07 13:57:13 +01:00
akwizgran
74f1fa5690 Merge branch '932-panic-button-terminate-process' into 'master'
Terminate the process after handling a panic trigger

Closes #932

See merge request !513
2017-04-07 12:49:52 +00:00
Torsten Grote
85c17b4cb0 Fix MessageId calculation for deprecated MessageQueue
This was preventing introduction messages from getting ACKed.
The introduction tests were modified to check for this.
2017-04-07 09:45:35 -03:00
akwizgran
6b3a1fd6d4 Merge branch 'fix-test-configuration' into 'master'
Fix "all tests" configuration

See merge request !509
2017-04-07 10:19:01 +00:00
akwizgran
bcabcfce8c Merge branch '925_panic_app_market' into 'master'
Refine the panic app list tap behavior

Closes #925

See merge request !511
2017-04-07 10:17:48 +00:00
goapunk
db0a3bf380 Refine the panic app list behavior
* Only open if a market is installed

Signed-off-by: goapunk <noobie@goapunks.net>
2017-04-07 12:07:10 +02:00
akwizgran
d5d9436e28 Terminate the process after handling a panic trigger. 2017-04-07 10:54:51 +01:00
Torsten Grote
0827b067ec Harmonize position of boolean message variables 2017-04-06 15:42:12 -03:00
akwizgran
9d0dbe9210 Merge branch '885_store_only_four_lan_ip' into 'master'
Store only 4 ip addresses because 5 exceed the maximum length.

Closes #885

See merge request !510
2017-04-06 14:38:49 +00:00
akwizgran
1f7d1bf515 Merge branch '675-polite-executor' into 'master'
Use a polite executor for validation tasks

Closes #675

See merge request !507
2017-04-06 14:37:59 +00:00
akwizgran
fb85ecf07b Added note about number of available processors changing. 2017-04-06 15:34:39 +01:00
akwizgran
a931e6b316 Merge branch '906_tapjacking' into 'master'
Add tapjacking protection

Closes #906

See merge request !502
2017-04-06 14:31:10 +00:00
akwizgran
3aa4644339 If we have multiple cores, leave one free from crypto tasks. 2017-04-06 11:36:02 +01:00
goapunk
9a638c804a Store only 4 ip addresses because 5 exceed the maximum length.
Signed-off-by: goapunk <noobie@goapunks.net>
2017-04-06 12:35:44 +02:00
akwizgran
df3254c634 Fix "all tests" configuration.
The last (empty) stage of this configuration used to
complain about not finding any tests. I replace the last
stage with a copy of the briar-android configuration
and removed briar-android from the list of prerequisites,
so all stages now contain tests.
2017-04-06 10:18:42 +01:00
akwizgran
ba353b9f2b List of wifi configs can be null. 2017-04-06 10:11:59 +01:00
goapunk
04c4e70dd1 Add tapjacking protection
* Set filterTouchesWhenObscured for all views
* Warn the user if Apps using the SYSTEM_ALERT_WINDOW permission are installed
* Warn the user if an App using the permission is installed while Briar is running

Signed-off-by: goapunk <noobie@goapunks.net>
2017-04-05 23:25:57 +02:00
akwizgran
d381e25e86 Limit the number of validation tasks on the crypto executor. 2017-04-05 17:34:21 +01:00
akwizgran
0c085f139a Added "polite" delegating executor. 2017-04-05 17:34:20 +01:00
akwizgran
4123f4a5ce Log time spent queueing and executing crypto and DB tasks. 2017-04-05 17:34:15 +01:00
akwizgran
7bc269fda4 Merge branch '914-simpler-secure-random' into 'master'
Remove Fortuna generator, fix Android SecureRandom bug

Closes #914

See merge request !500
2017-04-05 10:55:25 +00:00
akwizgran
a22931bae6 Merge branch '928-move-html-sanitation-to-dbthread' into 'master'
Move HTML Sanitation to DbThread

Closes #928

See merge request !506
2017-04-04 16:54:21 +00:00
akwizgran
403f886110 Merge branch '910-fix-intent-hijacking' into 'master'
Fix possible intent hijacking for implicit pending intents

Closes #910

See merge request !499
2017-04-04 16:34:38 +00:00
Torsten Grote
b7866be38d Move HTML Sanitation to DbThread 2017-04-04 13:27:06 -03:00
akwizgran
a1b415330e Merge branch '926-class-cast-exception' into 'master'
Don't cast Context to BaseActivity

Closes #926

See merge request !505
2017-04-04 12:44:38 +00:00
Torsten Grote
58318bb79f Remove pending intents for clearning notification counters
These counters are already reset when the user clicks the notification
or vists the area of the app the notifications are for.

This also removes a potential intent hijacking vulnerability.
2017-04-04 09:02:38 -03:00
akwizgran
10bb30e190 Don't assume Context is a BaseActivity. 2017-04-03 12:22:52 +01:00
akwizgran
199a2ffc46 Merge branch '909-prevent-multiple-password-screens' into 'master'
Prevent multiple instances of PasswordActivity

Closes #909

See merge request !504
2017-03-31 13:42:04 +00:00
akwizgran
f6ad2992f2 Prevent multiple instances of PasswordActivity. 2017-03-31 12:55:38 +01:00
akwizgran
f039bd1239 Merge branch '909-restrict-access-to-panic-prefs' into 'master'
Don't allow other apps to open the panic prefs activity

See merge request !503
2017-03-31 08:25:21 +00:00
Torsten Grote
da22d91ef3 Update expiry date and translations 2017-03-30 15:35:02 -03:00
Torsten Grote
cd360ec877 Merge branch '909-restrict-access-to-settings-activity' into 'master'
Require a system permission to open the settings activity

See merge request !501
2017-03-30 12:58:07 +00:00
akwizgran
8e1ada4cdc Don't allow other apps to open the panic prefs activity. 2017-03-30 12:26:33 +01:00
akwizgran
ac063b4c79 Require a system permission to open the settings activity. 2017-03-30 12:00:35 +01:00
akwizgran
10e6163e94 Merge branch '915-fix-forumactivitytest' into 'master'
Fix ForumActivityTest

Closes #915

See merge request !496
2017-03-30 08:19:30 +00:00
Torsten Grote
ebc3402307 Merge branch '912-validate-stream-encrypter-args' into 'master'
Validate arguments to StreamEncrypter#writeFrame()

Closes #912

See merge request !497
2017-03-29 16:52:54 +00:00
akwizgran
d9c63bbcfe Remove Fortuna generator, fix Android SecureRandom bug. 2017-03-29 16:31:59 +01:00
akwizgran
9c89e83c20 Merge branch '913-simpler-password-strength-estimation' into 'master'
Simpler password strength estimation

Closes #913

See merge request !495
2017-03-29 15:01:44 +00:00
akwizgran
adc9bdeb68 Merge branch 'run-configurations' into 'master'
Run configurations for tests

See merge request !498
2017-03-29 08:18:05 +00:00
akwizgran
ff7f0bdc63 Added run configurations for tests to git. 2017-03-28 15:36:18 +01:00
Torsten Grote
c5f6980c69 Fix ForumActivityTest 2017-03-28 09:34:42 -03:00
akwizgran
2574354997 Merge branch '905_set_testing_constant_on_debug' into 'master'
Set TESTING from BuildConfig

Closes #905

See merge request !494
2017-03-28 12:30:00 +00:00
akwizgran
c4e42949cf Simpler password strength estimation. 2017-03-28 13:27:04 +01:00
goapunk
1c5897f1cc Set TESTING from BuildConfig
Signed-off-by: goapunk <noobie@goapunks.net>
2017-03-27 23:39:39 +02:00
akwizgran
510f99c7da Validate arguments to StreamEncrypter#writeFrame(). 2017-03-27 16:26:49 +01:00
akwizgran
1918346ae8 Merge branch '911-link-sanitation' into 'master'
Sanitize all HTML before displaying it

Closes #911

See merge request !493
2017-03-27 09:45:17 +00:00
akwizgran
2a59515c72 Merge branch '907-panic-signout' into 'master'
Require a panic app to be set before executing any panic actions

Closes #907

See merge request !492
2017-03-27 09:43:30 +00:00
akwizgran
7161152b41 Merge branch '903_replace_Runtime_with_ProcessBuilder' into 'master'
Use ProcessBuilder instead of Runtime to start tor

See merge request !487
2017-03-27 09:37:00 +00:00
akwizgran
b42660edab Merge branch 'emoji-soft-reference' into 'master'
Fix potential NPE when getting soft reference

See merge request !490
2017-03-27 09:28:30 +00:00
akwizgran
b405bbf98e Merge branch 'setting-to-disable-tor' into 'master'
Add a setting to disable Tor

See merge request !489
2017-03-27 09:27:56 +00:00
akwizgran
c167938b61 Use constants for Tor network setting values. 2017-03-27 10:26:10 +01:00
Torsten Grote
24b531e6b2 Sanitize all HTML before displaying it 2017-03-24 16:45:36 -03:00
Torsten Grote
9cffff715a Require a panic app to be set before executing any panic actions 2017-03-24 16:19:09 -03:00
Torsten Grote
804e912e19 Merge branch 'remove-placeholder-tests' into 'master'
Remove placeholder tests

See merge request !491
2017-03-24 18:12:13 +00:00
akwizgran
d67e3900e3 Removed placeholder tests.
Evidently this way of nagging myself to write tests doesn't work.
2017-03-24 15:18:37 +00:00
akwizgran
e682f31898 Added a setting to disable Tor.
Also fixed a bug with settings namespaces.
2017-03-24 14:56:30 +00:00
akwizgran
a9053808b4 Merge branch '908-rss-import-dns-leak' into 'master'
Don't make DNS lookups during RSS import

Closes #908

See merge request !488
2017-03-24 10:02:19 +00:00
akwizgran
d9a62a0431 Merge branch 'print_tor_errors' into 'master'
Make Tor boot more verbose

See merge request !486
2017-03-24 10:01:29 +00:00
akwizgran
15ba73276d Merge branch '900-remove-error-state' into 'master'
Remove error state and reset session on error instead

Closes #900

See merge request !484
2017-03-24 09:57:03 +00:00
Torsten Grote
720dda784e Remove error state and reset session on error instead 2017-03-23 14:14:23 -03:00
akwizgran
0ae55404f5 Merge branch '900-simplify-sharing-client-state-machine' into 'master'
Remove REMOTE_LEFT state from sharing client state machine

See merge request !483
2017-03-23 16:13:44 +00:00
akwizgran
9c41437870 Prevent OkHttp from making local DNS lookups. 2017-03-23 15:13:15 +00:00
akwizgran
da9cde083f Include description of SOCKS error in exception. 2017-03-23 15:13:15 +00:00
goapunk
ce3156c9fe Use ProcessBuilder instead of Runtime to start tor
* ProcessBuilder copies the ENV from the current proc
  and preserves ANDROID_ROOT and ANDROID_DATA

Signed-off-by: goapunk <noobie@goapunks.net>
2017-03-18 09:46:24 +01:00
goapunk
be3752bf2f Set Android env vars
Signed-off-by: goapunk <noobie@goapunks.net>

(cherry picked from commit e26f663)
2017-03-17 16:00:34 +00:00
noobie
ef74db65aa Make Tor boot more verbose
Signed-off-by: noobie <noobie@goapunks.net>
2017-03-13 12:19:14 +01:00
Torsten Grote
867a233b6f Update expiry 2017-03-06 18:46:15 +01:00
Torsten Grote
59af25b2cd Remove REMOTE_LEFT state from sharing client state machine 2017-01-12 15:19:28 -02:00
akwizgran
79c78518fb Fix potential NPE when getting soft reference. 2016-12-28 13:47:33 +00:00
229 changed files with 4673 additions and 2499 deletions

9
.gitignore vendored
View File

@@ -9,17 +9,18 @@ Thumbs.db
.DS_Store
# Eclipse project files
#.classpath
#.project
.classpath
.project
.settings
# Local configuration file (sdk path, etc)
local.properties
# Android Studio
.idea/*
!.idea/runConfigurations/
!.idea/codeStyleSettings.xml
.gradle
build/
*.iml
.gitignore
src/test/
projectFilesBackup/

20
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,20 @@
image: registry.gitlab.com/fdroid/ci-images:client-latest
cache:
paths:
- .gradle/wrapper
- .gradle/caches
before_script:
- export GRADLE_USER_HOME=$PWD/.gradle
# - export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' app/build.gradle`
# - echo y | android --silent update sdk --no-ui --filter android-${ANDROID_COMPILE_SDK}
test:
script:
- ./gradlew test
after_script:
# this file changes 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/

28
.idea/runConfigurations/All_tests.xml generated Normal file
View File

@@ -0,0 +1,28 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="briar-android" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-android" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method>
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-api" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-core" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-j2se" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-core" run_configuration_type="AndroidJUnit" />
</method>
</configuration>
</component>

View File

@@ -0,0 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-api" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="bramble-api" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-api" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>
</component>

View File

@@ -0,0 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-core" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="bramble-core" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-core" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>
</component>

View File

@@ -0,0 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-j2se" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="bramble-j2se" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea -Djava.library.path=libs" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-j2se" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>
</component>

View File

@@ -0,0 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-android" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="briar-android" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-android" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>
</component>

View File

@@ -0,0 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-core" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="briar-core" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-core" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>
</component>

View File

@@ -12,8 +12,8 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 22
versionCode 1
versionName "1.0"
versionCode 14
versionName "0.14"
consumerProguardFiles 'proguard-rules.txt'
}

View File

@@ -67,6 +67,7 @@ import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
@@ -164,7 +165,7 @@ class DroidtoothPlugin implements DuplexPlugin {
bind();
} else {
// Enable Bluetooth if settings allow
if (callback.getSettings().getBoolean("enable", false)) {
if (callback.getSettings().getBoolean(PREF_BT_ENABLE, false)) {
wasEnabledByUs = true;
if (adapter.enable()) LOG.info("Enabling Bluetooth");
else LOG.info("Could not enable Bluetooth");

View File

@@ -79,6 +79,12 @@ import static java.util.logging.Level.WARNING;
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
import static org.briarproject.bramble.api.plugin.TorConstants.CONTROL_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_ALWAYS;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WIFI;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion;
@MethodsNotNullByDefault
@@ -182,19 +188,31 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
String torPath = torFile.getAbsolutePath();
String configPath = configFile.getAbsolutePath();
String pid = String.valueOf(android.os.Process.myPid());
String[] cmd = {torPath, "-f", configPath, OWNER, pid};
String[] env = {"HOME=" + torDirectory.getAbsolutePath()};
Process torProcess;
ProcessBuilder pb =
new ProcessBuilder(torPath, "-f", configPath, OWNER, pid);
Map<String, String> env = pb.environment();
env.put("HOME", torDirectory.getAbsolutePath());
pb.directory(torDirectory);
try {
torProcess = Runtime.getRuntime().exec(cmd, env, torDirectory);
torProcess = pb.start();
} catch (SecurityException | IOException e) {
throw new PluginException(e);
}
// Log the process's standard output until it detaches
if (LOG.isLoggable(INFO)) {
Scanner stdout = new Scanner(torProcess.getInputStream());
while (stdout.hasNextLine()) LOG.info(stdout.nextLine());
Scanner stderr = new Scanner(torProcess.getErrorStream());
while (stdout.hasNextLine() || stderr.hasNextLine()){
if(stdout.hasNextLine()) {
LOG.info(stdout.nextLine());
}
if(stderr.hasNextLine()){
LOG.info(stderr.nextLine());
}
}
stdout.close();
stderr.close();
}
try {
// Wait for the process to detach or exit
@@ -366,7 +384,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void run() {
// If there's already a port number stored in config, reuse it
String portString = callback.getSettings().get("port");
String portString = callback.getSettings().get(PREF_TOR_PORT);
int port;
if (StringUtils.isNullOrEmpty(portString)) port = 0;
else port = Integer.parseInt(portString);
@@ -389,7 +407,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// Store the port number
final String localPort = String.valueOf(ss.getLocalPort());
Settings s = new Settings();
s.put("port", localPort);
s.put(PREF_TOR_PORT, localPort);
callback.mergeSettings(s);
// Create a hidden service if necessary
ioExecutor.execute(new Runnable() {
@@ -666,7 +684,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void eventOccurred(Event e) {
if (e instanceof SettingsUpdatedEvent) {
if (((SettingsUpdatedEvent) e).getNamespace().equals("tor")) {
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
if (s.getNamespace().equals(ID.getString())) {
LOG.info("Tor settings updated");
updateConnectionStatus();
}
@@ -688,7 +707,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
country);
Settings s = callback.getSettings();
boolean useMobileData = s.getBoolean("torOverMobile", true);
int network = s.getInt(PREF_TOR_NETWORK,
PREF_TOR_NETWORK_ALWAYS);
if (LOG.isLoggable(INFO)) {
LOG.info("Online: " + online + ", wifi: " + wifi);
@@ -703,7 +723,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} else if (blocked) {
LOG.info("Disabling network, country is blocked");
enableNetwork(false);
} else if (!wifi && !useMobileData) {
} else if (network == PREF_TOR_NETWORK_NEVER
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
LOG.info("Disabling network due to data setting");
enableNetwork(false);
} else {

View File

@@ -0,0 +1,93 @@
package org.briarproject.bramble.system;
import android.app.Application;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ContentResolver;
import android.content.Context;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Parcel;
import android.provider.Settings;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.List;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static android.content.Context.WIFI_SERVICE;
import static android.provider.Settings.Secure.ANDROID_ID;
@Immutable
@NotNullByDefault
class AndroidSecureRandomProvider extends LinuxSecureRandomProvider {
private static final int SEED_LENGTH = 32;
private final Context appContext;
@Inject
AndroidSecureRandomProvider(Application app) {
appContext = app.getApplicationContext();
}
@Override
protected void writeToEntropyPool(DataOutputStream out) throws IOException {
super.writeToEntropyPool(out);
out.writeInt(android.os.Process.myPid());
out.writeInt(android.os.Process.myTid());
out.writeInt(android.os.Process.myUid());
if (Build.FINGERPRINT != null) out.writeUTF(Build.FINGERPRINT);
if (Build.SERIAL != null) out.writeUTF(Build.SERIAL);
ContentResolver contentResolver = appContext.getContentResolver();
String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
if (id != null) out.writeUTF(id);
Parcel parcel = Parcel.obtain();
WifiManager wm =
(WifiManager) appContext.getSystemService(WIFI_SERVICE);
List<WifiConfiguration> configs = wm.getConfiguredNetworks();
if (configs != null) {
for (WifiConfiguration config : configs)
parcel.writeParcelable(config, 0);
}
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt != null) {
for (BluetoothDevice device : bt.getBondedDevices())
parcel.writeParcelable(device, 0);
}
out.write(parcel.marshall());
parcel.recycle();
}
@Override
protected void writeSeed() {
super.writeSeed();
if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT <= 18)
applyOpenSslFix();
}
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
private void applyOpenSslFix() {
byte[] seed = new LinuxSecureRandomSpi().engineGenerateSeed(
SEED_LENGTH);
try {
// Seed the OpenSSL PRNG
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_seed", byte[].class)
.invoke(null, seed);
// Mix the output of the Linux PRNG into the OpenSSL PRNG
int bytesRead = (Integer) Class.forName(
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_load_file", String.class, long.class)
.invoke(null, "/dev/urandom", 1024);
if (bytesRead != 1024) throw new IOException();
} catch (Exception e) {
throw new SecurityException(e);
}
}
}

View File

@@ -1,42 +0,0 @@
package org.briarproject.bramble.system;
import android.app.Application;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Build;
import android.provider.Settings;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.DataOutputStream;
import java.io.IOException;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static android.provider.Settings.Secure.ANDROID_ID;
@Immutable
@NotNullByDefault
class AndroidSeedProvider extends LinuxSeedProvider {
private final Context appContext;
@Inject
AndroidSeedProvider(Application app) {
appContext = app.getApplicationContext();
}
@Override
void writeToEntropyPool(DataOutputStream out) throws IOException {
out.writeInt(android.os.Process.myPid());
out.writeInt(android.os.Process.myTid());
out.writeInt(android.os.Process.myUid());
if (Build.FINGERPRINT != null) out.writeUTF(Build.FINGERPRINT);
if (Build.SERIAL != null) out.writeUTF(Build.SERIAL);
ContentResolver contentResolver = appContext.getContentResolver();
String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
if (id != null) out.writeUTF(id);
super.writeToEntropyPool(out);
}
}

View File

@@ -4,7 +4,7 @@ import android.app.Application;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.SeedProvider;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import javax.inject.Singleton;
@@ -16,8 +16,8 @@ public class AndroidSystemModule {
@Provides
@Singleton
SeedProvider provideSeedProvider(Application app) {
return new AndroidSeedProvider(app);
SecureRandomProvider provideSecureRandomProvider(Application app) {
return new AndroidSecureRandomProvider(app);
}
@Provides

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.api;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.StringUtils;
import java.util.Arrays;
import java.util.Comparator;
@@ -53,6 +54,12 @@ public class Bytes implements Comparable<Bytes> {
return aBytes.length - bBytes.length;
}
@Override
public String toString() {
return getClass().getSimpleName() +
"(" + StringUtils.toHexString(getBytes()) + ")";
}
public static class BytesComparator implements Comparator<Bytes> {
@Override

View File

@@ -6,9 +6,9 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
public interface PasswordStrengthEstimator {
float NONE = 0;
float WEAK = 0.4f;
float QUITE_WEAK = 0.6f;
float QUITE_STRONG = 0.8f;
float WEAK = 0.25f;
float QUITE_WEAK = 0.5f;
float QUITE_STRONG = 0.75f;
float STRONG = 1;
/**

View File

@@ -13,7 +13,9 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault
public class Author {
public enum Status {ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES}
public enum Status {
NONE, ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES
}
private final AuthorId id;
private final String name;

View File

@@ -9,4 +9,5 @@ public interface BluetoothConstants {
String PROP_ADDRESS = "address";
String PROP_UUID = "uuid";
String PREF_BT_ENABLE = "enable";
}

View File

@@ -4,4 +4,5 @@ public interface LanTcpConstants {
TransportId ID = new TransportId("org.briarproject.bramble.lan");
String PREF_LAN_IP_PORTS = "ipPorts";
}

View File

@@ -8,4 +8,12 @@ public interface TorConstants {
int CONTROL_PORT = 59051;
int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds
String PREF_TOR_NETWORK = "network";
String PREF_TOR_PORT = "port";
int PREF_TOR_NETWORK_NEVER = 0;
int PREF_TOR_NETWORK_WIFI = 1;
int PREF_TOR_NETWORK_ALWAYS = 2;
}

View File

@@ -0,0 +1,23 @@
package org.briarproject.bramble.api.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.Provider;
import java.security.SecureRandom;
import javax.annotation.Nullable;
/**
* Wrapper for a platform-specific secure random number generator.
*/
@NotNullByDefault
public interface SecureRandomProvider {
/**
* Returns a {@link Provider} that provides a strong {@link SecureRandom}
* implementation, or null if the platform's default implementation should
* be used.
*/
@Nullable
Provider getProvider();
}

View File

@@ -1,18 +0,0 @@
package org.briarproject.bramble.api.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/**
* Uses a platform-specific source to provide a seed for a pseudo-random
* number generator.
*/
@NotNullByDefault
public interface SeedProvider {
/**
* The length of the seed in bytes.
*/
int SEED_BYTES = 32;
byte[] getSeed();
}

View File

@@ -19,7 +19,7 @@ public class PrivacyUtils {
@Nullable
public static String scrubMacAddress(@Nullable String address) {
if (address == null) return null;
if (address == null || address.length() == 0) return null;
// this is a fake address we need to know about
if (address.equals("02:00:00:00:00:00")) return address;
// keep first and last octet of MAC address

View File

@@ -0,0 +1,84 @@
package org.briarproject.bramble;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import static java.util.logging.Level.FINE;
/**
* An {@link Executor} that delegates its tasks to another {@link Executor}
* while limiting the number of tasks that are delegated concurrently. Tasks
* are delegated in the order they are submitted to this executor.
*/
@NotNullByDefault
public class PoliteExecutor implements Executor {
private static final Level LOG_LEVEL = FINE;
private final Object lock = new Object();
@GuardedBy("lock")
private final Queue<Runnable> queue = new LinkedList<Runnable>();
private final Executor delegate;
private final int maxConcurrentTasks;
private final Logger log;
@GuardedBy("lock")
private int concurrentTasks = 0;
/**
* @param tag the tag to be used for logging
* @param delegate the executor to which tasks will be delegated
* @param maxConcurrentTasks the maximum number of tasks that will be
* delegated concurrently. If this is set to 1, tasks submitted to this
* executor will run in the order they are submitted and will not run
* concurrently
*/
public PoliteExecutor(String tag, Executor delegate,
int maxConcurrentTasks) {
this.delegate = delegate;
this.maxConcurrentTasks = maxConcurrentTasks;
log = Logger.getLogger(tag);
}
@Override
public void execute(final Runnable r) {
final long submitted = System.currentTimeMillis();
Runnable wrapped = new Runnable() {
@Override
public void run() {
if (log.isLoggable(LOG_LEVEL)) {
long queued = System.currentTimeMillis() - submitted;
log.log(LOG_LEVEL, "Queue time " + queued + " ms");
}
try {
r.run();
} finally {
scheduleNext();
}
}
};
synchronized (lock) {
if (concurrentTasks < maxConcurrentTasks) {
concurrentTasks++;
delegate.execute(wrapped);
} else {
queue.add(wrapped);
}
}
}
private void scheduleNext() {
synchronized (lock) {
Runnable next = queue.poll();
if (next == null) concurrentTasks--;
else delegate.execute(next);
}
}
}

View File

@@ -0,0 +1,49 @@
package org.briarproject.bramble;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.util.logging.Level.FINE;
@NotNullByDefault
public class TimeLoggingExecutor extends ThreadPoolExecutor {
private static final Level LOG_LEVEL = FINE;
private final Logger log;
public TimeLoggingExecutor(String tag, int corePoolSize, int maxPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
super(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue,
handler);
log = Logger.getLogger(tag);
}
@Override
public void execute(final Runnable r) {
if (log.isLoggable(LOG_LEVEL)) {
final long submitted = System.currentTimeMillis();
super.execute(new Runnable() {
@Override
public void run() {
long started = System.currentTimeMillis();
long queued = started - submitted;
log.log(LOG_LEVEL, "Queue time " + queued + " ms");
r.run();
long executing = System.currentTimeMillis() - started;
log.log(LOG_LEVEL, "Execution time " + executing + " ms");
}
});
} else {
super.execute(r);
}
}
}

View File

@@ -1,62 +0,0 @@
package org.briarproject.bramble.crypto;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
/**
* A {@link SecureRandom} implementation that combines the outputs of two or
* more other implementations using XOR.
*/
class CombinedSecureRandom extends SecureRandom {
private static final Provider PROVIDER = new CombinedProvider();
CombinedSecureRandom(SecureRandom... randoms) {
super(new CombinedSecureRandomSpi(randoms), PROVIDER);
}
private static class CombinedSecureRandomSpi extends SecureRandomSpi {
private final SecureRandom[] randoms;
private CombinedSecureRandomSpi(SecureRandom... randoms) {
if (randoms.length < 2) throw new IllegalArgumentException();
this.randoms = randoms;
}
@Override
protected byte[] engineGenerateSeed(int numBytes) {
byte[] combined = new byte[numBytes];
for (SecureRandom random : randoms) {
byte[] b = random.generateSeed(numBytes);
int length = Math.min(numBytes, b.length);
for (int i = 0; i < length; i++)
combined[i] = (byte) (combined[i] ^ b[i]);
}
return combined;
}
@Override
protected void engineNextBytes(byte[] b) {
byte[] temp = new byte[b.length];
for (SecureRandom random : randoms) {
random.nextBytes(temp);
for (int i = 0; i < b.length; i++)
b[i] = (byte) (b[i] ^ temp[i]);
}
}
@Override
protected void engineSetSeed(byte[] seed) {
for (SecureRandom random : randoms) random.setSeed(seed);
}
}
private static class CombinedProvider extends Provider {
private CombinedProvider() {
super("Combined", 1.0, "");
}
}
}

View File

@@ -8,7 +8,7 @@ import org.briarproject.bramble.api.crypto.PseudoRandom;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.system.SeedProvider;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.TransportKeys;
@@ -29,7 +29,10 @@ import org.spongycastle.crypto.params.KeyParameter;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -101,16 +104,26 @@ class CryptoComponentImpl implements CryptoComponent {
private final MessageEncrypter messageEncrypter;
@Inject
CryptoComponentImpl(SeedProvider seedProvider) {
if (!FortunaSecureRandom.selfTest()) throw new RuntimeException();
SecureRandom platformSecureRandom = new SecureRandom();
CryptoComponentImpl(SecureRandomProvider secureRandomProvider) {
if (LOG.isLoggable(INFO)) {
String provider = platformSecureRandom.getProvider().getName();
String algorithm = platformSecureRandom.getAlgorithm();
LOG.info("Default SecureRandom: " + provider + " " + algorithm);
SecureRandom defaultSecureRandom = new SecureRandom();
String name = defaultSecureRandom.getProvider().getName();
String algorithm = defaultSecureRandom.getAlgorithm();
LOG.info("Default SecureRandom: " + name + " " + algorithm);
}
SecureRandom fortuna = new FortunaSecureRandom(seedProvider.getSeed());
secureRandom = new CombinedSecureRandom(platformSecureRandom, fortuna);
Provider provider = secureRandomProvider.getProvider();
if (provider == null) {
LOG.info("Using default");
} else {
installSecureRandomProvider(provider);
if (LOG.isLoggable(INFO)) {
SecureRandom installedSecureRandom = new SecureRandom();
String name = installedSecureRandom.getProvider().getName();
String algorithm = installedSecureRandom.getAlgorithm();
LOG.info("Installed SecureRandom: " + name + " " + algorithm);
}
}
secureRandom = new SecureRandom();
ECKeyGenerationParameters params = new ECKeyGenerationParameters(
PARAMETERS, secureRandom);
agreementKeyPairGenerator = new ECKeyPairGenerator();
@@ -124,6 +137,31 @@ class CryptoComponentImpl implements CryptoComponent {
messageEncrypter = new MessageEncrypter(secureRandom);
}
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
private void installSecureRandomProvider(Provider provider) {
Provider[] providers = Security.getProviders("SecureRandom.SHA1PRNG");
if (providers == null || providers.length == 0
|| !provider.getClass().equals(providers[0].getClass())) {
Security.insertProviderAt(provider, 1);
}
// Check the new provider is the default when no algorithm is specified
SecureRandom random = new SecureRandom();
if (!provider.getClass().equals(random.getProvider().getClass())) {
throw new SecurityException("Wrong SecureRandom provider: "
+ random.getProvider().getClass());
}
// Check the new provider is the default when SHA1PRNG is specified
try {
random = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new SecurityException(e);
}
if (!provider.getClass().equals(random.getProvider().getClass())) {
throw new SecurityException("Wrong SHA1PRNG provider: "
+ random.getProvider().getClass());
}
}
@Override
public SecretKey generateSecretKey() {
byte[] b = new byte[SecretKey.LENGTH];
@@ -133,7 +171,10 @@ class CryptoComponentImpl implements CryptoComponent {
@Override
public PseudoRandom getPseudoRandom(int seed1, int seed2) {
return new PseudoRandomImpl(seed1, seed2);
byte[] seed = new byte[INT_32_BYTES * 2];
ByteUtils.writeUint32(seed1, seed, 0);
ByteUtils.writeUint32(seed2, seed, INT_32_BYTES);
return new PseudoRandomImpl(seed);
}
@Override
@@ -296,7 +337,7 @@ class CryptoComponentImpl implements CryptoComponent {
public SecretKey deriveMasterSecret(byte[] theirPublicKey,
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
return deriveMasterSecret(deriveSharedSecret(
theirPublicKey,ourKeyPair, alice));
theirPublicKey, ourKeyPair, alice));
}
@Override
@@ -607,7 +648,7 @@ class CryptoComponentImpl implements CryptoComponent {
}
private long sampleRunningTime(int iterations) {
byte[] password = { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' };
byte[] password = {'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
byte[] salt = new byte[PBKDF_SALT_BYTES];
int keyLengthInBits = SecretKey.LENGTH * 8;
long start = System.nanoTime();

View File

@@ -1,12 +1,13 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.TimeLoggingExecutor;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.system.SeedProvider;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import java.security.SecureRandom;
import java.util.concurrent.BlockingQueue;
@@ -31,14 +32,17 @@ public class CryptoModule {
public static class EagerSingletons {
@Inject
@CryptoExecutor
Executor cryptoExecutor;
ExecutorService cryptoExecutor;
}
/**
* The maximum number of executor threads.
* <p>
* The number of available processors can change during the lifetime of the
* JVM, so this is just a reasonable guess.
*/
private static final int MAX_EXECUTOR_THREADS =
Runtime.getRuntime().availableProcessors();
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
private final ExecutorService cryptoExecutor;
@@ -49,8 +53,8 @@ public class CryptoModule {
RejectedExecutionHandler policy =
new ThreadPoolExecutor.DiscardPolicy();
// Create a limited # of threads and keep them in the pool for 60 secs
cryptoExecutor = new ThreadPoolExecutor(0, MAX_EXECUTOR_THREADS,
60, SECONDS, queue, policy);
cryptoExecutor = new TimeLoggingExecutor("CryptoExecutor", 0,
MAX_EXECUTOR_THREADS, 60, SECONDS, queue, policy);
}
@Provides
@@ -60,8 +64,9 @@ public class CryptoModule {
@Provides
@Singleton
CryptoComponent provideCryptoComponent(SeedProvider seedProvider) {
return new CryptoComponentImpl(seedProvider);
CryptoComponent provideCryptoComponent(
SecureRandomProvider secureRandomProvider) {
return new CryptoComponentImpl(secureRandomProvider);
}
@Provides
@@ -84,11 +89,18 @@ public class CryptoModule {
@Provides
@Singleton
@CryptoExecutor
Executor getCryptoExecutor(LifecycleManager lifecycleManager) {
ExecutorService getCryptoExecutorService(
LifecycleManager lifecycleManager) {
lifecycleManager.registerForShutdown(cryptoExecutor);
return cryptoExecutor;
}
@Provides
@CryptoExecutor
Executor getCryptoExecutor() {
return cryptoExecutor;
}
@Provides
SecureRandom getSecureRandom(CryptoComponent crypto) {
return crypto.getSecureRandom();

View File

@@ -1,76 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.spongycastle.crypto.Digest;
import javax.annotation.concurrent.NotThreadSafe;
/**
* A message digest that prevents length extension attacks - see Ferguson and
* Schneier, <i>Practical Cryptography</i>, chapter 6.
* <p>
* "Let h be an interative hash function. The hash function h<sub>d</sub> is
* defined by h<sub>d</sub> := h(h(m)), and has a claimed security level of
* min(k, n/2) where k is the security level of h and n is the size of the hash
* result."
*/
@NotThreadSafe
@NotNullByDefault
class DoubleDigest implements Digest {
private final Digest delegate;
DoubleDigest(Digest delegate) {
this.delegate = delegate;
}
private byte[] digest() {
byte[] digest = new byte[delegate.getDigestSize()];
delegate.doFinal(digest, 0); // h(m)
delegate.update(digest, 0, digest.length);
delegate.doFinal(digest, 0); // h(h(m))
return digest;
}
public int digest(byte[] buf, int offset, int len) {
byte[] digest = digest();
len = Math.min(len, digest.length);
System.arraycopy(digest, 0, buf, offset, len);
return len;
}
@Override
public int getDigestSize() {
return delegate.getDigestSize();
}
@Override
public String getAlgorithmName() {
return "Double " + delegate.getAlgorithmName();
}
@Override
public void reset() {
delegate.reset();
}
@Override
public void update(byte input) {
delegate.update(input);
}
public void update(byte[] input) {
delegate.update(input, 0, input.length);
}
@Override
public void update(byte[] input, int offset, int len) {
delegate.update(input, offset, len);
}
@Override
public int doFinal(byte[] out, int outOff) {
return digest(out, outOff, delegate.getDigestSize());
}
}

View File

@@ -1,114 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.digests.SHA256Digest;
import org.spongycastle.crypto.engines.AESLightEngine;
import org.spongycastle.crypto.params.KeyParameter;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.concurrent.ThreadSafe;
/**
* Implements the Fortuna pseudo-random number generator, as described in
* Ferguson and Schneier, <i>Practical Cryptography</i>, chapter 9.
*/
@ThreadSafe
@NotNullByDefault
class FortunaGenerator {
private static final int MAX_BYTES_PER_REQUEST = 1024 * 1024;
private static final int KEY_BYTES = 32;
private static final int BLOCK_BYTES = 16;
private final Lock lock = new ReentrantLock();
// The following are locking: lock
private final DoubleDigest digest = new DoubleDigest(new SHA256Digest());
private final BlockCipher cipher = new AESLightEngine();
private final byte[] key = new byte[KEY_BYTES];
private final byte[] counter = new byte[BLOCK_BYTES];
private final byte[] buffer = new byte[BLOCK_BYTES];
private final byte[] newKey = new byte[KEY_BYTES];
FortunaGenerator(byte[] seed) {
reseed(seed);
}
void reseed(byte[] seed) {
lock.lock();
try {
digest.update(key);
digest.update(seed);
digest.digest(key, 0, KEY_BYTES);
incrementCounter();
} finally {
lock.unlock();
}
}
// Package access for testing
void incrementCounter() {
lock.lock();
try {
counter[0]++;
for (int i = 0; counter[i] == 0; i++) {
if (i + 1 == BLOCK_BYTES)
throw new RuntimeException("Counter exhausted");
counter[i + 1]++;
}
} finally {
lock.unlock();
}
}
// Package access for testing
byte[] getCounter() {
lock.lock();
try {
return counter;
} finally {
lock.unlock();
}
}
int nextBytes(byte[] dest, int off, int len) {
lock.lock();
try {
// Don't write more than the maximum number of bytes in one request
if (len > MAX_BYTES_PER_REQUEST) len = MAX_BYTES_PER_REQUEST;
cipher.init(true, new KeyParameter(key));
// Generate full blocks directly into the output buffer
int fullBlocks = len / BLOCK_BYTES;
for (int i = 0; i < fullBlocks; i++) {
cipher.processBlock(counter, 0, dest, off + i * BLOCK_BYTES);
incrementCounter();
}
// Generate a partial block if needed
int done = fullBlocks * BLOCK_BYTES, remaining = len - done;
if (remaining >= BLOCK_BYTES) throw new AssertionError();
if (remaining > 0) {
cipher.processBlock(counter, 0, buffer, 0);
incrementCounter();
// Copy the partial block to the output buffer and erase our copy
System.arraycopy(buffer, 0, dest, off + done, remaining);
for (int i = 0; i < BLOCK_BYTES; i++) buffer[i] = 0;
}
// Generate a new key
for (int i = 0; i < KEY_BYTES / BLOCK_BYTES; i++) {
cipher.processBlock(counter, 0, newKey, i * BLOCK_BYTES);
incrementCounter();
}
System.arraycopy(newKey, 0, key, 0, KEY_BYTES);
for (int i = 0; i < KEY_BYTES; i++) newKey[i] = 0;
// Return the number of bytes written
return len;
} finally {
lock.unlock();
}
}
}

View File

@@ -1,81 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.util.StringUtils;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
import java.util.Arrays;
/**
* A {@link java.security.SecureRandom SecureRandom} implementation based on a
* {@link FortunaGenerator}.
*/
class FortunaSecureRandom extends SecureRandom {
// Package access for testing
static final byte[] SELF_TEST_VECTOR_1 =
StringUtils.fromHexString("4BD6EA599D47E3EE9DD911833C29CA22");
static final byte[] SELF_TEST_VECTOR_2 =
StringUtils.fromHexString("10984D576E6850E505CA9F42A9BFD88A");
static final byte[] SELF_TEST_VECTOR_3 =
StringUtils.fromHexString("1E12DA166BD86DCECDE50A8296018DE2");
private static final Provider PROVIDER = new FortunaProvider();
FortunaSecureRandom(byte[] seed) {
super(new FortunaSecureRandomSpi(seed), PROVIDER);
}
/**
* Tests that the {@link #nextBytes(byte[])} and {@link #setSeed(byte[])}
* methods are passed through to the generator in the expected way.
*/
static boolean selfTest() {
byte[] seed = new byte[32];
SecureRandom r = new FortunaSecureRandom(seed);
byte[] output = new byte[16];
r.nextBytes(output);
if (!Arrays.equals(SELF_TEST_VECTOR_1, output)) return false;
r.nextBytes(output);
if (!Arrays.equals(SELF_TEST_VECTOR_2, output)) return false;
r.setSeed(seed);
r.nextBytes(output);
return Arrays.equals(SELF_TEST_VECTOR_3, output);
}
private static class FortunaSecureRandomSpi extends SecureRandomSpi {
private final FortunaGenerator generator;
private FortunaSecureRandomSpi(byte[] seed) {
generator = new FortunaGenerator(seed);
}
@Override
protected byte[] engineGenerateSeed(int numBytes) {
byte[] b = new byte[numBytes];
engineNextBytes(b);
return b;
}
@Override
protected void engineNextBytes(byte[] b) {
int offset = 0;
while (offset < b.length)
offset += generator.nextBytes(b, offset, b.length - offset);
}
@Override
protected void engineSetSeed(byte[] seed) {
generator.reseed(seed);
}
}
private static class FortunaProvider extends Provider {
private FortunaProvider() {
super("Fortuna", 1.0, "");
}
}
}

View File

@@ -11,31 +11,14 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault
class PasswordStrengthEstimatorImpl implements PasswordStrengthEstimator {
private static final int LOWER = 26;
private static final int UPPER = 26;
private static final int DIGIT = 10;
private static final int OTHER = 10;
private static final double STRONG = Math.log(Math.pow(LOWER + UPPER +
DIGIT + OTHER, 10));
// The minimum number of unique characters in a strong password
private static final int STRONG_UNIQUE_CHARS = 12;
@Override
public float estimateStrength(String password) {
HashSet<Character> unique = new HashSet<Character>();
int length = password.length();
for (int i = 0; i < length; i++) unique.add(password.charAt(i));
boolean lower = false, upper = false, digit = false, other = false;
for (char c : unique) {
if (Character.isLowerCase(c)) lower = true;
else if (Character.isUpperCase(c)) upper = true;
else if (Character.isDigit(c)) digit = true;
else other = true;
}
int alphabetSize = 0;
if (lower) alphabetSize += LOWER;
if (upper) alphabetSize += UPPER;
if (digit) alphabetSize += DIGIT;
if (other) alphabetSize += OTHER;
double score = Math.log(Math.pow(alphabetSize, unique.size()));
return Math.min(1, (float) (score / STRONG));
return Math.min(1, (float) unique.size() / STRONG_UNIQUE_CHARS);
}
}

View File

@@ -2,30 +2,34 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.PseudoRandom;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.ByteUtils;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.engines.Salsa20Engine;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
@NotThreadSafe
@NotNullByDefault
class PseudoRandomImpl implements PseudoRandom {
private final FortunaGenerator generator;
private final Salsa20Engine cipher = new Salsa20Engine();
PseudoRandomImpl(int seed1, int seed2) {
byte[] seed = new byte[INT_32_BYTES * 2];
ByteUtils.writeUint32(seed1, seed, 0);
ByteUtils.writeUint32(seed2, seed, INT_32_BYTES);
generator = new FortunaGenerator(seed);
PseudoRandomImpl(byte[] seed) {
// Hash the seed to produce a 32-byte key
byte[] key = new byte[32];
Digest digest = new Blake2sDigest();
digest.update(seed, 0, seed.length);
digest.doFinal(key, 0);
// Initialise the stream cipher with an all-zero nonce
byte[] nonce = new byte[8];
cipher.init(true, new ParametersWithIV(new KeyParameter(key), nonce));
}
@Override
public byte[] nextBytes(int length) {
byte[] b = new byte[length];
int offset = 0;
while (offset < length) offset += generator.nextBytes(b, offset, length);
return b;
byte[] in = new byte[length], out = new byte[length];
cipher.processBytes(in, 0, length, out, 0);
return out;
}
}

View File

@@ -62,6 +62,8 @@ class StreamEncrypterImpl implements StreamEncrypter {
@Override
public void writeFrame(byte[] payload, int payloadLength,
int paddingLength, boolean finalFrame) throws IOException {
if (payloadLength < 0 || paddingLength < 0)
throw new IllegalArgumentException();
if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
throw new IllegalArgumentException();
// Don't allow the frame counter to wrap

View File

@@ -67,6 +67,7 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
@@ -130,8 +131,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
// Don't allow reentrant locking
if (lock.getReadHoldCount() > 0) throw new IllegalStateException();
if (lock.getWriteHoldCount() > 0) throw new IllegalStateException();
long start = System.currentTimeMillis();
if (readOnly) lock.readLock().lock();
else lock.writeLock().lock();
if (LOG.isLoggable(FINE)) {
long duration = System.currentTimeMillis() - start;
if (readOnly) LOG.fine("Waited " + duration + " ms for read lock");
else LOG.fine("Waited " + duration + " ms for write lock");
}
try {
return new Transaction(db.startTransaction(), readOnly);
} catch (DbException e) {
@@ -661,7 +668,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
acked.add(m);
}
}
transaction.attach(new MessagesAckedEvent(c, acked));
if (acked.size() > 0) {
transaction.attach(new MessagesAckedEvent(c, acked));
}
}
@Override

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.TimeLoggingExecutor;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
@@ -36,8 +37,8 @@ public class DatabaseExecutorModule {
RejectedExecutionHandler policy =
new ThreadPoolExecutor.DiscardPolicy();
// Use a single thread and keep it in the pool for 60 secs
databaseExecutor = new ThreadPoolExecutor(0, 1, 60, SECONDS, queue,
policy);
databaseExecutor = new TimeLoggingExecutor("DatabaseExecutor", 0, 1,
60, SECONDS, queue, policy);
}
@Provides

View File

@@ -68,8 +68,8 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
@NotNullByDefault
abstract class JdbcDatabase implements Database<Connection> {
private static final int SCHEMA_VERSION = 29;
private static final int MIN_SCHEMA_VERSION = 29;
private static final int SCHEMA_VERSION = 30;
private static final int MIN_SCHEMA_VERSION = 30;
private static final String CREATE_SETTINGS =
"CREATE TABLE settings"

View File

@@ -34,6 +34,7 @@ import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PREF_LAN_IP_PORTS;
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
@@ -43,7 +44,7 @@ class LanTcpPlugin extends TcpPlugin {
private static final Logger LOG =
Logger.getLogger(LanTcpPlugin.class.getName());
private static final int MAX_ADDRESSES = 5;
private static final int MAX_ADDRESSES = 4;
private static final String PROP_IP_PORTS = "ipPorts";
private static final String SEPARATOR = ",";
@@ -82,19 +83,19 @@ class LanTcpPlugin extends TcpPlugin {
private List<InetSocketAddress> parseSocketAddresses(String ipPorts) {
if (StringUtils.isNullOrEmpty(ipPorts)) return Collections.emptyList();
String[] split = ipPorts.split(SEPARATOR);
List<InetSocketAddress> remotes = new ArrayList<InetSocketAddress>();
List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>();
for (String ipPort : split) {
InetSocketAddress a = parseSocketAddress(ipPort);
if (a != null) remotes.add(a);
if (a != null) addresses.add(a);
}
return remotes;
return addresses;
}
@Override
protected void setLocalSocketAddress(InetSocketAddress a) {
String ipPort = getIpPortString(a);
// Get the list of recently used addresses
String setting = callback.getSettings().get(PROP_IP_PORTS);
String setting = callback.getSettings().get(PREF_LAN_IP_PORTS);
List<String> recent = new ArrayList<String>();
if (!StringUtils.isNullOrEmpty(setting))
Collections.addAll(recent, setting.split(SEPARATOR));
@@ -120,7 +121,7 @@ class LanTcpPlugin extends TcpPlugin {
}
// Save the setting
Settings settings = new Settings();
settings.put(PROP_IP_PORTS, setting);
settings.put(PREF_LAN_IP_PORTS, setting);
callback.mergeSettings(settings);
}

View File

@@ -8,6 +8,7 @@ import dagger.Module;
import dagger.Provides;
import static org.briarproject.bramble.api.plugin.TorConstants.CONNECT_TO_PROXY_TIMEOUT;
import static org.briarproject.bramble.api.plugin.TorConstants.EXTRA_SOCKET_TIMEOUT;
import static org.briarproject.bramble.api.plugin.TorConstants.SOCKS_PORT;
@Module
@@ -17,6 +18,7 @@ public class SocksModule {
SocketFactory provideTorSocketFactory() {
InetSocketAddress proxy = new InetSocketAddress("127.0.0.1",
SOCKS_PORT);
return new SocksSocketFactory(proxy, CONNECT_TO_PROXY_TIMEOUT);
return new SocksSocketFactory(proxy, CONNECT_TO_PROXY_TIMEOUT,
EXTRA_SOCKET_TIMEOUT);
}
}

View File

@@ -6,18 +6,36 @@ import org.briarproject.bramble.util.IoUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Arrays;
class SocksSocket extends Socket {
private final SocketAddress proxy;
private final int connectToProxyTimeout;
private static final String[] ERRORS = {
"Succeeded",
"General SOCKS server failure",
"Connection not allowed by ruleset",
"Network unreachable",
"Host unreachable",
"Connection refused",
"TTL expired",
"Command not supported",
"Address type not supported"
};
SocksSocket(SocketAddress proxy, int connectToProxyTimeout) {
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
private final SocketAddress proxy;
private final int connectToProxyTimeout, extraSocketTimeout;
SocksSocket(SocketAddress proxy, int connectToProxyTimeout,
int extraSocketTimeout) {
this.proxy = proxy;
this.connectToProxyTimeout = connectToProxyTimeout;
this.extraSocketTimeout = extraSocketTimeout;
}
@Override
@@ -28,6 +46,11 @@ class SocksSocket extends Socket {
if (!(endpoint instanceof InetSocketAddress))
throw new IllegalArgumentException();
InetSocketAddress inet = (InetSocketAddress) endpoint;
InetAddress address = inet.getAddress();
if (address != null
&& !Arrays.equals(address.getAddress(), UNSPECIFIED_ADDRESS)) {
throw new IllegalArgumentException();
}
String host = inet.getHostName();
if (host.length() > 255) throw new IllegalArgumentException();
int port = inet.getPort();
@@ -41,16 +64,16 @@ class SocksSocket extends Socket {
sendMethodRequest(out);
receiveMethodResponse(in);
// Use the supplied timeout temporarily
// Use the supplied timeout temporarily, plus any configured extra
int oldTimeout = getSoTimeout();
setSoTimeout(timeout);
setSoTimeout(timeout + extraSocketTimeout);
// Connect to the endpoint via the proxy
sendConnectRequest(out, host, port);
receiveConnectResponse(in);
// Restore the old timeout
setSoTimeout(oldTimeout);
// Restore the old timeout, plus any configured extra
setSoTimeout(oldTimeout + extraSocketTimeout);
}
private void sendMethodRequest(OutputStream out) throws IOException {
@@ -93,13 +116,16 @@ class SocksSocket extends Socket {
private void receiveConnectResponse(InputStream in) throws IOException {
byte[] connectResponse = new byte[4];
IoUtils.read(in, connectResponse);
byte version = connectResponse[0];
byte reply = connectResponse[1];
byte addressType = connectResponse[3];
int version = connectResponse[0] & 0xFF;
int reply = connectResponse[1] & 0xFF;
int addressType = connectResponse[3] & 0xFF;
if (version != 5)
throw new IOException("Unsupported SOCKS version: " + version);
if (reply != 0)
throw new IOException("Connection failed: " + reply);
if (reply != 0) {
if (reply < ERRORS.length)
throw new IOException("Connection failed: " + ERRORS[reply]);
else throw new IOException("Connection failed: " + reply);
}
if (addressType == 1) IoUtils.read(in, new byte[4]); // IPv4
else if (addressType == 4) IoUtils.read(in, new byte[16]); // IPv6
else throw new IOException("Unsupported address type: " + addressType);

View File

@@ -11,16 +11,18 @@ import javax.net.SocketFactory;
class SocksSocketFactory extends SocketFactory {
private final SocketAddress proxy;
private final int connectToProxyTimeout;
private final int connectToProxyTimeout, extraSocketTimeout;
SocksSocketFactory(SocketAddress proxy, int connectToProxyTimeout) {
SocksSocketFactory(SocketAddress proxy, int connectToProxyTimeout,
int extraSocketTimeout) {
this.proxy = proxy;
this.connectToProxyTimeout = connectToProxyTimeout;
this.extraSocketTimeout = extraSocketTimeout;
}
@Override
public Socket createSocket() {
return new SocksSocket(proxy, connectToProxyTimeout);
return new SocksSocket(proxy, connectToProxyTimeout, extraSocketTimeout);
}
@Override

View File

@@ -1,6 +1,8 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.event.EventBus;
@@ -29,6 +31,16 @@ public class SyncModule {
ValidationManager validationManager;
}
/**
* The maximum number of validation tasks to delegate to the crypto
* executor concurrently.
* <p>
* The number of available processors can change during the lifetime of the
* JVM, so this is just a reasonable guess.
*/
private static final int MAX_CONCURRENT_VALIDATION_TASKS =
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
@Provides
GroupFactory provideGroupFactory(CryptoComponent crypto) {
return new GroupFactoryImpl(crypto);
@@ -62,10 +74,20 @@ public class SyncModule {
@Provides
@Singleton
ValidationManager getValidationManager(LifecycleManager lifecycleManager,
EventBus eventBus, ValidationManagerImpl validationManager) {
ValidationManager provideValidationManager(
LifecycleManager lifecycleManager, EventBus eventBus,
ValidationManagerImpl validationManager) {
lifecycleManager.registerService(validationManager);
eventBus.addListener(validationManager);
return validationManager;
}
@Provides
@Singleton
@ValidationExecutor
Executor provideValidationExecutor(
@CryptoExecutor Executor cryptoExecutor) {
return new PoliteExecutor("ValidationExecutor", cryptoExecutor,
MAX_CONCURRENT_VALIDATION_TASKS);
}
}

View File

@@ -0,0 +1,25 @@
package org.briarproject.bramble.sync;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotation for injecting the executor for validation tasks. Also used for
* annotating methods that should run on the validation executor.
* <p>
* The contract of this executor is that tasks may be run concurrently, and
* submitting a task will never block. Tasks must not run indefinitely. Tasks
* submitted during shutdown are discarded.
*/
@Qualifier
@Target({FIELD, METHOD, PARAMETER})
@Retention(RUNTIME)
@interface ValidationExecutor {
}

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
@@ -50,8 +49,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
Logger.getLogger(ValidationManagerImpl.class.getName());
private final DatabaseComponent db;
private final Executor dbExecutor;
private final Executor cryptoExecutor;
private final Executor dbExecutor, validationExecutor;
private final MessageFactory messageFactory;
private final Map<ClientId, MessageValidator> validators;
private final Map<ClientId, IncomingMessageHook> hooks;
@@ -60,11 +58,11 @@ class ValidationManagerImpl implements ValidationManager, Service,
@Inject
ValidationManagerImpl(DatabaseComponent db,
@DatabaseExecutor Executor dbExecutor,
@CryptoExecutor Executor cryptoExecutor,
@ValidationExecutor Executor validationExecutor,
MessageFactory messageFactory) {
this.db = db;
this.dbExecutor = dbExecutor;
this.cryptoExecutor = cryptoExecutor;
this.validationExecutor = validationExecutor;
this.messageFactory = messageFactory;
validators = new ConcurrentHashMap<ClientId, MessageValidator>();
hooks = new ConcurrentHashMap<ClientId, IncomingMessageHook>();
@@ -104,6 +102,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void validateOutstandingMessages(ClientId c) {
try {
Queue<MessageId> unvalidated = new LinkedList<MessageId>();
@@ -130,6 +129,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void validateNextMessage(Queue<MessageId> unvalidated) {
try {
Message m;
@@ -167,6 +167,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void deliverOutstandingMessages(ClientId c) {
try {
Queue<MessageId> pending = new LinkedList<MessageId>();
@@ -194,6 +195,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void deliverNextPendingMessage(Queue<MessageId> pending) {
try {
boolean anyInvalid = false, allDelivered = true;
@@ -220,8 +222,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
Message m = messageFactory.createMessage(id, raw);
Group g = db.getGroup(txn, m.getGroupId());
ClientId c = g.getClientId();
Metadata meta = db.getMessageMetadataForValidator(txn,
id);
Metadata meta =
db.getMessageMetadataForValidator(txn, id);
DeliveryResult result = deliverMessage(txn, m, c, meta);
if (result.valid) {
pending.addAll(getPendingDependents(txn, id));
@@ -240,8 +242,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
db.endTransaction(txn);
}
if (invalidate != null) invalidateNextMessageAsync(invalidate);
deliverNextPendingMessageAsync(pending);
if (toShare != null) shareNextMessageAsync(toShare);
deliverNextPendingMessageAsync(pending);
} catch (NoSuchMessageException e) {
LOG.info("Message removed before delivery");
deliverNextPendingMessageAsync(pending);
@@ -249,13 +251,12 @@ class ValidationManagerImpl implements ValidationManager, Service,
LOG.info("Group removed before delivery");
deliverNextPendingMessageAsync(pending);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private void validateMessageAsync(final Message m, final Group g) {
cryptoExecutor.execute(new Runnable() {
validationExecutor.execute(new Runnable() {
@Override
public void run() {
validateMessage(m, g);
@@ -263,10 +264,12 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@ValidationExecutor
private void validateMessage(Message m, Group g) {
MessageValidator v = validators.get(g.getClientId());
if (v == null) {
LOG.warning("No validator");
if (LOG.isLoggable(WARNING))
LOG.warning("No validator for " + g.getClientId().getString());
} else {
try {
MessageContext context = v.validateMessage(m, g);
@@ -291,6 +294,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void storeMessageContext(Message m, ClientId c,
MessageContext context) {
try {
@@ -353,6 +357,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
}
}
@DatabaseExecutor
private DeliveryResult deliverMessage(Transaction txn, Message m,
ClientId c, Metadata meta) throws DbException {
// Deliver the message to the client if it's registered a hook
@@ -362,10 +367,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
try {
shareMsg = hook.incomingMessage(txn, m, meta);
} catch (InvalidMessageException e) {
// message is invalid, mark it as such and delete it
db.setMessageState(txn, m.getId(), INVALID);
db.deleteMessageMetadata(txn, m.getId());
db.deleteMessage(txn, m.getId());
invalidateMessage(txn, m.getId());
return new DeliveryResult(false, false);
}
}
@@ -373,6 +375,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
return new DeliveryResult(true, shareMsg);
}
@DatabaseExecutor
private Queue<MessageId> getPendingDependents(Transaction txn, MessageId m)
throws DbException {
Queue<MessageId> pending = new LinkedList<MessageId>();
@@ -392,6 +395,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void shareOutstandingMessages(ClientId c) {
try {
Queue<MessageId> toShare = new LinkedList<MessageId>();
@@ -424,6 +428,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void shareNextMessage(Queue<MessageId> toShare) {
try {
Transaction txn = db.startTransaction(false);
@@ -457,6 +462,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void invalidateNextMessage(Queue<MessageId> invalidate) {
try {
Transaction txn = db.startTransaction(false);
@@ -479,6 +485,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
}
}
@DatabaseExecutor
private void invalidateMessage(Transaction txn, MessageId m)
throws DbException {
db.setMessageState(txn, m, INVALID);
@@ -486,6 +493,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
db.deleteMessageMetadata(txn, m);
}
@DatabaseExecutor
private Queue<MessageId> getDependentsToInvalidate(Transaction txn,
MessageId m) throws DbException {
Queue<MessageId> invalidate = new LinkedList<MessageId>();
@@ -515,6 +523,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
});
}
@DatabaseExecutor
private void loadGroupAndValidate(final Message m) {
try {
Group g;
@@ -534,6 +543,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
}
private static class DeliveryResult {
private final boolean valid, share;
private DeliveryResult(boolean valid, boolean share) {

View File

@@ -0,0 +1,42 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
abstract class AbstractSecureRandomProvider implements SecureRandomProvider {
// Contribute whatever slightly unpredictable info we have to the pool
protected void writeToEntropyPool(DataOutputStream out) throws IOException {
out.writeLong(System.currentTimeMillis());
out.writeLong(System.nanoTime());
out.writeLong(Runtime.getRuntime().freeMemory());
List<NetworkInterface> ifaces =
Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface i : ifaces) {
List<InetAddress> addrs = Collections.list(i.getInetAddresses());
for (InetAddress a : addrs) out.write(a.getAddress());
byte[] hardware = i.getHardwareAddress();
if (hardware != null) out.write(hardware);
}
for (Entry<String, String> e : System.getenv().entrySet()) {
out.writeUTF(e.getKey());
out.writeUTF(e.getValue());
}
Properties properties = System.getProperties();
for (String key : properties.stringPropertyNames())
out.writeUTF(properties.getProperty(key));
}
}

View File

@@ -0,0 +1,69 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.Provider;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import static java.util.logging.Level.WARNING;
@Immutable
@NotNullByDefault
class LinuxSecureRandomProvider extends AbstractSecureRandomProvider {
private static final Logger LOG =
Logger.getLogger(LinuxSecureRandomProvider.class.getName());
private static final File RANDOM_DEVICE = new File("/dev/urandom");
private final AtomicBoolean seeded = new AtomicBoolean(false);
private final File outputDevice;
LinuxSecureRandomProvider() {
this(RANDOM_DEVICE);
}
LinuxSecureRandomProvider(File outputDevice) {
this.outputDevice = outputDevice;
}
@Override
public Provider getProvider() {
if (!seeded.getAndSet(true)) writeSeed();
return new LinuxProvider();
}
protected void writeSeed() {
try {
DataOutputStream out = new DataOutputStream(
new FileOutputStream(outputDevice));
writeToEntropyPool(out);
out.flush();
out.close();
} catch (IOException e) {
// On some devices /dev/urandom isn't writable - this isn't fatal
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
private static class LinuxProvider extends Provider {
private LinuxProvider() {
super("LinuxPRNG", 1.1, "A Linux-specific PRNG using /dev/urandom");
// Although /dev/urandom is not a SHA-1 PRNG, some callers
// explicitly request a SHA1PRNG SecureRandom and we need to
// prevent them from getting the default implementation whose
// output may have low entropy.
put("SecureRandom.SHA1PRNG", LinuxSecureRandomSpi.class.getName());
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
}
}
}

View File

@@ -0,0 +1,64 @@
package org.briarproject.bramble.system;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.SecureRandomSpi;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
public class LinuxSecureRandomSpi extends SecureRandomSpi {
private static final Logger LOG =
Logger.getLogger(LinuxSecureRandomSpi.class.getName());
private static final File RANDOM_DEVICE = new File("/dev/urandom");
private final File inputDevice, outputDevice;
public LinuxSecureRandomSpi() {
this(RANDOM_DEVICE, RANDOM_DEVICE);
}
LinuxSecureRandomSpi(File inputDevice, File outputDevice) {
this.inputDevice = inputDevice;
this.outputDevice = outputDevice;
}
@Override
protected void engineSetSeed(byte[] seed) {
try {
DataOutputStream out = new DataOutputStream(
new FileOutputStream(outputDevice));
out.write(seed);
out.flush();
out.close();
} catch (IOException e) {
// On some devices /dev/urandom isn't writable - this isn't fatal
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
@Override
protected void engineNextBytes(byte[] bytes) {
try {
DataInputStream in = new DataInputStream(
new FileInputStream(inputDevice));
in.readFully(bytes);
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected byte[] engineGenerateSeed(int len) {
byte[] seed = new byte[len];
engineNextBytes(seed);
return seed;
}
}

View File

@@ -1,75 +0,0 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.SeedProvider;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import static java.util.logging.Level.WARNING;
@Immutable
@NotNullByDefault
class LinuxSeedProvider implements SeedProvider {
private static final Logger LOG =
Logger.getLogger(LinuxSeedProvider.class.getName());
private final String outputFile, inputFile;
LinuxSeedProvider() {
this("/dev/urandom", "/dev/urandom");
}
LinuxSeedProvider(String outputFile, String inputFile) {
this.outputFile = outputFile;
this.inputFile = inputFile;
}
@Override
public byte[] getSeed() {
byte[] seed = new byte[SEED_BYTES];
// Contribute whatever slightly unpredictable info we have to the pool
try {
DataOutputStream out = new DataOutputStream(
new FileOutputStream(outputFile));
writeToEntropyPool(out);
out.flush();
out.close();
} catch (IOException e) {
// On some devices /dev/urandom isn't writable - this isn't fatal
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
// Read the seed from the pool
try {
DataInputStream in = new DataInputStream(
new FileInputStream(inputFile));
in.readFully(seed);
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
return seed;
}
void writeToEntropyPool(DataOutputStream out) throws IOException {
out.writeLong(System.currentTimeMillis());
out.writeLong(System.nanoTime());
List<NetworkInterface> ifaces =
Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface i : ifaces) {
List<InetAddress> addrs = Collections.list(i.getInetAddresses());
for (InetAddress a : addrs) out.write(a.getAddress());
}
}
}

View File

@@ -0,0 +1,142 @@
package org.briarproject.bramble;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class PoliteExecutorTest extends BrambleTestCase {
private static final String TAG = "Test";
private static final int TASKS = 10;
@Test
public void testTasksAreDelegatedInOrderOfSubmission() throws Exception {
// Delegate to a single-threaded executor
Executor delegate = Executors.newSingleThreadExecutor();
// Allow all the tasks to be delegated straight away
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, TASKS * 2);
final List<Integer> list = new Vector<Integer>();
final CountDownLatch latch = new CountDownLatch(TASKS);
for (int i = 0; i < TASKS; i++) {
final int result = i;
polite.execute(new Runnable() {
@Override
public void run() {
list.add(result);
latch.countDown();
}
});
}
// Wait for all the tasks to finish
latch.await();
// The tasks should have run in the order they were submitted
assertEquals(ascendingOrder(), list);
}
@Test
public void testQueuedTasksAreDelegatedInOrderOfSubmission()
throws Exception {
// Delegate to a single-threaded executor
Executor delegate = Executors.newSingleThreadExecutor();
// Allow two tasks to be delegated at a time
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, 2);
final List<Integer> list = new Vector<Integer>();
final CountDownLatch latch = new CountDownLatch(TASKS);
for (int i = 0; i < TASKS; i++) {
final int result = i;
polite.execute(new Runnable() {
@Override
public void run() {
list.add(result);
latch.countDown();
}
});
}
// Wait for all the tasks to finish
latch.await();
// The tasks should have run in the order they were submitted
assertEquals(ascendingOrder(), list);
}
@Test
public void testTasksRunInParallelOnDelegate() throws Exception {
// Delegate to a multi-threaded executor
Executor delegate = Executors.newCachedThreadPool();
// Allow all the tasks to be delegated straight away
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, TASKS * 2);
final List<Integer> list = new Vector<Integer>();
final CountDownLatch[] latches = new CountDownLatch[TASKS];
for (int i = 0; i < TASKS; i++) latches[i] = new CountDownLatch(1);
for (int i = 0; i < TASKS; i++) {
final int result = i;
polite.execute(new Runnable() {
@Override
public void run() {
try {
// Each task waits for the next task, if any, to finish
if (result < TASKS - 1) latches[result + 1].await();
list.add(result);
} catch (InterruptedException e) {
fail();
}
latches[result].countDown();
}
});
}
// Wait for all the tasks to finish
for (int i = 0; i < TASKS; i++) latches[i].await();
// The tasks should have finished in reverse order
assertEquals(descendingOrder(), list);
}
@Test
public void testTasksDoNotRunInParallelOnDelegate() throws Exception {
// Delegate to a multi-threaded executor
Executor delegate = Executors.newCachedThreadPool();
// Allow one task to be delegated at a time
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, 1);
final List<Integer> list = new Vector<Integer>();
final CountDownLatch latch = new CountDownLatch(TASKS);
for (int i = 0; i < TASKS; i++) {
final int result = i;
polite.execute(new Runnable() {
@Override
public void run() {
try {
// Each task runs faster than the previous task
Thread.sleep(TASKS - result);
list.add(result);
} catch (InterruptedException e) {
fail();
}
latch.countDown();
}
});
}
// Wait for all the tasks to finish
latch.await();
// The tasks should have finished in the order they were submitted
assertEquals(ascendingOrder(), list);
}
private List<Integer> ascendingOrder() {
Integer[] array = new Integer[TASKS];
for (int i = 0; i < TASKS; i++) array[i] = i;
return Arrays.asList(array);
}
private List<Integer> descendingOrder() {
Integer[] array = new Integer[TASKS];
for (int i = 0; i < TASKS; i++) array[i] = TASKS - 1 - i;
return Arrays.asList(array);
}
}

View File

@@ -45,7 +45,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
byte[] seed = new byte[32];
new SecureRandom().nextBytes(seed);
// Montgomery ladder multiplier
SecureRandom random = new FortunaSecureRandom(seed);
SecureRandom random = new PseudoSecureRandom(seed);
ECKeyGenerationParameters montgomeryGeneratorParams =
new ECKeyGenerationParameters(PARAMETERS, random);
ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator();
@@ -63,7 +63,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
ECPublicKeyParameters montgomeryPublic2 =
(ECPublicKeyParameters) montgomeryKeyPair2.getPublic();
// Default multiplier
random = new FortunaSecureRandom(seed);
random = new PseudoSecureRandom(seed);
ECKeyGenerationParameters defaultGeneratorParams =
new ECKeyGenerationParameters(defaultParameters, random);
ECKeyPairGenerator defaultGenerator = new ECKeyPairGenerator();

View File

@@ -1,99 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.engines.AESLightEngine;
import org.spongycastle.crypto.params.KeyParameter;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class FortunaGeneratorTest extends BrambleTestCase {
@Test
public void testCounterInitialisedToOne() {
FortunaGenerator f = new FortunaGenerator(new byte[32]);
// The counter is little-endian
byte[] expected = new byte[16];
expected[0] = 1;
assertArrayEquals(expected, f.getCounter());
}
@Test
public void testIncrementCounter() {
FortunaGenerator f = new FortunaGenerator(new byte[32]);
// Increment the counter until it reaches 255
for (int i = 1; i < 255; i++) f.incrementCounter();
byte[] expected = new byte[16];
expected[0] = (byte) 255;
assertArrayEquals(expected, f.getCounter());
// Increment the counter again - it should carry into the next byte
f.incrementCounter();
expected[0] = 0;
expected[1] = 1;
assertArrayEquals(expected, f.getCounter());
// Increment the counter until it carries into the next byte
for (int i = 256; i < 65536; i++) f.incrementCounter();
expected[0] = 0;
expected[1] = 0;
expected[2] = 1;
assertArrayEquals(expected, f.getCounter());
}
@Test
public void testNextBytes() {
// Generate several outputs with the same seed - they should all match
byte[] seed = new byte[32];
byte[] out1 = new byte[48];
new FortunaGenerator(seed).nextBytes(out1, 0, 48);
// One byte longer than a block, with an offset of one
byte[] out2 = new byte[49];
new FortunaGenerator(seed).nextBytes(out2, 1, 48);
for (int i = 0; i < 48; i++) assertEquals(out1[i], out2[i + 1]);
// One byte shorter than a block
byte[] out3 = new byte[47];
new FortunaGenerator(seed).nextBytes(out3, 0, 47);
for (int i = 0; i < 47; i++) assertEquals(out1[i], out3[i]);
// Less than a block, with an offset greater than a block
byte[] out4 = new byte[32];
new FortunaGenerator(seed).nextBytes(out4, 17, 15);
for (int i = 0; i < 15; i++) assertEquals(out1[i], out4[i + 17]);
}
@Test
public void testRekeying() {
byte[] seed = new byte[32];
FortunaGenerator f = new FortunaGenerator(seed);
// Generate three blocks of output
byte[] out1 = new byte[48];
f.nextBytes(out1, 0, 48);
// Create another generator with the same seed and generate one block
f = new FortunaGenerator(seed);
byte[] out2 = new byte[16];
f.nextBytes(out2, 0, 16);
// The generator should have rekeyed with the 2nd and 3rd blocks
byte[] expectedKey = new byte[32];
System.arraycopy(out1, 16, expectedKey, 0, 32);
// The generator's counter should have been incremented 3 times
byte[] expectedCounter = new byte[16];
expectedCounter[0] = 4;
// The next expected output block is the counter encrypted with the key
byte[] expectedOutput = new byte[16];
BlockCipher c = new AESLightEngine();
c.init(true, new KeyParameter(expectedKey));
c.processBlock(expectedCounter, 0, expectedOutput, 0);
// Check that the generator produces the expected output block
byte[] out3 = new byte[16];
f.nextBytes(out3, 0, 16);
assertArrayEquals(expectedOutput, out3);
}
@Test
public void testMaximumRequestLength() {
int expectedMax = 1024 * 1024;
byte[] output = new byte[expectedMax + 123];
FortunaGenerator f = new FortunaGenerator(new byte[32]);
assertEquals(expectedMax, f.nextBytes(output, 0, output.length));
}
}

View File

@@ -1,67 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.digests.SHA256Digest;
import org.spongycastle.crypto.engines.AESLightEngine;
import org.spongycastle.crypto.params.KeyParameter;
import static org.briarproject.bramble.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_1;
import static org.briarproject.bramble.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_2;
import static org.briarproject.bramble.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_3;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
public class FortunaSecureRandomTest extends BrambleTestCase {
@Test
public void testClassPassesSelfTest() {
assertTrue(FortunaSecureRandom.selfTest());
}
@Test
public void testSelfTestVectorsAreReproducible() {
byte[] key = new byte[32], seed = new byte[32];
byte[] counter = new byte[16], output = new byte[16];
byte[] newKey = new byte[32];
// Calculate the initial key
DoubleDigest digest = new DoubleDigest(new SHA256Digest());
digest.update(key);
digest.update(seed);
digest.digest(key, 0, 32);
// Calculate the first output block and the new key
BlockCipher c = new AESLightEngine();
c.init(true, new KeyParameter(key));
counter[0] = 1;
c.processBlock(counter, 0, output, 0);
counter[0] = 2;
c.processBlock(counter, 0, newKey, 0);
counter[0] = 3;
c.processBlock(counter, 0, newKey, 16);
System.arraycopy(newKey, 0, key, 0, 32);
// The first self-test vector should match the first output block
assertArrayEquals(SELF_TEST_VECTOR_1, output);
// Calculate the second output block and the new key before reseeding
c.init(true, new KeyParameter(key));
counter[0] = 4;
c.processBlock(counter, 0, output, 0);
counter[0] = 5;
c.processBlock(counter, 0, newKey, 0);
counter[0] = 6;
c.processBlock(counter, 0, newKey, 16);
System.arraycopy(newKey, 0, key, 0, 32);
// The second self-test vector should match the second output block
assertArrayEquals(SELF_TEST_VECTOR_2, output);
// Calculate the new key after reseeding
digest.update(key);
digest.update(seed);
digest.digest(key, 0, 32);
// Calculate the third output block
c.init(true, new KeyParameter(key));
counter[0] = 8;
c.processBlock(counter, 0, output, 0);
// The third self-test vector should match the third output block
assertArrayEquals(SELF_TEST_VECTOR_3, output);
}
}

View File

@@ -2,7 +2,7 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSeedProvider;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
import org.junit.Test;
@@ -21,7 +21,7 @@ public class HashTest extends BrambleTestCase {
private final byte[] inputBytes2 = new byte[0];
public HashTest() {
crypto = new CryptoComponentImpl(new TestSeedProvider());
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
}
@Test

View File

@@ -3,9 +3,9 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.system.SeedProvider;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSeedProvider;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
@@ -14,8 +14,9 @@ public class KeyAgreementTest extends BrambleTestCase {
@Test
public void testDeriveMasterSecret() throws Exception {
SeedProvider seedProvider = new TestSeedProvider();
CryptoComponent crypto = new CryptoComponentImpl(seedProvider);
SecureRandomProvider
secureRandomProvider = new TestSecureRandomProvider();
CryptoComponent crypto = new CryptoComponentImpl(secureRandomProvider);
KeyPair aPair = crypto.generateAgreementKeyPair();
byte[] aPub = aPair.getPublic().getEncoded();
KeyPair bPair = crypto.generateAgreementKeyPair();
@@ -27,8 +28,9 @@ public class KeyAgreementTest extends BrambleTestCase {
@Test
public void testDeriveSharedSecret() throws Exception {
SeedProvider seedProvider = new TestSeedProvider();
CryptoComponent crypto = new CryptoComponentImpl(seedProvider);
SecureRandomProvider
secureRandomProvider = new TestSecureRandomProvider();
CryptoComponent crypto = new CryptoComponentImpl(secureRandomProvider);
KeyPair aPair = crypto.generateAgreementKeyPair();
byte[] aPub = aPair.getPublic().getEncoded();
KeyPair bPair = crypto.generateAgreementKeyPair();

View File

@@ -1,20 +1,24 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSeedProvider;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class KeyDerivationTest extends BrambleTestCase {
@@ -23,7 +27,7 @@ public class KeyDerivationTest extends BrambleTestCase {
private final SecretKey master;
public KeyDerivationTest() {
crypto = new CryptoComponentImpl(new TestSeedProvider());
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
master = TestUtils.getSecretKey();
}
@@ -156,11 +160,7 @@ public class KeyDerivationTest extends BrambleTestCase {
}
private void assertAllDifferent(List<SecretKey> keys) {
for (SecretKey ki : keys) {
for (SecretKey kj : keys) {
if (ki == kj) assertArrayEquals(ki.getBytes(), kj.getBytes());
else assertFalse(Arrays.equals(ki.getBytes(), kj.getBytes()));
}
}
Set<Bytes> set = new HashSet<Bytes>();
for (SecretKey k : keys) assertTrue(set.add(new Bytes(k.getBytes())));
}
}

View File

@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSeedProvider;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
import org.junit.Test;
@@ -19,7 +19,7 @@ import static org.junit.Assert.assertTrue;
public class KeyEncodingAndParsingTest extends BrambleTestCase {
private final CryptoComponentImpl crypto =
new CryptoComponentImpl(new TestSeedProvider());
new CryptoComponentImpl(new TestSecureRandomProvider());
@Test
public void testAgreementPublicKeyLength() throws Exception {

View File

@@ -3,7 +3,7 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSeedProvider;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
import org.junit.Test;
@@ -22,7 +22,7 @@ public class MacTest extends BrambleTestCase {
private final byte[] inputBytes2 = new byte[0];
public MacTest() {
crypto = new CryptoComponentImpl(new TestSeedProvider());
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
}
@Test

View File

@@ -1,7 +1,7 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSeedProvider;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
import org.junit.Test;
@@ -15,7 +15,7 @@ import static org.junit.Assert.assertTrue;
public class PasswordBasedKdfTest extends BrambleTestCase {
private final CryptoComponentImpl crypto =
new CryptoComponentImpl(new TestSeedProvider());
new CryptoComponentImpl(new TestSecureRandomProvider());
@Test
public void testEncryptionAndDecryption() {

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.NONE;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_STRONG;
import static org.junit.Assert.assertTrue;
@@ -12,7 +13,7 @@ public class PasswordStrengthEstimatorImplTest extends BrambleTestCase {
@Test
public void testWeakPasswords() {
PasswordStrengthEstimator e = new PasswordStrengthEstimatorImpl();
assertTrue(e.estimateStrength("") < QUITE_STRONG);
assertTrue(e.estimateStrength("") == NONE);
assertTrue(e.estimateStrength("password") < QUITE_STRONG);
assertTrue(e.estimateStrength("letmein") < QUITE_STRONG);
assertTrue(e.estimateStrength("123456") < QUITE_STRONG);

View File

@@ -0,0 +1,48 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.PseudoRandom;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
class PseudoSecureRandom extends SecureRandom {
private static final Provider PROVIDER = new PseudoSecureRandomProvider();
PseudoSecureRandom(byte[] seed) {
super(new PseudoSecureRandomSpi(seed), PROVIDER);
}
private static class PseudoSecureRandomSpi extends SecureRandomSpi {
private final PseudoRandom pseudoRandom;
private PseudoSecureRandomSpi(byte[] seed) {
pseudoRandom = new PseudoRandomImpl(seed);
}
@Override
protected byte[] engineGenerateSeed(int length) {
return pseudoRandom.nextBytes(length);
}
@Override
protected void engineNextBytes(byte[] b) {
byte[] random = pseudoRandom.nextBytes(b.length);
System.arraycopy(random, 0, b, 0, b.length);
}
@Override
protected void engineSetSeed(byte[] seed) {
// Thank you for your input
}
}
private static class PseudoSecureRandomProvider extends Provider {
private PseudoSecureRandomProvider() {
super("PseudoSecureRandom", 1.0, "Only for testing");
}
}
}

View File

@@ -3,7 +3,7 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSeedProvider;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
import org.junit.Test;
@@ -22,7 +22,7 @@ public class SignatureTest extends BrambleTestCase {
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
public SignatureTest() {
crypto = new CryptoComponentImpl(new TestSeedProvider());
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
KeyPair k = crypto.generateSignatureKeyPair();
publicKey = k.getPublic().getEncoded();
privateKey = k.getPrivate().getEncoded();

View File

@@ -9,9 +9,13 @@ import java.io.ByteArrayOutputStream;
import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class StreamEncrypterImplTest extends BrambleTestCase {
@@ -30,6 +34,58 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
payload = TestUtils.getRandomBytes(payloadLength);
}
@Test(expected = IllegalArgumentException.class)
public void testRejectsNegativePayloadLength() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
s.writeFrame(payload, -1, 0, false);
}
@Test(expected = IllegalArgumentException.class)
public void testRejectsNegativePaddingLength() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
s.writeFrame(payload, 0, -1, false);
}
@Test(expected = IllegalArgumentException.class)
public void testRejectsMaxPayloadPlusPadding() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
byte[] bigPayload = new byte[MAX_PAYLOAD_LENGTH + 1];
s.writeFrame(bigPayload, MAX_PAYLOAD_LENGTH, 1, false);
}
@Test
public void testAcceptsMaxPayloadIncludingPadding() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
byte[] bigPayload = new byte[MAX_PAYLOAD_LENGTH];
s.writeFrame(bigPayload, MAX_PAYLOAD_LENGTH - 1, 1, false);
assertEquals(TAG_LENGTH + STREAM_HEADER_LENGTH + MAX_FRAME_LENGTH,
out.size());
}
@Test
public void testAcceptsMaxPayloadWithoutPadding() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
byte[] bigPayload = new byte[MAX_PAYLOAD_LENGTH];
s.writeFrame(bigPayload, MAX_PAYLOAD_LENGTH, 0, false);
assertEquals(TAG_LENGTH + STREAM_HEADER_LENGTH + MAX_FRAME_LENGTH,
out.size());
}
@Test
public void testWriteUnpaddedNonFinalFrameWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();

View File

@@ -1,14 +0,0 @@
package org.briarproject.bramble.properties;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import static org.junit.Assert.fail;
public class TransportPropertyManagerImplTest extends BrambleTestCase {
@Test
public void testUnitTestsExist() {
fail(); // FIXME: Write tests
}
}

View File

@@ -19,12 +19,12 @@ import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook;
import org.briarproject.bramble.api.sync.ValidationManager.MessageValidator;
import org.briarproject.bramble.api.sync.ValidationManager.State;
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.ImmediateExecutor;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.ByteUtils;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
@@ -38,8 +38,18 @@ import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
public class ValidationManagerImplTest extends BrambleTestCase {
public class ValidationManagerImplTest extends BrambleMockTestCase {
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final MessageFactory messageFactory =
context.mock(MessageFactory.class);
private final MessageValidator validator =
context.mock(MessageValidator.class);
private final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
private final Executor dbExecutor = new ImmediateExecutor();
private final Executor validationExecutor = new ImmediateExecutor();
private final ClientId clientId =
new ClientId(TestUtils.getRandomString(5));
private final MessageId messageId = new MessageId(TestUtils.getRandomId());
@@ -63,23 +73,58 @@ public class ValidationManagerImplTest extends BrambleTestCase {
private final MessageContext validResultWithDependencies =
new MessageContext(metadata, Collections.singletonList(messageId1));
private ValidationManagerImpl vm;
public ValidationManagerImplTest() {
// Encode the messages
System.arraycopy(groupId.getBytes(), 0, raw, 0, UniqueId.LENGTH);
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
}
@Before
public void setUp() {
vm = new ValidationManagerImpl(db, dbExecutor, validationExecutor,
messageFactory);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
}
@Test
public void testStartAndStop() throws Exception {
final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, true);
final Transaction txn2 = new Transaction(null, true);
context.checking(new Expectations() {{
// validateOutstandingMessages()
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getMessagesToValidate(txn, clientId);
will(returnValue(Collections.emptyList()));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
// deliverOutstandingMessages()
oneOf(db).startTransaction(true);
will(returnValue(txn1));
oneOf(db).getPendingMessages(txn1, clientId);
will(returnValue(Collections.emptyList()));
oneOf(db).commitTransaction(txn1);
oneOf(db).endTransaction(txn1);
// shareOutstandingMessages()
oneOf(db).startTransaction(true);
will(returnValue(txn2));
oneOf(db).getMessagesToShare(txn2, clientId);
will(returnValue(Collections.emptyList()));
oneOf(db).commitTransaction(txn2);
oneOf(db).endTransaction(txn2);
}});
vm.startService();
vm.stopService();
}
@Test
public void testMessagesAreValidatedAtStartup() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Executor dbExecutor = new ImmediateExecutor();
final Executor cryptoExecutor = new ImmediateExecutor();
final MessageFactory messageFactory =
context.mock(MessageFactory.class);
final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, true);
final Transaction txn2 = new Transaction(null, false);
@@ -87,6 +132,7 @@ public class ValidationManagerImplTest extends BrambleTestCase {
final Transaction txn4 = new Transaction(null, false);
final Transaction txn5 = new Transaction(null, true);
final Transaction txn6 = new Transaction(null, true);
context.checking(new Expectations() {{
// Get messages to validate
oneOf(db).startTransaction(true);
@@ -165,26 +211,11 @@ public class ValidationManagerImplTest extends BrambleTestCase {
oneOf(db).endTransaction(txn6);
}});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
cryptoExecutor, messageFactory);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
vm.startService();
context.assertIsSatisfied();
}
@Test
public void testPendingMessagesAreDeliveredAtStartup() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Executor dbExecutor = new ImmediateExecutor();
final Executor cryptoExecutor = new ImmediateExecutor();
final MessageFactory messageFactory =
context.mock(MessageFactory.class);
final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, true);
final Transaction txn2 = new Transaction(null, false);
@@ -266,26 +297,11 @@ public class ValidationManagerImplTest extends BrambleTestCase {
oneOf(db).endTransaction(txn4);
}});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
cryptoExecutor, messageFactory);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
vm.startService();
context.assertIsSatisfied();
}
@Test
public void testMessagesAreSharedAtStartup() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Executor dbExecutor = new ImmediateExecutor();
final Executor cryptoExecutor = new ImmediateExecutor();
final MessageFactory messageFactory =
context.mock(MessageFactory.class);
final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, true);
final Transaction txn2 = new Transaction(null, true);
@@ -333,29 +349,15 @@ public class ValidationManagerImplTest extends BrambleTestCase {
oneOf(db).endTransaction(txn4);
}});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
cryptoExecutor, messageFactory);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
vm.startService();
context.assertIsSatisfied();
}
@Test
public void testIncomingMessagesAreShared() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Executor dbExecutor = new ImmediateExecutor();
final Executor cryptoExecutor = new ImmediateExecutor();
final MessageFactory messageFactory =
context.mock(MessageFactory.class);
final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, false);
final Transaction txn2 = new Transaction(null, false);
context.checking(new Expectations() {{
// Load the group
oneOf(db).startTransaction(true);
@@ -396,33 +398,19 @@ public class ValidationManagerImplTest extends BrambleTestCase {
oneOf(db).endTransaction(txn2);
}});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
cryptoExecutor, messageFactory);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
vm.eventOccurred(new MessageAddedEvent(message, contactId));
context.assertIsSatisfied();
}
@Test
public void testValidationContinuesAfterNoSuchMessageException()
throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Executor dbExecutor = new ImmediateExecutor();
final Executor cryptoExecutor = new ImmediateExecutor();
final MessageFactory messageFactory =
context.mock(MessageFactory.class);
final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, true);
final Transaction txn2 = new Transaction(null, true);
final Transaction txn3 = new Transaction(null, false);
final Transaction txn4 = new Transaction(null, true);
final Transaction txn5 = new Transaction(null, true);
context.checking(new Expectations() {{
// Get messages to validate
oneOf(db).startTransaction(true);
@@ -481,33 +469,19 @@ public class ValidationManagerImplTest extends BrambleTestCase {
oneOf(db).endTransaction(txn5);
}});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
cryptoExecutor, messageFactory);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
vm.startService();
context.assertIsSatisfied();
}
@Test
public void testValidationContinuesAfterNoSuchGroupException()
throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Executor dbExecutor = new ImmediateExecutor();
final Executor cryptoExecutor = new ImmediateExecutor();
final MessageFactory messageFactory =
context.mock(MessageFactory.class);
final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, true);
final Transaction txn2 = new Transaction(null, true);
final Transaction txn3 = new Transaction(null, false);
final Transaction txn4 = new Transaction(null, true);
final Transaction txn5 = new Transaction(null, true);
context.checking(new Expectations() {{
// Get messages to validate
oneOf(db).startTransaction(true);
@@ -571,28 +545,14 @@ public class ValidationManagerImplTest extends BrambleTestCase {
oneOf(db).endTransaction(txn5);
}});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
cryptoExecutor, messageFactory);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
vm.startService();
context.assertIsSatisfied();
}
@Test
public void testNonLocalMessagesAreValidatedWhenAdded() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Executor dbExecutor = new ImmediateExecutor();
final Executor cryptoExecutor = new ImmediateExecutor();
final MessageFactory messageFactory =
context.mock(MessageFactory.class);
final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, false);
context.checking(new Expectations() {{
// Load the group
oneOf(db).startTransaction(true);
@@ -619,51 +579,20 @@ public class ValidationManagerImplTest extends BrambleTestCase {
oneOf(db).endTransaction(txn1);
}});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
cryptoExecutor, messageFactory);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
vm.eventOccurred(new MessageAddedEvent(message, contactId));
context.assertIsSatisfied();
}
@Test
public void testLocalMessagesAreNotValidatedWhenAdded() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Executor dbExecutor = new ImmediateExecutor();
final Executor cryptoExecutor = new ImmediateExecutor();
final MessageFactory messageFactory =
context.mock(MessageFactory.class);
final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
cryptoExecutor, messageFactory);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
vm.eventOccurred(new MessageAddedEvent(message, null));
context.assertIsSatisfied();
}
@Test
public void testMessagesWithUndeliveredDependenciesArePending()
throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Executor dbExecutor = new ImmediateExecutor();
final Executor cryptoExecutor = new ImmediateExecutor();
final MessageFactory messageFactory =
context.mock(MessageFactory.class);
final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, false);
context.checking(new Expectations() {{
// Load the group
oneOf(db).startTransaction(true);
@@ -688,29 +617,15 @@ public class ValidationManagerImplTest extends BrambleTestCase {
oneOf(db).endTransaction(txn1);
}});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
cryptoExecutor, messageFactory);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
vm.eventOccurred(new MessageAddedEvent(message, contactId));
context.assertIsSatisfied();
}
@Test
public void testMessagesWithDeliveredDependenciesGetDelivered()
throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Executor dbExecutor = new ImmediateExecutor();
final Executor cryptoExecutor = new ImmediateExecutor();
final MessageFactory messageFactory =
context.mock(MessageFactory.class);
final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, false);
context.checking(new Expectations() {{
// Load the group
oneOf(db).startTransaction(true);
@@ -741,30 +656,16 @@ public class ValidationManagerImplTest extends BrambleTestCase {
oneOf(db).endTransaction(txn1);
}});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
cryptoExecutor, messageFactory);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
vm.eventOccurred(new MessageAddedEvent(message, contactId));
context.assertIsSatisfied();
}
@Test
public void testMessagesWithInvalidDependenciesAreInvalid()
throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Executor dbExecutor = new ImmediateExecutor();
final Executor cryptoExecutor = new ImmediateExecutor();
final MessageFactory messageFactory =
context.mock(MessageFactory.class);
final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, false);
final Transaction txn2 = new Transaction(null, false);
context.checking(new Expectations() {{
// Load the group
oneOf(db).startTransaction(true);
@@ -809,26 +710,11 @@ public class ValidationManagerImplTest extends BrambleTestCase {
oneOf(db).endTransaction(txn2);
}});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
cryptoExecutor, messageFactory);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
vm.eventOccurred(new MessageAddedEvent(message, contactId));
context.assertIsSatisfied();
}
@Test
public void testRecursiveInvalidation() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Executor dbExecutor = new ImmediateExecutor();
final Executor cryptoExecutor = new ImmediateExecutor();
final MessageFactory messageFactory =
context.mock(MessageFactory.class);
final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
final MessageId messageId3 = new MessageId(TestUtils.getRandomId());
final MessageId messageId4 = new MessageId(TestUtils.getRandomId());
final Map<MessageId, State> twoDependents =
@@ -842,6 +728,7 @@ public class ValidationManagerImplTest extends BrambleTestCase {
final Transaction txn4 = new Transaction(null, false);
final Transaction txn5 = new Transaction(null, false);
final Transaction txn6 = new Transaction(null, false);
context.checking(new Expectations() {{
// Load the group
oneOf(db).startTransaction(true);
@@ -927,26 +814,11 @@ public class ValidationManagerImplTest extends BrambleTestCase {
oneOf(db).endTransaction(txn6);
}});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
cryptoExecutor, messageFactory);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
vm.eventOccurred(new MessageAddedEvent(message, contactId));
context.assertIsSatisfied();
}
@Test
public void testPendingDependentsGetDelivered() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Executor dbExecutor = new ImmediateExecutor();
final Executor cryptoExecutor = new ImmediateExecutor();
final MessageFactory messageFactory =
context.mock(MessageFactory.class);
final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
final MessageId messageId3 = new MessageId(TestUtils.getRandomId());
final MessageId messageId4 = new MessageId(TestUtils.getRandomId());
final Message message3 = new Message(messageId3, groupId, timestamp,
@@ -968,6 +840,7 @@ public class ValidationManagerImplTest extends BrambleTestCase {
final Transaction txn4 = new Transaction(null, false);
final Transaction txn5 = new Transaction(null, false);
final Transaction txn6 = new Transaction(null, false);
context.checking(new Expectations() {{
// Load the group
oneOf(db).startTransaction(true);
@@ -1100,26 +973,11 @@ public class ValidationManagerImplTest extends BrambleTestCase {
oneOf(db).endTransaction(txn6);
}});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
cryptoExecutor, messageFactory);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
vm.eventOccurred(new MessageAddedEvent(message, contactId));
context.assertIsSatisfied();
}
@Test
public void testOnlyReadyPendingDependentsGetDelivered() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Executor dbExecutor = new ImmediateExecutor();
final Executor cryptoExecutor = new ImmediateExecutor();
final MessageFactory messageFactory =
context.mock(MessageFactory.class);
final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
final Map<MessageId, State> twoDependencies =
new LinkedHashMap<MessageId, State>();
twoDependencies.put(messageId, DELIVERED);
@@ -1127,6 +985,7 @@ public class ValidationManagerImplTest extends BrambleTestCase {
final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, false);
final Transaction txn2 = new Transaction(null, false);
context.checking(new Expectations() {{
// Load the group
oneOf(db).startTransaction(true);
@@ -1162,86 +1021,6 @@ public class ValidationManagerImplTest extends BrambleTestCase {
oneOf(db).endTransaction(txn2);
}});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
cryptoExecutor, messageFactory);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
vm.eventOccurred(new MessageAddedEvent(message, contactId));
context.assertIsSatisfied();
}
@Test
public void testMessageDependencyCycle() throws Exception {
final MessageContext cycleContext = new MessageContext(metadata,
Collections.singletonList(messageId));
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Executor dbExecutor = new ImmediateExecutor();
final Executor cryptoExecutor = new ImmediateExecutor();
final MessageFactory messageFactory =
context.mock(MessageFactory.class);
final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, false);
final Transaction txn2 = new Transaction(null, true);
final Transaction txn3 = new Transaction(null, false);
context.checking(new Expectations() {{
// Load the group
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getGroup(txn, groupId);
will(returnValue(group));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
// Validate the message: valid
oneOf(validator).validateMessage(message, group);
will(returnValue(validResultWithDependencies));
// Store the validation result
oneOf(db).startTransaction(false);
will(returnValue(txn1));
oneOf(db).addMessageDependencies(txn1, message,
validResultWithDependencies.getDependencies());
oneOf(db).getMessageDependencies(txn1, messageId);
will(returnValue(Collections.singletonMap(messageId1, UNKNOWN)));
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
oneOf(db).setMessageState(txn1, messageId, PENDING);
oneOf(db).commitTransaction(txn1);
oneOf(db).endTransaction(txn1);
// Second message is coming in
oneOf(db).startTransaction(true);
will(returnValue(txn2));
oneOf(db).getGroup(txn2, groupId);
will(returnValue(group));
oneOf(db).commitTransaction(txn2);
oneOf(db).endTransaction(txn2);
// Validate the message: valid
oneOf(validator).validateMessage(message1, group);
will(returnValue(cycleContext));
// Store the validation result
oneOf(db).startTransaction(false);
will(returnValue(txn3));
oneOf(db).addMessageDependencies(txn3, message1,
cycleContext.getDependencies());
oneOf(db).getMessageDependencies(txn3, messageId1);
will(returnValue(Collections.singletonMap(messageId, PENDING)));
oneOf(db).mergeMessageMetadata(txn3, messageId1, metadata);
oneOf(db).setMessageState(txn3, messageId1, PENDING);
oneOf(db).commitTransaction(txn3);
oneOf(db).endTransaction(txn3);
}});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
cryptoExecutor, messageFactory);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
vm.eventOccurred(new MessageAddedEvent(message, contactId));
vm.eventOccurred(new MessageAddedEvent(message1, contactId));
context.assertIsSatisfied();
}
}

View File

@@ -0,0 +1,57 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.OsUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.security.Provider;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class LinuxSecureRandomProviderTest extends BrambleTestCase {
private final File testDir = TestUtils.getTestDirectory();
@Before
public void setUp() {
testDir.mkdirs();
}
@Test
public void testGetProviderWritesToRandomDeviceOnFirstCall()
throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Redirect the provider's output to a file
File urandom = new File(testDir, "urandom");
urandom.delete();
assertTrue(urandom.createNewFile());
assertEquals(0, urandom.length());
LinuxSecureRandomProvider p = new LinuxSecureRandomProvider(urandom);
// Getting a provider should write entropy to the file
Provider provider = p.getProvider();
assertNotNull(provider);
assertEquals("LinuxPRNG", provider.getName());
// There should be at least 16 bytes from the clock, 8 from the runtime
long length = urandom.length();
assertTrue(length >= 24);
// Getting another provider should not write to the file again
provider = p.getProvider();
assertNotNull(provider);
assertEquals("LinuxPRNG", provider.getName());
assertEquals(length, urandom.length());
}
@After
public void tearDown() {
TestUtils.deleteTestDirectory(testDir);
}
}

View File

@@ -0,0 +1,128 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.IoUtils;
import org.briarproject.bramble.util.OsUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class LinuxSecureRandomSpiTest extends BrambleTestCase {
private static final File RANDOM_DEVICE = new File("/dev/urandom");
private static final int SEED_BYTES = 32;
private final File testDir = TestUtils.getTestDirectory();
@Before
public void setUp() {
testDir.mkdirs();
}
@Test
public void testSeedsAreDistinct() {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
Set<Bytes> seeds = new HashSet<Bytes>();
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi();
for (int i = 0; i < 1000; i++) {
byte[] seed = engine.engineGenerateSeed(SEED_BYTES);
assertEquals(SEED_BYTES, seed.length);
assertTrue(seeds.add(new Bytes(seed)));
}
}
@Test
public void testEngineSetSeedWritesToRandomDevice() throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Redirect the engine's output to a file
File urandom = new File(testDir, "urandom");
urandom.delete();
assertTrue(urandom.createNewFile());
assertEquals(0, urandom.length());
// Generate a seed
byte[] seed = TestUtils.getRandomBytes(SEED_BYTES);
// Check that the engine writes the seed to the file
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(RANDOM_DEVICE,
urandom);
engine.engineSetSeed(seed);
assertEquals(SEED_BYTES, urandom.length());
byte[] written = new byte[SEED_BYTES];
FileInputStream in = new FileInputStream(urandom);
IoUtils.read(in, written);
in.close();
assertArrayEquals(seed, written);
}
@Test
public void testEngineNextBytesReadsFromRandomDevice() throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Generate some entropy
byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
// Write the entropy to a file
File urandom = new File(testDir, "urandom");
urandom.delete();
FileOutputStream out = new FileOutputStream(urandom);
out.write(entropy);
out.flush();
out.close();
assertTrue(urandom.exists());
assertEquals(SEED_BYTES, urandom.length());
// Check that the engine reads from the file
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(urandom,
RANDOM_DEVICE);
byte[] b = new byte[SEED_BYTES];
engine.engineNextBytes(b);
assertArrayEquals(entropy, b);
}
@Test
public void testEngineGenerateSeedReadsFromRandomDevice() throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Generate some entropy
byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
// Write the entropy to a file
File urandom = new File(testDir, "urandom");
urandom.delete();
FileOutputStream out = new FileOutputStream(urandom);
out.write(entropy);
out.flush();
out.close();
assertTrue(urandom.exists());
assertEquals(SEED_BYTES, urandom.length());
// Check that the engine reads from the file
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(urandom,
RANDOM_DEVICE);
byte[] b = engine.engineGenerateSeed(SEED_BYTES);
assertArrayEquals(entropy, b);
}
@After
public void tearDown() {
TestUtils.deleteTestDirectory(testDir);
}
}

View File

@@ -1,90 +0,0 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.OsUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.util.HashSet;
import java.util.Set;
import static org.briarproject.bramble.api.system.SeedProvider.SEED_BYTES;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class LinuxSeedProviderTest extends BrambleTestCase {
private final File testDir = TestUtils.getTestDirectory();
@Before
public void setUp() {
testDir.mkdirs();
}
@Test
public void testSeedAppearsSane() {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
Set<Bytes> seeds = new HashSet<Bytes>();
LinuxSeedProvider p = new LinuxSeedProvider();
for (int i = 0; i < 1000; i++) {
byte[] seed = p.getSeed();
assertEquals(SEED_BYTES, seed.length);
assertTrue(seeds.add(new Bytes(seed)));
}
}
@Test
public void testEntropyIsWrittenToPool() throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Redirect the provider's entropy to a file
File urandom = new File(testDir, "urandom");
urandom.delete();
assertTrue(urandom.createNewFile());
assertEquals(0, urandom.length());
String path = urandom.getAbsolutePath();
LinuxSeedProvider p = new LinuxSeedProvider(path, "/dev/urandom");
p.getSeed();
// There should be 16 bytes from the clock, plus network interfaces
assertTrue(urandom.length() > 20);
}
@Test
public void testSeedIsReadFromPool() throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Generate a seed
byte[] seed = TestUtils.getRandomBytes(SEED_BYTES);
// Write the seed to a file
File urandom = new File(testDir, "urandom");
urandom.delete();
FileOutputStream out = new FileOutputStream(urandom);
out.write(seed);
out.flush();
out.close();
assertTrue(urandom.exists());
assertEquals(SEED_BYTES, urandom.length());
// Check that the provider reads the seed from the file
String path = urandom.getAbsolutePath();
LinuxSeedProvider p = new LinuxSeedProvider("/dev/urandom", path);
assertArrayEquals(seed, p.getSeed());
}
@After
public void tearDown() {
TestUtils.deleteTestDirectory(testDir);
}
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import java.security.Provider;
@NotNullByDefault
public class TestSecureRandomProvider implements SecureRandomProvider {
@Override
public Provider getProvider() {
// Use the default provider
return null;
}
}

View File

@@ -1,13 +0,0 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.SeedProvider;
@NotNullByDefault
public class TestSeedProvider implements SeedProvider {
@Override
public byte[] getSeed() {
return TestUtils.getRandomBytes(32);
}
}

View File

@@ -1,6 +1,6 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.system.SeedProvider;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import javax.inject.Singleton;
@@ -12,7 +12,7 @@ public class TestSeedProviderModule {
@Provides
@Singleton
SeedProvider provideSeedProvider() {
return new TestSeedProvider();
SecureRandomProvider provideSeedProvider() {
return new TestSecureRandomProvider();
}
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.bramble.test;
import javax.net.SocketFactory;
import dagger.Module;
import dagger.Provides;
@Module
public class TestSocksModule {
@Provides
SocketFactory provideSocketFactory() {
return SocketFactory.getDefault();
}
}

View File

@@ -0,0 +1,19 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.util.OsUtils;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class DesktopSecureRandomModule {
@Provides
@Singleton
SecureRandomProvider provideSecureRandomProvider() {
return OsUtils.isLinux() ? new LinuxSecureRandomProvider() : null;
}
}

View File

@@ -1,19 +0,0 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.system.SeedProvider;
import org.briarproject.bramble.util.OsUtils;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class DesktopSeedProviderModule {
@Provides
@Singleton
SeedProvider provideSeedProvider() {
return OsUtils.isLinux() ? new LinuxSeedProvider() : null;
}
}

View File

@@ -82,6 +82,8 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 22
versionCode 14
versionName "0.14"
resValue "string", "app_package", "org.briarproject.briar"
buildConfigField "String", "GitHash", "\"${getGitHash()}\""
}

View File

@@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="org.briarproject.briar"
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="13"
android:versionName="0.13">
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.bluetooth"/>
<uses-feature android:name="android.hardware.camera" />
@@ -23,7 +21,7 @@
<application
android:name=".android.BriarApplicationImpl"
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:icon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:logo="@mipmap/ic_launcher_round"
android:theme="@style/BriarTheme">
@@ -333,7 +331,8 @@
<activity
android:name=".android.settings.SettingsActivity"
android:label="@string/settings_button"
android:parentActivityName=".android.navdrawer.NavDrawerActivity">
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
android:permission="android.permission.READ_NETWORK_USAGE_HISTORY">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.navdrawer.NavDrawerActivity"
@@ -356,13 +355,12 @@
<activity
android:name=".android.panic.PanicPreferencesActivity"
android:label="@string/panic_setting">
<intent-filter>
<action android:name="info.guardianproject.panic.action.CONNECT"/>
<action android:name="info.guardianproject.panic.action.DISCONNECT"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
android:label="@string/panic_setting"
android:parentActivityName=".android.settings.SettingsActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.settings.SettingsActivity"
/>
</activity>
<activity

View File

@@ -28,9 +28,11 @@ import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.reporting.BriarReportSender;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.ReferenceManager;
import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.feed.FeedManager;
import org.briarproject.briar.api.forum.ForumManager;
import org.briarproject.briar.api.forum.ForumSharingManager;
@@ -43,6 +45,8 @@ import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel;
import java.util.concurrent.Executor;
@@ -75,6 +79,8 @@ public interface AndroidComponent
@DatabaseExecutor
Executor databaseExecutor();
MessageTracker messageTracker();
LifecycleManager lifecycleManager();
IdentityManager identityManager();
@@ -87,6 +93,8 @@ public interface AndroidComponent
AndroidNotificationManager androidNotificationManager();
ScreenFilterMonitor screenFilterMonitor();
ConnectionRegistry connectionRegistry();
ContactManager contactManager();
@@ -138,10 +146,14 @@ public interface AndroidComponent
@IoExecutor
Executor ioExecutor();
void inject(BriarService activity);
void inject(BriarService briarService);
void inject(BriarReportSender briarReportSender);
void inject(EmojiProvider emojiProvider);
void inject(RecentEmojiPageModel recentEmojiPageModel);
// Eager singleton load
void inject(AppModule.EagerSingletons init);
}

View File

@@ -2,11 +2,8 @@ package org.briarproject.briar.android;
import android.app.Application;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.UiThread;
@@ -62,7 +59,6 @@ import static android.app.Notification.DEFAULT_SOUND;
import static android.app.Notification.DEFAULT_VIBRATE;
import static android.content.Context.NOTIFICATION_SERVICE;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static android.support.v4.app.NotificationCompat.CATEGORY_MESSAGE;
import static android.support.v4.app.NotificationCompat.CATEGORY_SOCIAL;
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
@@ -73,8 +69,6 @@ import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.INTENT_
import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.INTENT_CONTACTS;
import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.INTENT_FORUMS;
import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.INTENT_GROUPS;
import static org.briarproject.briar.android.settings.SettingsFragment.PREF_NOTIFY_BLOG;
import static org.briarproject.briar.android.settings.SettingsFragment.PREF_NOTIFY_GROUP;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
@ThreadSafe
@@ -92,25 +86,13 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
// Content URIs to differentiate between pending intents
private static final String CONTACT_URI =
"content://org.briarproject/contact";
"content://org.briarproject.briar/contact";
private static final String GROUP_URI =
"content://org.briarproject/group";
"content://org.briarproject.briar/group";
private static final String FORUM_URI =
"content://org.briarproject/forum";
"content://org.briarproject.briar/forum";
private static final String BLOG_URI =
"content://org.briarproject/blog";
// Actions for intents that are broadcast when notifications are dismissed
private static final String CLEAR_PRIVATE_MESSAGE_ACTION =
"org.briarproject.briar.CLEAR_PRIVATE_MESSAGE_NOTIFICATION";
private static final String CLEAR_GROUP_ACTION =
"org.briarproject.briar.CLEAR_GROUP_NOTIFICATION";
private static final String CLEAR_FORUM_ACTION =
"org.briarproject.briar.CLEAR_FORUM_NOTIFICATION";
private static final String CLEAR_BLOG_ACTION =
"org.briarproject.briar.CLEAR_BLOG_NOTIFICATION";
private static final String CLEAR_INTRODUCTION_ACTION =
"org.briarproject.briar.CLEAR_INTRODUCTION_NOTIFICATION";
"content://org.briarproject.briar/blog";
private static final Logger LOG =
Logger.getLogger(AndroidNotificationManagerImpl.class.getName());
@@ -119,7 +101,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
private final SettingsManager settingsManager;
private final AndroidExecutor androidExecutor;
private final Context appContext;
private final BroadcastReceiver receiver = new DeleteIntentReceiver();
private final AtomicBoolean used = new AtomicBoolean(false);
// The following must only be accessed on the main UI thread
@@ -157,30 +138,11 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
} catch (DbException e) {
throw new ServiceException(e);
}
// Register a broadcast receiver for notifications being dismissed
Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
@Override
public Void call() {
IntentFilter filter = new IntentFilter();
filter.addAction(CLEAR_PRIVATE_MESSAGE_ACTION);
filter.addAction(CLEAR_GROUP_ACTION);
filter.addAction(CLEAR_FORUM_ACTION);
filter.addAction(CLEAR_BLOG_ACTION);
filter.addAction(CLEAR_INTRODUCTION_ACTION);
appContext.registerReceiver(receiver, filter);
return null;
}
});
try {
f.get();
} catch (InterruptedException | ExecutionException e) {
throw new ServiceException(e);
}
}
@Override
public void stopService() throws ServiceException {
// Clear all notifications and unregister the broadcast receiver
// Clear all notifications
Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
@Override
public Void call() {
@@ -189,7 +151,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
clearForumPostNotification();
clearBlogPostNotification();
clearIntroductionSuccessNotification();
appContext.unregisterReceiver(receiver);
return null;
}
});
@@ -325,34 +286,30 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
private void updateContactNotification() {
if (contactTotal == 0) {
clearContactNotification();
} else if (settings.getBoolean("notifyPrivateMessages", true)) {
} else if (settings.getBoolean(PREF_NOTIFY_PRIVATE, true)) {
NotificationCompat.Builder b =
new NotificationCompat.Builder(appContext);
b.setSmallIcon(R.drawable.notification_private_message);
b.setColor(ContextCompat.getColor(appContext, R.color.briar_primary));
b.setColor(ContextCompat.getColor(appContext,
R.color.briar_primary));
b.setContentTitle(appContext.getText(R.string.app_name));
b.setContentText(appContext.getResources().getQuantityString(
R.plurals.private_message_notification_text, contactTotal,
contactTotal));
boolean sound = settings.getBoolean("notifySound", true);
String ringtoneUri = settings.get("notifyRingtoneUri");
boolean sound = settings.getBoolean(PREF_NOTIFY_SOUND, true);
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
if (sound && !StringUtils.isNullOrEmpty(ringtoneUri))
b.setSound(Uri.parse(ringtoneUri));
b.setDefaults(getDefaults());
b.setOnlyAlertOnce(true);
b.setAutoCancel(true);
// Clear the counters if the notification is dismissed
Intent clear = new Intent(CLEAR_PRIVATE_MESSAGE_ACTION);
PendingIntent delete = PendingIntent.getBroadcast(appContext, 0,
clear, 0);
b.setDeleteIntent(delete);
if (contactCounts.size() == 1) {
// Touching the notification shows the relevant conversation
Intent i = new Intent(appContext, ConversationActivity.class);
ContactId c = contactCounts.keySet().iterator().next();
i.putExtra(CONTACT_ID, c.getInt());
i.setData(Uri.parse(CONTACT_URI + "/" + c.getInt()));
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
TaskStackBuilder t = TaskStackBuilder.create(appContext);
t.addParentStack(ConversationActivity.class);
t.addNextIntent(i);
@@ -361,7 +318,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
// Touching the notification shows the contact list
Intent i = new Intent(appContext, NavDrawerActivity.class);
i.putExtra(INTENT_CONTACTS, true);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
i.setData(Uri.parse(CONTACT_URI));
TaskStackBuilder t = TaskStackBuilder.create(appContext);
t.addParentStack(NavDrawerActivity.class);
@@ -381,11 +338,11 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@UiThread
private int getDefaults() {
int defaults = DEFAULT_LIGHTS;
boolean sound = settings.getBoolean("notifySound", true);
String ringtoneUri = settings.get("notifyRingtoneUri");
boolean sound = settings.getBoolean(PREF_NOTIFY_SOUND, true);
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
if (sound && StringUtils.isNullOrEmpty(ringtoneUri))
defaults |= DEFAULT_SOUND;
if (settings.getBoolean("notifyVibration", true))
if (settings.getBoolean(PREF_NOTIFY_VIBRATION, true))
defaults |= DEFAULT_VIBRATE;
return defaults;
}
@@ -438,22 +395,18 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
NotificationCompat.Builder b =
new NotificationCompat.Builder(appContext);
b.setSmallIcon(R.drawable.notification_private_group);
b.setColor(ContextCompat.getColor(appContext, R.color.briar_primary));
b.setColor(ContextCompat.getColor(appContext,
R.color.briar_primary));
b.setContentTitle(appContext.getText(R.string.app_name));
b.setContentText(appContext.getResources().getQuantityString(
R.plurals.group_message_notification_text, groupTotal,
groupTotal));
String ringtoneUri = settings.get("notifyRingtoneUri");
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
if (!StringUtils.isNullOrEmpty(ringtoneUri))
b.setSound(Uri.parse(ringtoneUri));
b.setDefaults(getDefaults());
b.setOnlyAlertOnce(true);
b.setAutoCancel(true);
// Clear the counters if the notification is dismissed
Intent clear = new Intent(CLEAR_GROUP_ACTION);
PendingIntent delete = PendingIntent.getBroadcast(appContext, 0,
clear, 0);
b.setDeleteIntent(delete);
if (groupCounts.size() == 1) {
// Touching the notification shows the relevant group
Intent i = new Intent(appContext, GroupActivity.class);
@@ -461,7 +414,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
i.putExtra(GROUP_ID, g.getBytes());
String idHex = StringUtils.toHexString(g.getBytes());
i.setData(Uri.parse(GROUP_URI + "/" + idHex));
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
TaskStackBuilder t = TaskStackBuilder.create(appContext);
t.addParentStack(GroupActivity.class);
t.addNextIntent(i);
@@ -470,7 +423,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
// Touching the notification shows the group list
Intent i = new Intent(appContext, NavDrawerActivity.class);
i.putExtra(INTENT_GROUPS, true);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
i.setData(Uri.parse(GROUP_URI));
TaskStackBuilder t = TaskStackBuilder.create(appContext);
t.addParentStack(NavDrawerActivity.class);
@@ -530,26 +483,22 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
private void updateForumPostNotification() {
if (forumTotal == 0) {
clearForumPostNotification();
} else if (settings.getBoolean("notifyForumPosts", true)) {
} else if (settings.getBoolean(PREF_NOTIFY_FORUM, true)) {
NotificationCompat.Builder b =
new NotificationCompat.Builder(appContext);
b.setSmallIcon(R.drawable.notification_forum);
b.setColor(ContextCompat.getColor(appContext, R.color.briar_primary));
b.setColor(ContextCompat.getColor(appContext,
R.color.briar_primary));
b.setContentTitle(appContext.getText(R.string.app_name));
b.setContentText(appContext.getResources().getQuantityString(
R.plurals.forum_post_notification_text, forumTotal,
forumTotal));
String ringtoneUri = settings.get("notifyRingtoneUri");
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
if (!StringUtils.isNullOrEmpty(ringtoneUri))
b.setSound(Uri.parse(ringtoneUri));
b.setDefaults(getDefaults());
b.setOnlyAlertOnce(true);
b.setAutoCancel(true);
// Clear the counters if the notification is dismissed
Intent clear = new Intent(CLEAR_FORUM_ACTION);
PendingIntent delete = PendingIntent.getBroadcast(appContext, 0,
clear, 0);
b.setDeleteIntent(delete);
if (forumCounts.size() == 1) {
// Touching the notification shows the relevant forum
Intent i = new Intent(appContext, ForumActivity.class);
@@ -557,7 +506,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
i.putExtra(GROUP_ID, g.getBytes());
String idHex = StringUtils.toHexString(g.getBytes());
i.setData(Uri.parse(FORUM_URI + "/" + idHex));
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
TaskStackBuilder t = TaskStackBuilder.create(appContext);
t.addParentStack(ForumActivity.class);
t.addNextIntent(i);
@@ -566,7 +515,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
// Touching the notification shows the forum list
Intent i = new Intent(appContext, NavDrawerActivity.class);
i.putExtra(INTENT_FORUMS, true);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
i.setData(Uri.parse(FORUM_URI));
TaskStackBuilder t = TaskStackBuilder.create(appContext);
t.addParentStack(NavDrawerActivity.class);
@@ -630,26 +579,22 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
NotificationCompat.Builder b =
new NotificationCompat.Builder(appContext);
b.setSmallIcon(R.drawable.notification_blog);
b.setColor(ContextCompat.getColor(appContext, R.color.briar_primary));
b.setColor(ContextCompat.getColor(appContext,
R.color.briar_primary));
b.setContentTitle(appContext.getText(R.string.app_name));
b.setContentText(appContext.getResources().getQuantityString(
R.plurals.blog_post_notification_text, blogTotal,
blogTotal));
String ringtoneUri = settings.get("notifyRingtoneUri");
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
if (!StringUtils.isNullOrEmpty(ringtoneUri))
b.setSound(Uri.parse(ringtoneUri));
b.setDefaults(getDefaults());
b.setOnlyAlertOnce(true);
b.setAutoCancel(true);
// Clear the counters if the notification is dismissed
Intent clear = new Intent(CLEAR_BLOG_ACTION);
PendingIntent delete = PendingIntent.getBroadcast(appContext, 0,
clear, 0);
b.setDeleteIntent(delete);
// Touching the notification shows the combined blog feed
Intent i = new Intent(appContext, NavDrawerActivity.class);
i.putExtra(INTENT_BLOGS, true);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
i.setData(Uri.parse(BLOG_URI));
TaskStackBuilder t = TaskStackBuilder.create(appContext);
t.addParentStack(NavDrawerActivity.class);
@@ -696,21 +641,16 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
b.setContentText(appContext.getResources().getQuantityString(
R.plurals.introduction_notification_text, introductionTotal,
introductionTotal));
String ringtoneUri = settings.get("notifyRingtoneUri");
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
if (!StringUtils.isNullOrEmpty(ringtoneUri))
b.setSound(Uri.parse(ringtoneUri));
b.setDefaults(getDefaults());
b.setOnlyAlertOnce(true);
b.setAutoCancel(true);
// Clear the counter if the notification is dismissed
Intent clear = new Intent(CLEAR_INTRODUCTION_ACTION);
PendingIntent delete = PendingIntent.getBroadcast(appContext, 0,
clear, 0);
b.setDeleteIntent(delete);
// Touching the notification shows the contact list
Intent i = new Intent(appContext, NavDrawerActivity.class);
i.putExtra(INTENT_CONTACTS, true);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
i.setData(Uri.parse(CONTACT_URI));
TaskStackBuilder t = TaskStackBuilder.create(appContext);
t.addParentStack(NavDrawerActivity.class);
@@ -847,27 +787,4 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
});
}
private class DeleteIntentReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
if (CLEAR_PRIVATE_MESSAGE_ACTION.equals(action)) {
clearContactNotification();
} else if (CLEAR_GROUP_ACTION.equals(action)) {
clearGroupMessageNotification();
} else if (CLEAR_FORUM_ACTION.equals(action)) {
clearForumPostNotification();
} else if (CLEAR_BLOG_ACTION.equals(action)) {
clearBlogPostNotification();
} else if (CLEAR_INTRODUCTION_ACTION.equals(action)) {
clearIntroductionSuccessNotification();
}
}
});
}
}
}

View File

@@ -16,6 +16,7 @@ import org.briarproject.bramble.api.ui.UiCallback;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.ReferenceManager;
import org.briarproject.briar.api.android.ScreenFilterMonitor;
import java.io.File;
import java.security.GeneralSecurityException;
@@ -37,6 +38,8 @@ public class AppModule {
static class EagerSingletons {
@Inject
AndroidNotificationManager androidNotificationManager;
@Inject
ScreenFilterMonitor screenFilterMonitor;
}
private final Application application;
@@ -166,4 +169,12 @@ public class AppModule {
eventBus.addListener(notificationManager);
return notificationManager;
}
@Provides
@Singleton
ScreenFilterMonitor provideScreenFilterMonitor(
LifecycleManager lifecycleManager, ScreenFilterMonitorImpl sfm) {
lifecycleManager.registerService(sfm);
return sfm;
}
}

View File

@@ -28,7 +28,6 @@ import javax.inject.Inject;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static android.support.v4.app.NotificationCompat.CATEGORY_SERVICE;
import static android.support.v4.app.NotificationCompat.PRIORITY_MIN;
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
@@ -83,8 +82,7 @@ public class BriarService extends Service {
b.setWhen(0); // Don't show the time
b.setOngoing(true);
Intent i = new Intent(this, NavDrawerActivity.class);
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP |
FLAG_ACTIVITY_SINGLE_TOP);
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
b.setContentIntent(PendingIntent.getActivity(this, 0, i, 0));
if (Build.VERSION.SDK_INT >= 21) {
b.setCategory(CATEGORY_SERVICE);

View File

@@ -0,0 +1,282 @@
package org.briarproject.briar.android;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.support.annotation.UiThread;
import android.support.v7.preference.PreferenceManager;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.api.android.ScreenFilterMonitor;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.GET_SIGNATURES;
import static java.util.logging.Level.WARNING;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ScreenFilterMonitorImpl extends BroadcastReceiver
implements Service, ScreenFilterMonitor {
private static final Logger LOG =
Logger.getLogger(ScreenFilterMonitorImpl.class.getName());
private static final String PREF_SCREEN_FILTER_APPS =
"shownScreenFilterApps";
/*
* Ignore Play Services if it uses this package name and public key - it's
* effectively a system app, but not flagged as such on older systems
*/
private static final String PLAY_SERVICES_PACKAGE =
"com.google.android.gms";
private static final String PLAY_SERVICES_PUBLIC_KEY =
"30820120300D06092A864886F70D01010105000382010D0030820108" +
"0282010100AB562E00D83BA208AE0A966F124E29DA11F2AB56D08F58" +
"E2CCA91303E9B754D372F640A71B1DCB130967624E4656A7776A9219" +
"3DB2E5BFB724A91E77188B0E6A47A43B33D9609B77183145CCDF7B2E" +
"586674C9E1565B1F4C6A5955BFF251A63DABF9C55C27222252E875E4" +
"F8154A645F897168C0B1BFC612EABF785769BB34AA7984DC7E2EA276" +
"4CAE8307D8C17154D7EE5F64A51A44A602C249054157DC02CD5F5C0E" +
"55FBEF8519FBE327F0B1511692C5A06F19D18385F5C4DBC2D6B93F68" +
"CC2979C70E18AB93866B3BD5DB8999552A0E3B4C99DF58FB918BEDC1" +
"82BA35E003C1B4B10DD244A8EE24FFFD333872AB5221985EDAB0FC0D" +
"0B145B6AA192858E79020103";
private final Context appContext;
private final AndroidExecutor androidExecutor;
private final PackageManager pm;
private final SharedPreferences prefs;
private final AtomicBoolean used = new AtomicBoolean(false);
// The following must only be accessed on the UI thread
private final Set<String> apps = new HashSet<>();
private final Set<String> shownApps;
private boolean serviceStarted = false;
@Inject
ScreenFilterMonitorImpl(AndroidExecutor executor, Application app) {
this.androidExecutor = executor;
this.appContext = app;
pm = appContext.getPackageManager();
prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
shownApps = getShownScreenFilterApps();
}
@Override
public void startService() throws ServiceException {
if (used.getAndSet(true)) throw new IllegalStateException();
Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
@Override
public Void call() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_PACKAGE_ADDED);
intentFilter.addDataScheme("package");
appContext.registerReceiver(ScreenFilterMonitorImpl.this,
intentFilter);
apps.addAll(getInstalledScreenFilterApps());
serviceStarted = true;
return null;
}
});
try {
f.get();
} catch (InterruptedException | ExecutionException e) {
throw new ServiceException(e);
}
}
@Override
public void stopService() throws ServiceException {
Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
@Override
public Void call() {
serviceStarted = false;
appContext.unregisterReceiver(ScreenFilterMonitorImpl.this);
return null;
}
});
try {
f.get();
} catch (InterruptedException | ExecutionException e) {
throw new ServiceException(e);
}
}
private Set<String> getShownScreenFilterApps() {
// Result must not be modified
Set<String> s = prefs.getStringSet(PREF_SCREEN_FILTER_APPS, null);
HashSet<String> result = new HashSet<>();
if (s != null) {
result.addAll(s);
}
return result;
}
@Override
public void onReceive(Context context, Intent intent) {
if (!intent.getBooleanExtra(EXTRA_REPLACING, false)) {
final String packageName =
intent.getData().getEncodedSchemeSpecificPart();
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
String pkg = isOverlayApp(packageName);
if (pkg == null) {
return;
}
apps.add(pkg);
}
});
}
}
@Override
@UiThread
public Set<String> getApps() {
if (!serviceStarted) {
apps.addAll(getInstalledScreenFilterApps());
}
TreeSet<String> buf = new TreeSet<>();
if (apps.isEmpty()) {
return buf;
}
buf.addAll(apps);
buf.removeAll(shownApps);
return buf;
}
@Override
@UiThread
public void storeAppsAsShown(Collection<String> s, boolean persistent) {
HashSet<String> buf = new HashSet<>(s);
shownApps.addAll(buf);
if (persistent && !s.isEmpty()) {
buf.addAll(getShownScreenFilterApps());
prefs.edit()
.putStringSet(PREF_SCREEN_FILTER_APPS, buf)
.apply();
}
}
private Set<String> getInstalledScreenFilterApps() {
HashSet<String> screenFilterApps = new HashSet<>();
List<PackageInfo> packageInfos =
pm.getInstalledPackages(GET_PERMISSIONS);
for (PackageInfo packageInfo : packageInfos) {
if (isOverlayApp(packageInfo)) {
String name = pkgToString(packageInfo);
if (name != null) {
screenFilterApps.add(name);
}
}
}
return screenFilterApps;
}
// Checks if a package uses the SYSTEM_ALERT_WINDOW permission and if so
// returns the app name.
@Nullable
private String isOverlayApp(String pkg) {
try {
PackageInfo pkgInfo = pm.getPackageInfo(pkg, GET_PERMISSIONS);
if (isOverlayApp(pkgInfo)) {
return pkgToString(pkgInfo);
}
} catch (NameNotFoundException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
return null;
}
// Fetches the application name for a given package.
@Nullable
private String pkgToString(PackageInfo pkgInfo) {
CharSequence seq = pm.getApplicationLabel(pkgInfo.applicationInfo);
if (seq != null) {
return seq.toString();
}
return null;
}
// Checks if an installed package is a user app using the permission.
private boolean isOverlayApp(PackageInfo packageInfo) {
int mask = FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP;
// Ignore system apps
if ((packageInfo.applicationInfo.flags & mask) != 0) {
return false;
}
// Ignore Play Services, it's effectively a system app
if (isPlayServices(packageInfo.packageName)) {
return false;
}
// Get permissions
String[] requestedPermissions = packageInfo.requestedPermissions;
if (requestedPermissions != null) {
for (String requestedPermission : requestedPermissions) {
if (requestedPermission.equals(SYSTEM_ALERT_WINDOW)) {
return true;
}
}
}
return false;
}
private boolean isPlayServices(String pkg) {
if (!PLAY_SERVICES_PACKAGE.equals(pkg)) return false;
try {
PackageInfo sigs = pm.getPackageInfo(pkg, GET_SIGNATURES);
// The genuine Play Services app should have a single signature
Signature[] signatures = sigs.signatures;
if (signatures == null || signatures.length != 1) return false;
// Extract the public key from the signature
CertificateFactory certFactory =
CertificateFactory.getInstance("X509");
byte[] signatureBytes = signatures[0].toByteArray();
InputStream in = new ByteArrayInputStream(signatureBytes);
X509Certificate cert =
(X509Certificate) certFactory.generateCertificate(in);
byte[] publicKeyBytes = cert.getPublicKey().getEncoded();
String publicKey = StringUtils.toHexString(publicKeyBytes);
return PLAY_SERVICES_PUBLIC_KEY.equals(publicKey);
} catch (NameNotFoundException | CertificateException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return false;
}
}
}

View File

@@ -1,5 +1,7 @@
package org.briarproject.briar.android;
import org.briarproject.briar.BuildConfig;
import java.util.logging.Level;
import static java.util.logging.Level.INFO;
@@ -11,7 +13,7 @@ public interface TestingConstants {
* Whether this is an alpha or beta build. This should be set to false for
* release builds.
*/
boolean TESTING = true;
boolean TESTING = BuildConfig.DEBUG;
/** Default log level. */
Level DEFAULT_LOG_LEVEL = TESTING ? INFO : OFF;

View File

@@ -64,8 +64,6 @@ import org.briarproject.briar.android.sharing.ShareForumFragment;
import org.briarproject.briar.android.sharing.ShareForumMessageFragment;
import org.briarproject.briar.android.sharing.SharingModule;
import org.briarproject.briar.android.splash.SplashScreenActivity;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel;
import dagger.Component;
@@ -151,10 +149,6 @@ public interface ActivityComponent {
void inject(RssFeedManageActivity activity);
void inject(EmojiProvider emojiProvider);
void inject(RecentEmojiPageModel recentEmojiPageModel);
// Fragments
void inject(ContactListFragment fragment);

View File

@@ -13,11 +13,15 @@ import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.DestroyableContext;
import org.briarproject.briar.android.controller.ActivityLifecycleController;
import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.fragment.SFDialogFragment;
import org.briarproject.briar.api.android.ScreenFilterMonitor;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
@@ -25,13 +29,16 @@ import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOT
public abstract class BaseActivity extends AppCompatActivity
implements DestroyableContext {
protected ActivityComponent activityComponent;
private final List<ActivityLifecycleController> lifecycleControllers =
new ArrayList<>();
private boolean destroyed = false;
@Inject
protected ScreenFilterMonitor screenFilterMonitor;
private SFDialogFragment dialogFrag;
public abstract void injectActivity(ActivityComponent component);
public void addLifecycleController(ActivityLifecycleController alc) {
@@ -58,6 +65,7 @@ public abstract class BaseActivity extends AppCompatActivity
for (ActivityLifecycleController alc : lifecycleControllers) {
alc.onActivityCreate(this);
}
}
public ActivityComponent getActivityComponent() {
@@ -89,6 +97,35 @@ public abstract class BaseActivity extends AppCompatActivity
}
}
@Override
protected void onPostResume() {
super.onPostResume();
showNewScreenFilterWarning();
}
@Override
protected void onPause() {
super.onPause();
if (dialogFrag != null) {
dialogFrag.dismiss();
dialogFrag = null;
}
}
protected void showNewScreenFilterWarning() {
final Set<String> apps = screenFilterMonitor.getApps();
if (apps.isEmpty()) {
return;
}
dialogFrag = SFDialogFragment.newInstance(new ArrayList<>(apps));
dialogFrag.setCancelable(false);
dialogFrag.show(getSupportFragmentManager(), "SFDialog");
}
public void rememberShownApps(ArrayList<String> s, boolean permanent) {
screenFilterMonitor.storeAppsAsShown(s, permanent);
}
@Override
protected void onDestroy() {
super.onDestroy();

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android.activity;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.transition.Slide;
@@ -14,20 +13,20 @@ import android.view.Window;
import org.briarproject.briar.R;
import org.briarproject.briar.android.controller.BriarController;
import org.briarproject.briar.android.controller.DbController;
import org.briarproject.briar.android.controller.handler.UiResultHandler;
import org.briarproject.briar.android.controller.handler.ResultHandler;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.login.PasswordActivity;
import org.briarproject.briar.android.panic.ExitActivity;
import java.util.logging.Logger;
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;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
@SuppressLint("Registered")
@@ -61,7 +60,6 @@ public abstract class BriarActivity extends BaseActivity {
super.onStart();
if (!briarController.hasEncryptionKey() && !isFinishing()) {
Intent i = new Intent(this, PasswordActivity.class);
i.setFlags(FLAG_ACTIVITY_SINGLE_TOP);
startActivityForResult(i, REQUEST_PASSWORD);
}
}
@@ -118,17 +116,28 @@ public abstract class BriarActivity extends BaseActivity {
}
protected void signOut(final boolean removeFromRecentApps) {
briarController.signOut(new UiResultHandler<Void>(this) {
@Override
public void onResultUi(Void result) {
if (removeFromRecentApps) startExitActivity();
else finishAndExit();
}
});
if (briarController.hasEncryptionKey()) {
// Don't use UiResultHandler because we want the result even if
// this activity has been destroyed
briarController.signOut(new ResultHandler<Void>() {
@Override
public void onResult(Void result) {
runOnUiThread(new Runnable() {
@Override
public void run() {
exit(removeFromRecentApps);
}
});
}
});
} else {
exit(removeFromRecentApps);
}
}
protected void signOut() {
signOut(false);
private void exit(boolean removeFromRecentApps) {
if (removeFromRecentApps) startExitActivity();
else finishAndExit();
}
private void startExitActivity() {

View File

@@ -21,6 +21,7 @@ import org.briarproject.briar.api.blog.Blog;
import org.briarproject.briar.api.blog.BlogCommentHeader;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPostHeader;
import org.briarproject.briar.util.HtmlUtils;
import java.util.ArrayList;
import java.util.Collection;
@@ -33,6 +34,7 @@ import javax.annotation.Nullable;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.briar.util.HtmlUtils.ARTICLE;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -229,6 +231,7 @@ abstract class BaseControllerImpl extends DbControllerImpl
return header;
}
@DatabaseExecutor
private BlogPostItem getItem(BlogPostHeader h) throws DbException {
String body;
if (h instanceof BlogCommentHeader) {
@@ -243,10 +246,11 @@ abstract class BaseControllerImpl extends DbControllerImpl
}
}
@DatabaseExecutor
private String getPostBody(MessageId m) throws DbException {
String body = bodyCache.get(m);
if (body == null) {
body = blogManager.getPostBody(m);
body = HtmlUtils.clean(blogManager.getPostBody(m), ARTICLE);
bodyCache.put(m, body);
}
//noinspection ConstantConditions

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.blog;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.Toolbar;
import android.view.View;
@@ -15,6 +14,7 @@ import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.sharing.BlogSharingStatusActivity;
import javax.annotation.Nullable;
import javax.inject.Inject;
@MethodsNotNullByDefault

View File

@@ -169,7 +169,7 @@ class BlogControllerImpl extends BaseControllerImpl
LocalAuthor a = identityManager.getLocalAuthor();
Blog b = blogManager.getBlog(groupId);
boolean ours = a.getId().equals(b.getAuthor().getId());
boolean removable = blogManager.canBeRemoved(groupId);
boolean removable = blogManager.canBeRemoved(b);
BlogItem blog = new BlogItem(b, ours, removable);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))

View File

@@ -43,7 +43,6 @@ import javax.inject.Inject;
import static android.app.Activity.RESULT_OK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static android.widget.Toast.LENGTH_SHORT;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_BLOG;
@@ -149,14 +148,14 @@ public class BlogFragment extends BaseFragment
return true;
case R.id.action_blog_share:
Intent i2 = new Intent(getActivity(), ShareBlogActivity.class);
i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
i2.putExtra(GROUP_ID, groupId.getBytes());
startActivityForResult(i2, REQUEST_SHARE_BLOG);
return true;
case R.id.action_blog_sharing_status:
Intent i3 = new Intent(getActivity(),
BlogSharingStatusActivity.class);
i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
i3.putExtra(GROUP_ID, groupId.getBytes());
startActivity(i3);
return true;

View File

@@ -48,6 +48,10 @@ public class BlogPostItem implements Comparable<BlogPostItem> {
return body;
}
public boolean isRssFeed() {
return header.isRssFeed();
}
public boolean isRead() {
return read;
}

View File

@@ -108,7 +108,8 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
author.setAuthor(a);
author.setAuthorStatus(post.getAuthorStatus());
author.setDate(post.getTimestamp());
author.setPersona(AuthorView.NORMAL);
author.setPersona(
item.isRssFeed() ? AuthorView.RSS_FEED : AuthorView.NORMAL);
// TODO make author clickable more often #624
if (item.getHeader().getType() == POST) {
author.setBlogLink(post.getGroupId());
@@ -168,7 +169,9 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
reblogger.setVisibility(VISIBLE);
reblogger.setPersona(AuthorView.REBLOGGER);
author.setPersona(AuthorView.COMMENTER);
author.setPersona(item.getHeader().getRootPost().isRssFeed() ?
AuthorView.RSS_FEED_REBLOGGED :
AuthorView.COMMENTER);
// comments
for (BlogCommentHeader c : item.getComments()) {

View File

@@ -179,7 +179,6 @@ public class FeedFragment extends BaseFragment implements
case R.id.action_rss_feeds_import:
Intent i2 =
new Intent(getActivity(), RssFeedImportActivity.class);
i2.putExtra(GROUP_ID, personalBlog.getId().getBytes());
startActivity(i2);
return true;
case R.id.action_rss_feeds_manage:

View File

@@ -6,7 +6,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ImageButton;
import android.widget.TextView;
import org.briarproject.briar.R;
@@ -39,12 +39,7 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
if (item == null) return;
// Feed Title
if (item.getTitle() != null) {
ui.title.setText(item.getTitle());
ui.title.setVisibility(VISIBLE);
} else {
ui.title.setVisibility(GONE);
}
ui.title.setText(item.getTitle());
// Delete Button
ui.delete.setOnClickListener(new OnClickListener() {
@@ -75,6 +70,14 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
} else {
ui.description.setVisibility(GONE);
}
// Open feed's blog when clicked
ui.layout.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
listener.onFeedClick(item);
}
});
}
@Override
@@ -99,8 +102,9 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
}
static class FeedViewHolder extends RecyclerView.ViewHolder {
private final View layout;
private final TextView title;
private final ImageView delete;
private final ImageButton delete;
private final TextView imported;
private final TextView updated;
private final TextView author;
@@ -110,8 +114,9 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
private FeedViewHolder(View v) {
super(v);
layout = v;
title = (TextView) v.findViewById(R.id.titleView);
delete = (ImageView) v.findViewById(R.id.deleteButton);
delete = (ImageButton) v.findViewById(R.id.deleteButton);
imported = (TextView) v.findViewById(R.id.importedView);
updated = (TextView) v.findViewById(R.id.updatedView);
author = (TextView) v.findViewById(R.id.authorView);
@@ -121,6 +126,7 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
}
interface RssFeedListener {
void onFeedClick(Feed feed);
void onDeleteClick(Feed feed);
}

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.blog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
@@ -15,7 +14,6 @@ import android.widget.ProgressBar;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
@@ -44,9 +42,6 @@ public class RssFeedImportActivity extends BriarActivity {
@IoExecutor
Executor ioExecutor;
// Fields that are accessed from background threads must be volatile
private volatile GroupId groupId = null;
@Inject
@SuppressWarnings("WeakerAccess")
volatile FeedManager feedManager;
@@ -55,12 +50,6 @@ public class RssFeedImportActivity extends BriarActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// GroupId from Intent
Intent i = getIntent();
byte[] b = i.getByteArrayExtra(GROUP_ID);
if (b == null) throw new IllegalStateException("No Group in intent.");
groupId = new GroupId(b);
setContentView(R.layout.activity_rss_feed_import);
urlInput = (EditText) findViewById(R.id.urlInput);
@@ -128,7 +117,7 @@ public class RssFeedImportActivity extends BriarActivity {
@Override
public void run() {
try {
feedManager.addFeed(url, groupId);
feedManager.addFeed(url);
feedImported();
} catch (DbException | IOException e) {
if (LOG.isLoggable(WARNING))

View File

@@ -1,15 +1,16 @@
package org.briarproject.briar.android.blog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
@@ -23,6 +24,7 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.support.design.widget.Snackbar.LENGTH_LONG;
import static java.util.logging.Level.WARNING;
@@ -34,7 +36,6 @@ public class RssFeedManageActivity extends BriarActivity
private BriarRecyclerView list;
private RssFeedAdapter adapter;
private GroupId groupId;
@Inject
@SuppressWarnings("WeakerAccess")
@@ -44,12 +45,6 @@ public class RssFeedManageActivity extends BriarActivity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// GroupId from Intent
Intent i = getIntent();
byte[] b = i.getByteArrayExtra(GROUP_ID);
if (b == null) throw new IllegalStateException("No Group in intent.");
groupId = new GroupId(b);
setContentView(R.layout.activity_rss_feed_manage);
adapter = new RssFeedAdapter(this, this);
@@ -87,7 +82,6 @@ public class RssFeedManageActivity extends BriarActivity
return true;
case R.id.action_rss_feeds_import:
Intent i = new Intent(this, RssFeedImportActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
startActivity(i);
return true;
default:
@@ -100,21 +94,32 @@ public class RssFeedManageActivity extends BriarActivity
component.inject(this);
}
@Override
public void onFeedClick(Feed feed) {
Intent i = new Intent(this, BlogActivity.class);
i.putExtra(GROUP_ID, feed.getBlogId().getBytes());
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
}
@Override
public void onDeleteClick(final Feed feed) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
feedManager.removeFeed(feed.getUrl());
onFeedDeleted(feed);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
onDeleteError();
}
}
});
DialogInterface.OnClickListener okListener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
deleteFeed(feed);
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(this,
R.style.BriarDialogTheme);
builder.setTitle(getString(R.string.blogs_rss_remove_feed));
builder.setMessage(
getString(R.string.blogs_rss_remove_feed_dialog_message));
builder.setPositiveButton(R.string.cancel, null);
builder.setNegativeButton(R.string.blogs_rss_remove_feed_ok,
okListener);
builder.show();
}
private void loadFeeds() {
@@ -149,6 +154,22 @@ public class RssFeedManageActivity extends BriarActivity
});
}
private void deleteFeed(final Feed feed) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
feedManager.removeFeed(feed);
onFeedDeleted(feed);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
onDeleteError();
}
}
});
}
private void onLoadError() {
runOnUiThreadUnlessDestroyed(new Runnable() {
@Override

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.android.contact;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.v7.widget.RecyclerView;
import android.view.View;
@@ -13,6 +12,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import javax.annotation.Nullable;
import im.delight.android.identicons.IdenticonDrawable;
@UiThread

View File

@@ -946,6 +946,7 @@ public class ConversationActivity extends BriarActivity
@Override
public void respondToRequest(final ConversationRequestItem item,
final boolean accept) {
item.setAnswered(true);
int position = adapter.findItemPosition(item);
if (position != INVALID_POSITION) {
adapter.notifyItemChanged(position, item);

View File

@@ -21,7 +21,8 @@ class ConversationRequestItem extends ConversationNoticeInItem {
private final GroupId requestedGroupId;
private final RequestType requestType;
private final SessionId sessionId;
private final boolean answered, canBeOpened;
private final boolean canBeOpened;
private boolean answered;
ConversationRequestItem(MessageId id, GroupId groupId,
RequestType requestType, SessionId sessionId, String text,
@@ -53,6 +54,10 @@ class ConversationRequestItem extends ConversationNoticeInItem {
return answered;
}
void setAnswered(boolean answered) {
this.answered = answered;
}
public boolean canBeOpened() {
return canBeOpened;
}

View File

@@ -51,6 +51,8 @@ class ConversationRequestViewHolder extends ConversationNoticeInViewHolder {
acceptButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
acceptButton.setEnabled(false);
declineButton.setEnabled(false);
listener.respondToRequest(item, true);
}
});
@@ -58,6 +60,8 @@ class ConversationRequestViewHolder extends ConversationNoticeInViewHolder {
declineButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
acceptButton.setEnabled(false);
declineButton.setEnabled(false);
listener.respondToRequest(item, false);
}
});

View File

@@ -17,7 +17,7 @@ public class DbControllerImpl implements DbController {
private static final Logger LOG =
Logger.getLogger(DbControllerImpl.class.getName());
private final Executor dbExecutor;
protected final Executor dbExecutor;
private final LifecycleManager lifecycleManager;
@Inject

View File

@@ -1,7 +1,5 @@
package org.briarproject.briar.android.controller;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
@@ -15,6 +13,7 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Nullable;
import javax.inject.Inject;
@NotNullByDefault

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