Compare commits

...

108 Commits

Author SHA1 Message Date
akwizgran
070165f608 Bump version numbers for 1.4.23 release. 2023-02-24 14:04:57 +00:00
Torsten Grote
445f174275 Merge branch 'update-tor-bridges' into 'master'
Update Tor bridges

See merge request briar/briar!1778
2023-02-24 13:28:27 +00:00
akwizgran
ea5af72878 Add some non-default bridges. 2023-02-24 12:31:22 +00:00
akwizgran
ecf2e75424 Remove some bridges not known to Onionoo. 2023-02-24 12:30:24 +00:00
akwizgran
feebd89029 Remove some failing bridges. 2023-02-24 12:23:37 +00:00
akwizgran
cf723f8002 Merge branch 'dont-unpack-tor' into 'master'
Upgrade tor, obfs4proxy, snoflake and convert tor to regular dependencies in bramble-java

See merge request briar/briar!1775
2023-02-24 12:13:31 +00:00
akwizgran
b8e743021c Update translations. 2023-02-24 12:11:58 +00:00
Sebastian Kürten
b785b6c10f Upgrade tor 2023-02-24 13:05:46 +01:00
Sebastian Kürten
26ec200f50 Convert tor to regular dependencies 2023-02-24 12:51:50 +01:00
akwizgran
82efb0d044 Upgrade Tor, obfs4 and snowflake; use new artifact layout. 2023-02-23 16:25:08 +01:00
akwizgran
4ac4ba13d4 Merge branch 'split-app' into 'master'
Split out APP for check_reproducibility CI job

See merge request briar/briar!1777
2023-02-22 17:06:31 +00:00
Torsten Grote
0844cd3547 Split out APP for check_reproducibility CI job 2023-02-21 16:14:02 -03:00
Torsten Grote
69e6648ded Merge branch '2415-check-bt-socket-streams-not-null' into 'master'
Check that BluetoothSocket's input and output streams aren't null

Closes #2415

See merge request briar/briar!1776
2023-02-21 13:38:49 +00:00
akwizgran
518aeb38b9 Check that BluetoothSocket's input and output streams aren't null. 2023-02-21 13:29:35 +00:00
akwizgran
7e5e61fc05 Merge branch 'dont-package-all-snowflake-architectures-into-headless-jars' into 'master'
Don't package all snowflake architectures into headless jars

See merge request briar/briar!1774
2023-02-21 11:34:04 +00:00
Sebastian Kürten
6ecb44bcaa Don't package all snowflake architectures into headless jars 2023-02-21 09:38:24 +01:00
akwizgran
f02bbebf6c Bump version numbers for 1.4.22 release. 2023-02-20 17:29:41 +00:00
akwizgran
b8612715f8 Merge branch 'check_repro' into 'master'
Fix variable substitution for check_reproducibility

See merge request briar/briar!1773
2023-02-20 17:26:25 +00:00
Torsten Grote
b86ddfa22f Fix variable substitution for check_reproducibility 2023-02-20 14:08:19 -03:00
akwizgran
0dd4d86f4a Bump version numbers for 1.4.21 release. 2023-02-20 16:00:45 +00:00
akwizgran
17e0829f42 Update translations. 2023-02-20 16:00:05 +00:00
Torsten Grote
938d8b71a0 Merge branch 'bdf-cleanup' into 'master'
Clean up some BDF quirks

See merge request briar/briar!1772
2023-02-20 14:15:47 +00:00
akwizgran
1b808584b6 Replace some more longs with ints. 2023-02-20 13:53:40 +00:00
akwizgran
36db5b48ef Remove methods for manually reading lists and dictionaries. 2023-02-20 13:05:38 +00:00
akwizgran
ccd6ed9ff0 Add fast path for writing BdfDictionaries. 2023-02-20 11:56:13 +00:00
akwizgran
0ced10b3a9 Use getInt() in a couple more places. 2023-02-20 11:33:48 +00:00
akwizgran
98064e9efe Remove BdfWriter methods for manually constructing lists and dicts. 2023-02-18 17:36:02 +00:00
akwizgran
63172ef2e4 Add 32-bit int methods to BdfList and BdfDictionary.
We use these a lot so it's useful to have built-in support.

Also refactor BdfList and BdfDictionary so the getters that take default values behave like the other getters. This simplifies the semantics and allows duplicated code to be removed.

Add comprehensive tests for BdfList and BdfDictionary.
2023-02-18 17:36:02 +00:00
akwizgran
7a854e70cb Add BdfReader methods for 32-bit ints.
We use these a lot so it's convenient to have built-in support.

Also make BdfReaderImpl and BdfWriterImpl final to enable compiler optimisations.
2023-02-18 17:36:02 +00:00
akwizgran
ac8a4db457 Add support for reading and writing BDF in canonical form.
Existing transport property updates may not be in canonical form, so we need to parse them leniently.
2023-02-18 17:36:02 +00:00
akwizgran
5a09530670 Reject invalid UTF-8 instead of ignoring it. 2023-02-18 17:10:57 +00:00
akwizgran
4fe91bacc6 Merge branch 'sync-record-reader-tests' into 'master'
Add some tests for sync record reader

See merge request briar/briar!1771
2023-02-17 17:38:04 +00:00
akwizgran
7f70a1519b Make message fields local. 2023-02-17 17:28:10 +00:00
akwizgran
c92ee0458e Add some tests for sync record reader. 2023-02-17 17:19:59 +00:00
akwizgran
10b1fe756d Merge branch 'reproduce-headless' into 'master'
Check reproducibility of headless releases

See merge request briar/briar!1769
2023-02-13 16:21:50 +00:00
Torsten Grote
1a2a250be0 Check reproducibility of headless releases 2023-02-10 16:43:01 -03:00
akwizgran
a621b8077e Merge branch 'update-reproducer-release-tag' into 'master'
Update briar-reproducer release tag

See merge request briar/briar!1768
2023-02-10 14:17:15 +00:00
akwizgran
19084d4060 Merge branch 'mailbox-version-mismatch' into 'master'
Show mailbox version issues before connection failures

See merge request briar/briar!1767
2023-02-10 13:57:36 +00:00
akwizgran
2f73ee1b57 Update briar-reproducer release tag. 2023-02-10 13:54:44 +00:00
Torsten Grote
45fa12c0b3 Show mailbox version issues before connection failures 2023-02-09 13:10:02 -03:00
akwizgran
4253bbaaf5 Update translations. 2023-02-06 12:40:04 +00:00
akwizgran
8c2e58796b Merge branch 'mailbox-convert-qr' into 'master'
Add convenience method for converting mailbox pairing text into QR code payload

See merge request briar/briar!1766
2023-02-03 14:34:04 +00:00
Torsten Grote
3f13e7e9c3 Add convenience method for converting mailbox pairing text into QR code payload 2023-02-03 11:18:25 -03:00
akwizgran
421a93b9a6 Merge branch 'rss-from-file' into 'master'
Allow to import RSS feeds from a file

See merge request briar/briar!1765
2023-01-31 13:17:21 +00:00
Torsten Grote
8a088638db Don't show success fragment for RSS file import 2023-01-31 09:57:51 -03:00
Torsten Grote
a888c5f632 Allow to import RSS feeds from a file 2023-01-30 15:31:25 -03:00
Torsten Grote
0b94814620 Merge branch 'remove-migration-code' into 'master'
Remove various bits of code whose migration periods have passed

See merge request briar/briar!1750
2023-01-30 13:59:02 +00:00
akwizgran
e82e11acfa Merge branch '2384-mailbox-problem-notification' into 'master'
Clear mailbox problem notification after unlinking

Closes #2384

See merge request briar/briar!1764
2023-01-27 15:09:27 +00:00
Torsten Grote
795461d9a8 Clear mailbox problem notification after unlinking 2023-01-27 11:49:01 -03:00
Torsten Grote
7b8d01cfe0 Merge branch '1822-rss-feeds-backend' into 'master'
Resolve "Import RSS feeds shared by other apps"

See merge request briar/briar!1763
2023-01-25 11:39:34 +00:00
akwizgran
abd04ee7f5 Add tests for feed serialisation/deserialisation. 2023-01-24 17:27:52 +00:00
akwizgran
cc5365eaf0 Remove redundant comparison from test. 2023-01-24 17:27:34 +00:00
akwizgran
6b20b03698 Bump version numbers for 1.4.20 release. 2023-01-24 15:51:48 +00:00
akwizgran
9da7fbf4f6 Update translations. 2023-01-24 15:51:26 +00:00
akwizgran
f64f442fcf Merge branch 'add-comment-for-is-connected' into 'master'
Add comment about NetworkInfo#isConnected()

See merge request briar/briar!1762
2023-01-24 15:25:52 +00:00
akwizgran
6eda2f6d13 AnimalSniffer doesn't allow StandardCharsets in tests. 2023-01-24 14:50:40 +00:00
akwizgran
6faa095dfb FeedMatcher interface doesn't need to be public. 2023-01-24 14:48:55 +00:00
akwizgran
4007fca668 Add integration tests for importing an RSS feed from a file. 2023-01-24 14:15:03 +00:00
akwizgran
28a747f7f3 Add method for adding an RSS feed from an input stream. 2023-01-24 13:57:44 +00:00
Sebastian Kürten
fd2d5c9173 Add comment about NetworkInfo#isConnected() 2023-01-24 14:48:03 +01:00
akwizgran
8f7bb9d26b Don't overwrite the list of feeds after fetching. 2023-01-24 13:28:22 +00:00
akwizgran
dc220200b6 Match newly added RSS feeds to existing feeds. 2023-01-24 12:43:14 +00:00
Torsten Grote
0cea137d75 Merge branch 'update-tor-bridges' into 'master'
Update Tor bridges

See merge request briar/briar!1761
2023-01-23 15:06:28 +00:00
akwizgran
2eef34f424 Use new transaction wrappers. 2023-01-23 13:02:16 +00:00
akwizgran
a68fff9dd2 Merge branch 'tor-0.4.7.13' into 'master'
Upgrade Tor to 0.4.7.13

See merge request briar/briar!1760
2023-01-23 12:11:32 +00:00
akwizgran
ddc8f4a7d7 Add three non-default obfs4 bridges. 2023-01-20 16:12:47 +00:00
akwizgran
f961b6a80b Remove three failing bridges. 2023-01-20 16:11:11 +00:00
akwizgran
93439d9c17 Update translations. 2023-01-20 15:50:28 +00:00
akwizgran
f3ee884816 Upgrade Tor to 0.4.7.13. 2023-01-20 15:34:23 +00:00
akwizgran
8ca22043cf Merge branch '1897-sharing-status' into 'master'
Introduce SharingStatus to report more fine-grained status

Closes #1897

See merge request briar/briar!1758
2023-01-20 14:33:32 +00:00
Torsten Grote
9353b78da8 Clarify sharing state docs 2023-01-20 11:13:35 -03:00
Torsten Grote
429bbe1275 Introduce more sharing states 2023-01-20 11:13:35 -03:00
Torsten Grote
c5fb1416bd Update JavaDoc for SharingState change 2023-01-20 11:13:34 -03:00
Torsten Grote
e52cbd896e Introduce SharingStatus to report more fine-grained status 2023-01-20 11:13:34 -03:00
akwizgran
ab1b8784b7 Merge branch '90-clickable-links' into 'master'
Resolve "Handle Hyperlinks (Clickable Links)"

Closes #90

See merge request briar/briar!1757
2023-01-20 13:47:08 +00:00
akwizgran
55a4daa92f Merge branch 'progressbar-remove-contact' into 'master'
Show progress bar while removing contact

See merge request briar/briar!1759
2023-01-20 13:44:29 +00:00
akwizgran
e52250f1e4 Don't sort list of RSS feeds in UI. 2023-01-18 15:04:38 +00:00
akwizgran
33d01aac8c Add matcher for matching an imported feed against existing feeds. 2023-01-18 15:04:38 +00:00
akwizgran
b920382e44 Store additional properties of RSS feed in metadata. 2023-01-18 15:04:38 +00:00
akwizgran
1a2f85f701 Small code cleanups for feed manager, don't fetch new feeds twice. 2023-01-18 15:04:33 +00:00
Airplane Mode
186bcc0b47 Show progress bar while removing contact 2023-01-16 23:56:26 +00:00
Torsten Grote
8b9140f477 Merge branch 'tor-0.4.7.12' into 'master'
Upgrade Tor to 0.4.7.12

See merge request briar/briar!1755
2023-01-13 16:25:42 +00:00
Katelyn Dickey
f959c32935 Remove autoLink attribute which was causing warnings to show twice, and highlight links in comments before a blog is expanded 2023-01-05 17:20:40 -05:00
akwizgran
1c060bc6db Upgrade Tor to 0.4.7.12. 2023-01-04 17:51:46 +00:00
Katelyn Dickey
5e44e4d308 Add clickable links to blog comments 2023-01-03 20:52:36 -05:00
Katelyn Dickey
75d5dec45f Add clickable links to notices/requests 2023-01-03 18:38:13 -05:00
Katelyn Dickey
d825227eb5 Add clickable links to threads (forums/groups) 2023-01-03 18:38:07 -05:00
Katelyn Dickey
967dd1f18d Add clickable links for conversations 2023-01-03 18:37:40 -05:00
akwizgran
4a4147b563 Bump version numbers for 1.4.19 release. 2022-12-30 11:15:32 +00:00
akwizgran
08b72af647 Update translations. 2022-12-30 11:07:38 +00:00
akwizgran
528e090c6f Merge branch '2409-require-obsolete-bluetooth-permission' into 'master'
Require obsolete BLUETOOTH permission on API 31

Closes #2409

See merge request briar/briar!1754
2022-12-30 10:56:29 +00:00
akwizgran
652f9e5705 Require obsolete BLUETOOTH permission on API 31.
This is a workaround for a platform bug on Xiaomi/Redmi/POCO devices that still checks for the obsolete permission.
2022-12-28 14:12:30 +00:00
akwizgran
6a91ec7a6b Merge branch '2407-bluetooth-permission' into 'master'
Always check Bluetooth permission when trying to get own address

Closes #2407

See merge request briar/briar!1753
2022-12-28 11:07:31 +00:00
akwizgran
c3a9eff96b Always check Bluetooth permission when trying to get own address. 2022-12-22 17:46:12 +00:00
akwizgran
bd05d893eb Merge branch '2397-wrong-type-of-qr-code' into 'master'
Tweak text for unknown QR code type

See merge request briar/briar!1752
2022-12-21 12:29:48 +00:00
akwizgran
6965bc0acd Tweak text for unknown QR code type. 2022-12-21 12:19:31 +00:00
akwizgran
c6e9554026 Merge branch '2397-wrong-type-of-qr-code' into 'master'
Show appropriate error message if user scans wrong kind of QR code

Closes #2397

See merge request briar/briar!1748
2022-12-19 15:43:16 +00:00
akwizgran
ab8734e373 Show relevant message when contact QR code has unknown format. 2022-12-19 10:24:58 +00:00
akwizgran
267956b36c Restore javadoc for qrCodeTooOld flag. 2022-12-19 10:04:55 +00:00
akwizgran
ec84ddb38b Merge branch '2403-show-progress-while-connecting-to-mailbox' into 'master'
Show progress while connecting to mailbox

Closes #2403

See merge request briar/briar!1747
2022-12-14 12:20:43 +00:00
akwizgran
ba2db48d8e Center text, add margin at bottom to center layout. 2022-12-14 12:03:06 +00:00
akwizgran
186f61f771 Set width of text views to 0dp so margins are applied. 2022-12-12 16:14:34 +00:00
akwizgran
7a3ffcbae6 Remove various bits of code whose migration periods have passed. 2022-12-07 17:47:02 +00:00
akwizgran
95d8783852 Show appropriate error message if contact QR code is scanned. 2022-12-02 14:27:42 +00:00
akwizgran
b4f3604584 Show appropriate error message if mailbox QR code is scanned. 2022-12-02 13:35:00 +00:00
akwizgran
badccac90c Factor out recognition of QR code format. 2022-12-02 13:35:00 +00:00
akwizgran
1b8d1a5a8d Update test expectations. 2022-11-30 17:30:33 +00:00
akwizgran
2fe57d2597 Show progress while connecting to mailbox. 2022-11-30 17:17:08 +00:00
215 changed files with 5132 additions and 1748 deletions

View File

@@ -83,7 +83,8 @@ android test:
test_reproducible:
stage: check_reproducibility
script:
- "curl -X POST -F token=${RELEASE_CHECK_TOKEN} -F ref=master -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/61/trigger/pipeline"
- "curl -X POST -F token=${RELEASE_CHECK_TOKEN} -F ref=master -F variables[APP]='briar' -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/61/trigger/pipeline"
- "curl -X POST -F token=${RELEASE_JAR_CHECK_TOKEN} -F ref=main -F variables[APP]='briar-headless' -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/307/trigger/pipeline"
only:
- tags

View File

@@ -13,8 +13,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 31
versionCode 10418
versionName "1.4.18"
versionCode 10423
versionName "1.4.23"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -71,7 +71,7 @@ def torLibsDir = 'src/main/jniLibs'
task cleanTorBinaries {
outputs.dir torLibsDir
doLast {
delete fileTree(torLibsDir) { include '**/*.so' }
delete fileTree(torLibsDir)
}
}
@@ -80,34 +80,9 @@ clean.dependsOn cleanTorBinaries
task unpackTorBinaries {
outputs.dir torLibsDir
doLast {
configurations.tor.each { outer ->
zipTree(outer).each { inner ->
if (inner.name.endsWith('_arm_pie.zip')) {
copy {
from zipTree(inner)
into torLibsDir
rename '(.*)', 'armeabi-v7a/lib$1.so'
}
} else if (inner.name.endsWith('_arm64_pie.zip')) {
copy {
from zipTree(inner)
into torLibsDir
rename '(.*)', 'arm64-v8a/lib$1.so'
}
} else if (inner.name.endsWith('_x86_pie.zip')) {
copy {
from zipTree(inner)
into torLibsDir
rename '(.*)', 'x86/lib$1.so'
}
} else if (inner.name.endsWith('_x86_64_pie.zip')) {
copy {
from zipTree(inner)
into torLibsDir
rename '(.*)', 'x86_64/lib$1.so'
}
}
}
copy {
from configurations.tor.collect { zipTree(it) }
into torLibsDir
}
}
dependsOn cleanTorBinaries

View File

@@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.briarproject.bramble">
<uses-feature
@@ -7,15 +8,17 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- The BLUETOOTH permission was supposed to be removed in API 31 but is still needed on some Xiaomi/Redmi/POCO devices running API 31 -->
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
android:maxSdkVersion="31" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
android:usesPermissionFlags="neverForLocation"
tools:targetApi="31" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.INTERNET" />

View File

@@ -118,6 +118,11 @@ class AndroidNetworkManager implements NetworkManager, Service {
try {
NetworkInfo net = connectivityManager.getActiveNetworkInfo();
boolean connected = net != null && net.isConnected();
// Research into Android's behavior to check network connectivity
// (https://code.briarproject.org/briar/public-mesh-research/-/issues/19)
// has shown that NetworkInfo#isConnected() returns true if the device
// is connected to any Wifi, independent of whether any specific IP
// address can be reached using it or any domain names can be resolved.
boolean wifi = false, ipv6Only = false;
if (connected) {
wifi = net.getType() == TYPE_WIFI;

View File

@@ -33,8 +33,10 @@ class AndroidBluetoothTransportConnection
super(plugin);
this.connectionLimiter = connectionLimiter;
this.socket = socket;
in = timeoutMonitor.createTimeoutInputStream(
socket.getInputStream(), plugin.getMaxIdleTime() * 2);
InputStream socketIn = socket.getInputStream();
if (socketIn == null) throw new IOException();
in = timeoutMonitor.createTimeoutInputStream(socketIn,
plugin.getMaxIdleTime() * 2L);
wakeLock = wakeLockManager.createWakeLock("BluetoothConnection");
wakeLock.acquire();
String address = socket.getRemoteDevice().getAddress();
@@ -48,7 +50,9 @@ class AndroidBluetoothTransportConnection
@Override
protected OutputStream getOutputStream() throws IOException {
return socket.getOutputStream();
OutputStream socketOut = socket.getOutputStream();
if (socketOut == null) throw new IOException();
return socketOut;
}
@Override

View File

@@ -65,6 +65,9 @@ public class AndroidUtils {
public static Pair<String, String> getBluetoothAddressAndMethod(Context ctx,
BluetoothAdapter adapter) {
// If we don't have permission to access the adapter's address, let
// the caller know we can't find it
if (!hasBtConnectPermission(ctx)) return new Pair<>("", "");
// Return the adapter's address if it's valid and not fake
@SuppressLint("HardwareIds")
String address = adapter.getAddress();

View File

@@ -24,9 +24,9 @@ dependencyVerification {
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.briarproject:obfs4proxy-android:0.0.14-tor1:obfs4proxy-android-0.0.14-tor1.jar:8b08068778b133484b17956d8f7a7710739c33f671a26a68156f4d34e6f28c30',
'org.briarproject:snowflake-android:2.3.1:snowflake-android-2.3.1.jar:1f83c9a070f87b7074af13627709a8b5aced5460104be7166af736b1bb73c293',
'org.briarproject:tor-android:0.4.5.14:tor-android-0.4.5.14.jar:7cf1beaa6c1db51fc8fac263aba9624ef289c3db29772509efcbc59f7057330a',
'org.briarproject:obfs4proxy-android:0.0.14-tor2:obfs4proxy-android-0.0.14-tor2.jar:a0a93770d6760ce57d9dbd31cc7177687374e00c3361dac22ab75e3b6e0f289e',
'org.briarproject:snowflake-android:2.5.1:snowflake-android-2.5.1.jar:88ec81c17b1b6fa884d06839dec0330e328b45c89f88c970a213ce91ca8eac87',
'org.briarproject:tor-android:0.4.7.13-2:tor-android-0.4.7.13-2.jar:453fd463b234a2104edd7f0d02d0649cbb5c5efbe47a76df3828f55a3f90f8b5',
'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a',
'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',

View File

@@ -16,6 +16,7 @@ import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
@Immutable
@@ -23,17 +24,24 @@ import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOC
public abstract class BdfMessageValidator implements MessageValidator {
protected static final Logger LOG =
Logger.getLogger(BdfMessageValidator.class.getName());
getLogger(BdfMessageValidator.class.getName());
protected final ClientHelper clientHelper;
protected final MetadataEncoder metadataEncoder;
protected final Clock clock;
protected final boolean canonical;
protected BdfMessageValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
MetadataEncoder metadataEncoder, Clock clock, boolean canonical) {
this.clientHelper = clientHelper;
this.metadataEncoder = metadataEncoder;
this.clock = clock;
this.canonical = canonical;
}
protected BdfMessageValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
this(clientHelper, metadataEncoder, clock, true);
}
protected abstract BdfMessageContext validateMessage(Message m, Group g,
@@ -49,7 +57,7 @@ public abstract class BdfMessageValidator implements MessageValidator {
"Timestamp is too far in the future");
}
try {
BdfList bodyList = clientHelper.toList(m.getBody());
BdfList bodyList = clientHelper.toList(m, canonical);
BdfMessageContext result = validateMessage(m, g, bodyList);
Metadata meta = metadataEncoder.encode(result.getDictionary());
return new MessageContext(meta, result.getDependencies());

View File

@@ -49,6 +49,9 @@ public interface ClientHelper {
BdfList getMessageAsList(Transaction txn, MessageId m) throws DbException,
FormatException;
BdfList getMessageAsList(Transaction txn, MessageId m, boolean canonical)
throws DbException, FormatException;
BdfDictionary getGroupMetadataAsDictionary(GroupId g) throws DbException,
FormatException;
@@ -106,6 +109,8 @@ public interface ClientHelper {
BdfList toList(Message m) throws FormatException;
BdfList toList(Message m, boolean canonical) throws FormatException;
BdfList toList(Author a);
byte[] sign(String label, BdfList toSign, PrivateKey privateKey)

View File

@@ -11,7 +11,7 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
public class BdfDictionary extends TreeMap<String, Object> {
public final class BdfDictionary extends TreeMap<String, Object> {
public static final Object NULL_VALUE = new Object();
@@ -39,9 +39,9 @@ public class BdfDictionary extends TreeMap<String, Object> {
}
public Boolean getBoolean(String key) throws FormatException {
Object o = get(key);
if (o instanceof Boolean) return (Boolean) o;
throw new FormatException();
Boolean value = getOptionalBoolean(key);
if (value == null) throw new FormatException();
return value;
}
@Nullable
@@ -52,19 +52,16 @@ public class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException();
}
public Boolean getBoolean(String key, Boolean defaultValue) {
Object o = get(key);
if (o instanceof Boolean) return (Boolean) o;
return defaultValue;
public Boolean getBoolean(String key, Boolean defaultValue)
throws FormatException {
Boolean value = getOptionalBoolean(key);
return value == null ? defaultValue : value;
}
public Long getLong(String key) throws FormatException {
Object o = get(key);
if (o instanceof Long) return (Long) o;
if (o instanceof Integer) return ((Integer) o).longValue();
if (o instanceof Short) return ((Short) o).longValue();
if (o instanceof Byte) return ((Byte) o).longValue();
throw new FormatException();
Long value = getOptionalLong(key);
if (value == null) throw new FormatException();
return value;
}
@Nullable
@@ -78,20 +75,37 @@ public class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException();
}
public Long getLong(String key, Long defaultValue) {
Object o = get(key);
if (o instanceof Long) return (Long) o;
if (o instanceof Integer) return ((Integer) o).longValue();
if (o instanceof Short) return ((Short) o).longValue();
if (o instanceof Byte) return ((Byte) o).longValue();
return defaultValue;
public Long getLong(String key, Long defaultValue) throws FormatException {
Long value = getOptionalLong(key);
return value == null ? defaultValue : value;
}
public Integer getInt(String key) throws FormatException {
Integer value = getOptionalInt(key);
if (value == null) throw new FormatException();
return value;
}
@Nullable
public Integer getOptionalInt(String key) throws FormatException {
Long value = getOptionalLong(key);
if (value == null) return null;
if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
throw new FormatException();
}
return value.intValue();
}
public Integer getInt(String key, Integer defaultValue)
throws FormatException {
Integer value = getOptionalInt(key);
return value == null ? defaultValue : value;
}
public Double getDouble(String key) throws FormatException {
Object o = get(key);
if (o instanceof Double) return (Double) o;
if (o instanceof Float) return ((Float) o).doubleValue();
throw new FormatException();
Double value = getOptionalDouble(key);
if (value == null) throw new FormatException();
return value;
}
@Nullable
@@ -103,17 +117,16 @@ public class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException();
}
public Double getDouble(String key, Double defaultValue) {
Object o = get(key);
if (o instanceof Double) return (Double) o;
if (o instanceof Float) return ((Float) o).doubleValue();
return defaultValue;
public Double getDouble(String key, Double defaultValue)
throws FormatException {
Double value = getOptionalDouble(key);
return value == null ? defaultValue : value;
}
public String getString(String key) throws FormatException {
Object o = get(key);
if (o instanceof String) return (String) o;
throw new FormatException();
String value = getOptionalString(key);
if (value == null) throw new FormatException();
return value;
}
@Nullable
@@ -124,17 +137,16 @@ public class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException();
}
public String getString(String key, String defaultValue) {
Object o = get(key);
if (o instanceof String) return (String) o;
return defaultValue;
public String getString(String key, String defaultValue)
throws FormatException {
String value = getOptionalString(key);
return value == null ? defaultValue : value;
}
public byte[] getRaw(String key) throws FormatException {
Object o = get(key);
if (o instanceof byte[]) return (byte[]) o;
if (o instanceof Bytes) return ((Bytes) o).getBytes();
throw new FormatException();
byte[] value = getOptionalRaw(key);
if (value == null) throw new FormatException();
return value;
}
@Nullable
@@ -146,17 +158,16 @@ public class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException();
}
public byte[] getRaw(String key, byte[] defaultValue) {
Object o = get(key);
if (o instanceof byte[]) return (byte[]) o;
if (o instanceof Bytes) return ((Bytes) o).getBytes();
return defaultValue;
public byte[] getRaw(String key, byte[] defaultValue)
throws FormatException {
byte[] value = getOptionalRaw(key);
return value == null ? defaultValue : value;
}
public BdfList getList(String key) throws FormatException {
Object o = get(key);
if (o instanceof BdfList) return (BdfList) o;
throw new FormatException();
BdfList value = getOptionalList(key);
if (value == null) throw new FormatException();
return value;
}
@Nullable
@@ -167,16 +178,16 @@ public class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException();
}
public BdfList getList(String key, BdfList defaultValue) {
Object o = get(key);
if (o instanceof BdfList) return (BdfList) o;
return defaultValue;
public BdfList getList(String key, BdfList defaultValue)
throws FormatException {
BdfList value = getOptionalList(key);
return value == null ? defaultValue : value;
}
public BdfDictionary getDictionary(String key) throws FormatException {
Object o = get(key);
if (o instanceof BdfDictionary) return (BdfDictionary) o;
throw new FormatException();
BdfDictionary value = getOptionalDictionary(key);
if (value == null) throw new FormatException();
return value;
}
@Nullable
@@ -188,9 +199,9 @@ public class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException();
}
public BdfDictionary getDictionary(String key, BdfDictionary defaultValue) {
Object o = get(key);
if (o instanceof BdfDictionary) return (BdfDictionary) o;
return defaultValue;
public BdfDictionary getDictionary(String key, BdfDictionary defaultValue)
throws FormatException {
BdfDictionary value = getOptionalDictionary(key);
return value == null ? defaultValue : value;
}
}

View File

@@ -13,7 +13,7 @@ import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
@NotThreadSafe
public class BdfList extends ArrayList<Object> {
public final class BdfList extends ArrayList<Object> {
/**
* Factory method for constructing lists inline.
@@ -33,15 +33,15 @@ public class BdfList extends ArrayList<Object> {
super(items);
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean isInRange(int index) {
return index >= 0 && index < size();
}
public Boolean getBoolean(int index) throws FormatException {
if (!isInRange(index)) throw new FormatException();
Object o = get(index);
if (o instanceof Boolean) return (Boolean) o;
throw new FormatException();
Boolean value = getOptionalBoolean(index);
if (value == null) throw new FormatException();
return value;
}
@Nullable
@@ -53,21 +53,16 @@ public class BdfList extends ArrayList<Object> {
throw new FormatException();
}
public Boolean getBoolean(int index, Boolean defaultValue) {
if (!isInRange(index)) return defaultValue;
Object o = get(index);
if (o instanceof Boolean) return (Boolean) o;
return defaultValue;
public Boolean getBoolean(int index, Boolean defaultValue)
throws FormatException {
Boolean value = getOptionalBoolean(index);
return value == null ? defaultValue : value;
}
public Long getLong(int index) throws FormatException {
if (!isInRange(index)) throw new FormatException();
Object o = get(index);
if (o instanceof Long) return (Long) o;
if (o instanceof Integer) return ((Integer) o).longValue();
if (o instanceof Short) return ((Short) o).longValue();
if (o instanceof Byte) return ((Byte) o).longValue();
throw new FormatException();
Long value = getOptionalLong(index);
if (value == null) throw new FormatException();
return value;
}
@Nullable
@@ -82,22 +77,37 @@ public class BdfList extends ArrayList<Object> {
throw new FormatException();
}
public Long getLong(int index, Long defaultValue) {
if (!isInRange(index)) return defaultValue;
Object o = get(index);
if (o instanceof Long) return (Long) o;
if (o instanceof Integer) return ((Integer) o).longValue();
if (o instanceof Short) return ((Short) o).longValue();
if (o instanceof Byte) return ((Byte) o).longValue();
return defaultValue;
public Long getLong(int index, Long defaultValue) throws FormatException {
Long value = getOptionalLong(index);
return value == null ? defaultValue : value;
}
public Integer getInt(int index) throws FormatException {
Integer value = getOptionalInt(index);
if (value == null) throw new FormatException();
return value;
}
@Nullable
public Integer getOptionalInt(int index) throws FormatException {
Long value = getOptionalLong(index);
if (value == null) return null;
if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
throw new FormatException();
}
return value.intValue();
}
public Integer getInt(int index, Integer defaultValue)
throws FormatException {
Integer value = getOptionalInt(index);
return value == null ? defaultValue : value;
}
public Double getDouble(int index) throws FormatException {
if (!isInRange(index)) throw new FormatException();
Object o = get(index);
if (o instanceof Double) return (Double) o;
if (o instanceof Float) return ((Float) o).doubleValue();
throw new FormatException();
Double value = getOptionalDouble(index);
if (value == null) throw new FormatException();
return value;
}
@Nullable
@@ -110,19 +120,16 @@ public class BdfList extends ArrayList<Object> {
throw new FormatException();
}
public Double getDouble(int index, Double defaultValue) {
if (!isInRange(index)) return defaultValue;
Object o = get(index);
if (o instanceof Double) return (Double) o;
if (o instanceof Float) return ((Float) o).doubleValue();
return defaultValue;
public Double getDouble(int index, Double defaultValue)
throws FormatException {
Double value = getOptionalDouble(index);
return value == null ? defaultValue : value;
}
public String getString(int index) throws FormatException {
if (!isInRange(index)) throw new FormatException();
Object o = get(index);
if (o instanceof String) return (String) o;
throw new FormatException();
String value = getOptionalString(index);
if (value == null) throw new FormatException();
return value;
}
@Nullable
@@ -134,19 +141,16 @@ public class BdfList extends ArrayList<Object> {
throw new FormatException();
}
public String getString(int index, String defaultValue) {
if (!isInRange(index)) return defaultValue;
Object o = get(index);
if (o instanceof String) return (String) o;
return defaultValue;
public String getString(int index, String defaultValue)
throws FormatException {
String value = getOptionalString(index);
return value == null ? defaultValue : value;
}
public byte[] getRaw(int index) throws FormatException {
if (!isInRange(index)) throw new FormatException();
Object o = get(index);
if (o instanceof byte[]) return (byte[]) o;
if (o instanceof Bytes) return ((Bytes) o).getBytes();
throw new FormatException();
byte[] value = getOptionalRaw(index);
if (value == null) throw new FormatException();
return value;
}
@Nullable
@@ -159,19 +163,16 @@ public class BdfList extends ArrayList<Object> {
throw new FormatException();
}
public byte[] getRaw(int index, byte[] defaultValue) {
if (!isInRange(index)) return defaultValue;
Object o = get(index);
if (o instanceof byte[]) return (byte[]) o;
if (o instanceof Bytes) return ((Bytes) o).getBytes();
return defaultValue;
public byte[] getRaw(int index, byte[] defaultValue)
throws FormatException {
byte[] value = getOptionalRaw(index);
return value == null ? defaultValue : value;
}
public BdfList getList(int index) throws FormatException {
if (!isInRange(index)) throw new FormatException();
Object o = get(index);
if (o instanceof BdfList) return (BdfList) o;
throw new FormatException();
BdfList value = getOptionalList(index);
if (value == null) throw new FormatException();
return value;
}
@Nullable
@@ -183,18 +184,16 @@ public class BdfList extends ArrayList<Object> {
throw new FormatException();
}
public BdfList getList(int index, BdfList defaultValue) {
if (!isInRange(index)) return defaultValue;
Object o = get(index);
if (o instanceof BdfList) return (BdfList) o;
return defaultValue;
public BdfList getList(int index, BdfList defaultValue)
throws FormatException {
BdfList value = getOptionalList(index);
return value == null ? defaultValue : value;
}
public BdfDictionary getDictionary(int index) throws FormatException {
if (!isInRange(index)) throw new FormatException();
Object o = get(index);
if (o instanceof BdfDictionary) return (BdfDictionary) o;
throw new FormatException();
BdfDictionary value = getOptionalDictionary(index);
if (value == null) throw new FormatException();
return value;
}
@Nullable
@@ -207,10 +206,9 @@ public class BdfList extends ArrayList<Object> {
throw new FormatException();
}
public BdfDictionary getDictionary(int index, BdfDictionary defaultValue) {
if (!isInRange(index)) return defaultValue;
Object o = get(index);
if (o instanceof BdfDictionary) return (BdfDictionary) o;
return defaultValue;
public BdfDictionary getDictionary(int index, BdfDictionary defaultValue)
throws FormatException {
BdfDictionary value = getOptionalDictionary(index);
return value == null ? defaultValue : value;
}
}

View File

@@ -32,6 +32,12 @@ public interface BdfReader {
void skipLong() throws IOException;
boolean hasInt() throws IOException;
int readInt() throws IOException;
void skipInt() throws IOException;
boolean hasDouble() throws IOException;
double readDouble() throws IOException;
@@ -54,23 +60,11 @@ public interface BdfReader {
BdfList readList() throws IOException;
void readListStart() throws IOException;
boolean hasListEnd() throws IOException;
void readListEnd() throws IOException;
void skipList() throws IOException;
boolean hasDictionary() throws IOException;
BdfDictionary readDictionary() throws IOException;
void readDictionaryStart() throws IOException;
boolean hasDictionaryEnd() throws IOException;
void readDictionaryEnd() throws IOException;
void skipDictionary() throws IOException;
}

View File

@@ -9,6 +9,8 @@ public interface BdfReaderFactory {
BdfReader createReader(InputStream in);
BdfReader createReader(InputStream in, boolean canonical);
BdfReader createReader(InputStream in, int nestedLimit,
int maxBufferSize);
int maxBufferSize, boolean canonical);
}

View File

@@ -24,13 +24,5 @@ public interface BdfWriter {
void writeList(Collection<?> c) throws IOException;
void writeListStart() throws IOException;
void writeListEnd() throws IOException;
void writeDictionary(Map<?, ?> m) throws IOException;
void writeDictionaryStart() throws IOException;
void writeDictionaryEnd() throws IOException;
}

View File

@@ -1,18 +1,26 @@
package org.briarproject.bramble.api.keyagreement;
public interface KeyAgreementConstants {
import org.briarproject.bramble.api.mailbox.MailboxConstants;
/**
* The version of the BQP protocol used in beta releases. This version
* number is reserved.
*/
byte BETA_PROTOCOL_VERSION = 89;
public interface KeyAgreementConstants {
/**
* The current version of the BQP protocol.
*/
byte PROTOCOL_VERSION = 4;
/**
* The QR code format identifier, used to distinguish BQP QR codes from
* QR codes used for other purposes. See
* {@link MailboxConstants#QR_FORMAT_ID}.
*/
byte QR_FORMAT_ID = 0;
/**
* The QR code format version.
*/
byte QR_FORMAT_VERSION = PROTOCOL_VERSION;
/**
* The length of the BQP key commitment in bytes.
*/

View File

@@ -7,5 +7,5 @@ import java.io.IOException;
@NotNullByDefault
public interface PayloadParser {
Payload parse(byte[] raw) throws IOException;
Payload parse(String payload) throws IOException;
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConstants;
import org.briarproject.bramble.api.plugin.TransportId;
import java.util.List;
@@ -19,6 +20,18 @@ public interface MailboxConstants {
*/
TransportId ID = new TransportId("org.briarproject.bramble.mailbox");
/**
* The QR code format identifier, used to distinguish mailbox QR codes
* from QR codes used for other purposes. See
* {@link KeyAgreementConstants#QR_FORMAT_ID};
*/
byte QR_FORMAT_ID = 1;
/**
* The QR code format version.
*/
byte QR_FORMAT_VERSION = 0;
/**
* Mailbox API versions that we support as a client. This is reported to our
* contacts by {@link MailboxUpdateManager}.

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
@@ -34,6 +35,16 @@ public interface MailboxManager {
*/
MailboxPairingTask startPairingTask(String qrCodePayload);
/**
* Takes a textual QR code representation in
* {@link org.briarproject.bramble.util.Base32} format and converts it
* into a qrCodePayload as expected by {@link #startPairingTask(String)}.
*
* @throws FormatException when the provided payload did not include a
* proper briar-mailbox:// link.
*/
String convertBase32Payload(String base32Payload) throws FormatException;
/**
* Can be used by the UI to test the mailbox connection.
*

View File

@@ -1,17 +1,44 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
public abstract class MailboxPairingState {
public static class QrCodeReceived extends MailboxPairingState {
public abstract static class Pending extends MailboxPairingState {
public final long timeStarted;
private Pending(long timeStarted) {
this.timeStarted = timeStarted;
}
}
public static class Pairing extends MailboxPairingState {
public static class QrCodeReceived extends Pending {
public QrCodeReceived(long timeStarted) {
super(timeStarted);
}
}
public static class Pairing extends Pending {
public Pairing(long timeStarted) {
super(timeStarted);
}
}
public static class Paired extends MailboxPairingState {
}
public static class InvalidQrCode extends MailboxPairingState {
public final QrCodeType qrCodeType;
public final int formatVersion;
public InvalidQrCode(QrCodeType qrCodeType, int formatVersion) {
this.qrCodeType = qrCodeType;
this.formatVersion = formatVersion;
}
}
public static class MailboxAlreadyPaired extends MailboxPairingState {

View File

@@ -27,8 +27,6 @@ public interface TorConstants {
int PREF_TOR_NETWORK_AUTOMATIC = 0;
int PREF_TOR_NETWORK_WITHOUT_BRIDGES = 1;
int PREF_TOR_NETWORK_WITH_BRIDGES = 2;
// TODO: Remove when settings migration code is removed
int PREF_TOR_NETWORK_NEVER = 3;
// Default values for local settings
boolean DEFAULT_PREF_PLUGIN_ENABLE = true;

View File

@@ -0,0 +1,16 @@
package org.briarproject.bramble.api.qrcode;
import org.briarproject.bramble.api.Pair;
import org.briarproject.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface QrCodeClassifier {
enum QrCodeType {
BQP,
MAILBOX,
UNKNOWN
}
Pair<QrCodeType, Integer> classifyQrCode(String payload);
}

View File

@@ -0,0 +1,25 @@
package org.briarproject.bramble.api.qrcode;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* Thrown when a QR code that has been scanned does not have the expected type.
*/
@Immutable
@NotNullByDefault
public class WrongQrCodeTypeException extends FormatException {
private final QrCodeType qrCodeType;
public WrongQrCodeTypeException(QrCodeType qrCodeType) {
this.qrCodeType = qrCodeType;
}
public QrCodeType getQrCodeType() {
return qrCodeType;
}
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.util;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
@@ -15,15 +14,21 @@ import java.util.regex.Pattern;
import javax.annotation.Nullable;
import static java.nio.charset.CodingErrorAction.IGNORE;
import static java.nio.charset.CodingErrorAction.REPORT;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
@SuppressWarnings("CharsetObjectCanBeUsed")
@NotNullByDefault
public class StringUtils {
private static final Charset UTF_8 = Charset.forName("UTF-8");
private static Pattern MAC = Pattern.compile("[0-9a-f]{2}:[0-9a-f]{2}:" +
"[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}",
CASE_INSENSITIVE);
public static final Charset UTF_8 = Charset.forName("UTF-8");
public static final Charset US_ASCII = Charset.forName("US-ASCII");
public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
private static final Pattern MAC =
Pattern.compile("[0-9a-f]{2}:[0-9a-f]{2}:" +
"[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}",
CASE_INSENSITIVE);
private static final char[] HEX = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7',
@@ -45,33 +50,41 @@ public class StringUtils {
}
public static byte[] toUtf8(String s) {
try {
return s.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
return s.getBytes(UTF_8);
}
public static String fromUtf8(byte[] bytes) {
return fromUtf8(bytes, 0, bytes.length);
public static String fromUtf8(byte[] bytes) throws FormatException {
return fromUtf8(bytes, 0, bytes.length, true);
}
public static String fromUtf8(byte[] bytes, int off, int len) {
public static String fromUtf8(byte[] bytes, int off, int len)
throws FormatException {
return fromUtf8(bytes, off, len, true);
}
private static String fromUtf8(byte[] bytes, int off, int len,
boolean strict) throws FormatException {
CharsetDecoder decoder = UTF_8.newDecoder();
decoder.onMalformedInput(IGNORE);
decoder.onUnmappableCharacter(IGNORE);
decoder.onMalformedInput(strict ? REPORT : IGNORE);
decoder.onUnmappableCharacter(strict ? REPORT : IGNORE);
ByteBuffer buffer = ByteBuffer.wrap(bytes, off, len);
try {
return decoder.decode(buffer).toString();
} catch (CharacterCodingException e) {
throw new AssertionError(e);
throw new FormatException();
}
}
public static String truncateUtf8(String s, int maxUtf8Length) {
byte[] utf8 = toUtf8(s);
if (utf8.length <= maxUtf8Length) return s;
return fromUtf8(utf8, 0, maxUtf8Length);
// Don't be strict when converting back, so that if we truncate a
// multi-byte character the whole character gets dropped
try {
return fromUtf8(utf8, 0, maxUtf8Length, false);
} catch (FormatException e) {
throw new AssertionError(e);
}
}
/**

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.api.data;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
@@ -8,9 +9,12 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.Map.Entry;
import static java.lang.Boolean.TRUE;
import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class BdfDictionaryTest extends BrambleTestCase {
@@ -19,20 +23,20 @@ public class BdfDictionaryTest extends BrambleTestCase {
public void testConstructors() {
assertEquals(Collections.<String, Object>emptyMap(),
new BdfDictionary());
assertEquals(Collections.singletonMap("foo", NULL_VALUE),
new BdfDictionary(Collections.singletonMap("foo", NULL_VALUE)));
assertEquals(singletonMap("foo", NULL_VALUE),
new BdfDictionary(singletonMap("foo", NULL_VALUE)));
}
@Test
public void testFactoryMethod() {
assertEquals(Collections.<String, Object>emptyMap(),
BdfDictionary.of());
assertEquals(Collections.singletonMap("foo", NULL_VALUE),
assertEquals(singletonMap("foo", NULL_VALUE),
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)));
}
@Test
public void testIntegerPromotion() throws Exception {
public void testLongPromotion() throws Exception {
BdfDictionary d = new BdfDictionary();
d.put("foo", (byte) 1);
d.put("bar", (short) 2);
@@ -44,6 +48,33 @@ public class BdfDictionaryTest extends BrambleTestCase {
assertEquals(Long.valueOf(4), d.getLong("bam"));
}
@Test
public void testIntPromotionAndDemotion() throws Exception {
BdfDictionary d = new BdfDictionary();
d.put("foo", (byte) 1);
d.put("bar", (short) 2);
d.put("baz", 3);
d.put("bam", 4L);
assertEquals(Integer.valueOf(1), d.getInt("foo"));
assertEquals(Integer.valueOf(2), d.getInt("bar"));
assertEquals(Integer.valueOf(3), d.getInt("baz"));
assertEquals(Integer.valueOf(4), d.getInt("bam"));
}
@Test(expected = FormatException.class)
public void testIntUnderflow() throws Exception {
BdfDictionary d =
BdfDictionary.of(new BdfEntry("foo", Integer.MIN_VALUE - 1L));
d.getInt("foo");
}
@Test(expected = FormatException.class)
public void testIntOverflow() throws Exception {
BdfDictionary d =
BdfDictionary.of(new BdfEntry("foo", Integer.MAX_VALUE + 1L));
d.getInt("foo");
}
@Test
public void testFloatPromotion() throws Exception {
BdfDictionary d = new BdfDictionary();
@@ -67,7 +98,7 @@ public class BdfDictionaryTest extends BrambleTestCase {
}
@Test
public void testKeySetIteratorIsOrderedByKeys() throws Exception {
public void testKeySetIteratorIsOrderedByKeys() {
BdfDictionary d = new BdfDictionary();
d.put("a", 1);
d.put("d", 4);
@@ -86,7 +117,7 @@ public class BdfDictionaryTest extends BrambleTestCase {
}
@Test
public void testValuesIteratorIsOrderedByKeys() throws Exception {
public void testValuesIteratorIsOrderedByKeys() {
BdfDictionary d = new BdfDictionary();
d.put("a", 1);
d.put("d", 4);
@@ -105,7 +136,7 @@ public class BdfDictionaryTest extends BrambleTestCase {
}
@Test
public void testEntrySetIteratorIsOrderedByKeys() throws Exception {
public void testEntrySetIteratorIsOrderedByKeys() {
BdfDictionary d = new BdfDictionary();
d.put("a", 1);
d.put("d", 4);
@@ -130,4 +161,284 @@ public class BdfDictionaryTest extends BrambleTestCase {
assertEquals("d", e.getKey());
assertEquals(4, e.getValue());
}
@Test(expected = FormatException.class)
public void testMissingValueForBooleanThrowsFormatException()
throws Exception {
new BdfDictionary().getBoolean("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForLongThrowsFormatException()
throws Exception {
new BdfDictionary().getLong("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForIntThrowsFormatException() throws Exception {
new BdfDictionary().getInt("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForDoubleThrowsFormatException()
throws Exception {
new BdfDictionary().getDouble("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForStringThrowsFormatException()
throws Exception {
new BdfDictionary().getString("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForRawThrowsFormatException() throws Exception {
new BdfDictionary().getRaw("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForListThrowsFormatException()
throws Exception {
new BdfDictionary().getList("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForDictionaryThrowsFormatException()
throws Exception {
new BdfDictionary().getDictionary("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForBooleanThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getBoolean("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForLongThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getLong("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForIntThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getInt("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForDoubleThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getDouble("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForStringThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getString("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForRawThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getRaw("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForListThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getList("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForDictionaryThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getDictionary("foo");
}
@Test
public void testOptionalMethodsReturnNullForMissingValue()
throws Exception {
testOptionalMethodsReturnNull(new BdfDictionary());
}
@Test
public void testOptionalMethodsReturnNullForNullValue() throws Exception {
BdfDictionary d = BdfDictionary.of(new BdfEntry("foo", NULL_VALUE));
testOptionalMethodsReturnNull(d);
}
private void testOptionalMethodsReturnNull(BdfDictionary d)
throws Exception {
assertNull(d.getOptionalBoolean("foo"));
assertNull(d.getOptionalLong("foo"));
assertNull(d.getOptionalInt("foo"));
assertNull(d.getOptionalDouble("foo"));
assertNull(d.getOptionalString("foo"));
assertNull(d.getOptionalRaw("foo"));
assertNull(d.getOptionalList("foo"));
assertNull(d.getOptionalDictionary("foo"));
}
@Test
public void testDefaultMethodsReturnDefaultForMissingValue()
throws Exception {
testDefaultMethodsReturnDefault(new BdfDictionary());
}
@Test
public void testDefaultMethodsReturnDefaultForNullValue() throws Exception {
BdfDictionary d = BdfDictionary.of(new BdfEntry("foo", NULL_VALUE));
testDefaultMethodsReturnDefault(d);
}
private void testDefaultMethodsReturnDefault(BdfDictionary d)
throws Exception {
assertEquals(TRUE, d.getBoolean("foo", TRUE));
assertEquals(Long.valueOf(123L), d.getLong("foo", 123L));
assertEquals(Integer.valueOf(123), d.getInt("foo", 123));
assertEquals(Double.valueOf(123D), d.getDouble("foo", 123D));
assertEquals("123", d.getString("foo", "123"));
byte[] defaultRaw = {1, 2, 3};
assertArrayEquals(defaultRaw, d.getRaw("foo", defaultRaw));
BdfList defaultList = BdfList.of(1, 2, 3);
assertEquals(defaultList, d.getList("foo", defaultList));
BdfDictionary defaultDict = BdfDictionary.of(new BdfEntry("123", 123));
assertEquals(defaultDict, d.getDictionary("foo", defaultDict));
}
@Test(expected = FormatException.class)
public void testWrongTypeForBooleanThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getBoolean("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalBooleanThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalBoolean("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultBooleanThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getBoolean("foo", true);
}
@Test(expected = FormatException.class)
public void testWrongTypeForLongThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getLong("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalLongThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getOptionalLong("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultLongThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getLong("foo", 1L);
}
@Test(expected = FormatException.class)
public void testWrongTypeForIntThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getInt("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalIntThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getOptionalInt("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultIntThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getInt("foo", 1);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDoubleThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getDouble("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalDoubleThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalDouble("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultDoubleThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getDouble("foo", 1D);
}
@Test(expected = FormatException.class)
public void testWrongTypeForStringThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getString("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalStringThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalString("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultStringThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getString("foo", "");
}
@Test(expected = FormatException.class)
public void testWrongTypeForRawThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getRaw("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalRawThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalRaw("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultRawThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getRaw("foo", new byte[0]);
}
@Test(expected = FormatException.class)
public void testWrongTypeForListThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getList("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalListThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalList("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultListThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getList("foo",
new BdfList());
}
@Test(expected = FormatException.class)
public void testWrongTypeForDictionaryThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getDictionary("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalDictionaryThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalDictionary("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultDictionaryThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getDictionary("foo",
new BdfDictionary());
}
}

View File

@@ -5,31 +5,31 @@ import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import static java.lang.Boolean.TRUE;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class BdfListTest extends BrambleTestCase {
@Test
public void testConstructors() {
assertEquals(Collections.emptyList(), new BdfList());
assertEquals(Arrays.asList(1, 2, NULL_VALUE),
new BdfList(Arrays.asList(1, 2, NULL_VALUE)));
assertEquals(emptyList(), new BdfList());
assertEquals(asList(1, 2, NULL_VALUE),
new BdfList(asList(1, 2, NULL_VALUE)));
}
@Test
public void testFactoryMethod() {
assertEquals(Collections.emptyList(), BdfList.of());
assertEquals(Arrays.asList(1, 2, NULL_VALUE),
BdfList.of(1, 2, NULL_VALUE));
assertEquals(emptyList(), BdfList.of());
assertEquals(asList(1, 2, NULL_VALUE), BdfList.of(1, 2, NULL_VALUE));
}
@Test
public void testIntegerPromotion() throws Exception {
public void testLongPromotion() throws Exception {
BdfList list = new BdfList();
list.add((byte) 1);
list.add((short) 2);
@@ -41,6 +41,31 @@ public class BdfListTest extends BrambleTestCase {
assertEquals(Long.valueOf(4), list.getLong(3));
}
@Test
public void testIntPromotionAndDemotion() throws Exception {
BdfList list = new BdfList();
list.add((byte) 1);
list.add((short) 2);
list.add(3);
list.add(4L);
assertEquals(Integer.valueOf(1), list.getInt(0));
assertEquals(Integer.valueOf(2), list.getInt(1));
assertEquals(Integer.valueOf(3), list.getInt(2));
assertEquals(Integer.valueOf(4), list.getInt(3));
}
@Test(expected = FormatException.class)
public void testIntUnderflow() throws Exception {
BdfList list = BdfList.of(Integer.MIN_VALUE - 1L);
list.getInt(0);
}
@Test(expected = FormatException.class)
public void testIntOverflow() throws Exception {
BdfList list = BdfList.of(Integer.MAX_VALUE + 1L);
list.getInt(0);
}
@Test
public void testFloatPromotion() throws Exception {
BdfList list = new BdfList();
@@ -63,61 +88,6 @@ public class BdfListTest extends BrambleTestCase {
assertArrayEquals(new byte[123], second);
}
@Test
@SuppressWarnings("ConstantConditions")
public void testIndexOutOfBoundsReturnsDefaultValue() throws Exception {
BdfList list = BdfList.of(1, 2, 3);
boolean defaultBoolean = true;
assertEquals(defaultBoolean, list.getBoolean(-1, defaultBoolean));
assertEquals(defaultBoolean, list.getBoolean(3, defaultBoolean));
Long defaultLong = 123L;
assertEquals(defaultLong, list.getLong(-1, defaultLong));
assertEquals(defaultLong, list.getLong(3, defaultLong));
Double defaultDouble = 1.23;
assertEquals(defaultDouble, list.getDouble(-1, defaultDouble));
assertEquals(defaultDouble, list.getDouble(3, defaultDouble));
String defaultString = "123";
assertEquals(defaultString, list.getString(-1, defaultString));
assertEquals(defaultString, list.getString(3, defaultString));
byte[] defaultBytes = new byte[] {1, 2, 3};
assertArrayEquals(defaultBytes, list.getRaw(-1, defaultBytes));
assertArrayEquals(defaultBytes, list.getRaw(3, defaultBytes));
BdfList defaultList = BdfList.of(1, 2, 3);
assertEquals(defaultList, list.getList(-1, defaultList));
assertEquals(defaultList, list.getList(3, defaultList));
BdfDictionary defaultDict = BdfDictionary.of(
new BdfEntry("1", 1),
new BdfEntry("2", 2),
new BdfEntry("3", 3)
);
assertEquals(defaultDict, list.getDictionary(-1, defaultDict));
assertEquals(defaultDict, list.getDictionary(3, defaultDict));
}
@Test
@SuppressWarnings("ConstantConditions")
public void testWrongTypeReturnsDefaultValue() throws Exception {
BdfList list = BdfList.of(1, 2, 3, true);
boolean defaultBoolean = true;
assertEquals(defaultBoolean, list.getBoolean(0, defaultBoolean));
Long defaultLong = 123L;
assertEquals(defaultLong, list.getLong(3, defaultLong));
Double defaultDouble = 1.23;
assertEquals(defaultDouble, list.getDouble(0, defaultDouble));
String defaultString = "123";
assertEquals(defaultString, list.getString(0, defaultString));
byte[] defaultBytes = new byte[] {1, 2, 3};
assertArrayEquals(defaultBytes, list.getRaw(0, defaultBytes));
BdfList defaultList = BdfList.of(1, 2, 3);
assertEquals(defaultList, list.getList(0, defaultList));
BdfDictionary defaultDict = BdfDictionary.of(
new BdfEntry("1", 1),
new BdfEntry("2", 2),
new BdfEntry("3", 3)
);
assertEquals(defaultDict, list.getDictionary(0, defaultDict));
}
@Test(expected = FormatException.class)
public void testNegativeIndexForBooleanThrowsFormatException()
throws Exception {
@@ -130,6 +100,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalBoolean(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultBooleanThrowsFormatException()
throws Exception {
new BdfList().getBoolean(-1, true);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForLongThrowsFormatException()
throws Exception {
@@ -142,6 +118,30 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalLong(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultLongThrowsFormatException()
throws Exception {
new BdfList().getLong(-1, 1L);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForIntThrowsFormatException()
throws Exception {
new BdfList().getInt(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForOptionalIntThrowsFormatException()
throws Exception {
new BdfList().getOptionalInt(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultIntThrowsFormatException()
throws Exception {
new BdfList().getInt(-1, 1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDoubleThrowsFormatException()
throws Exception {
@@ -154,6 +154,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalDouble(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultDoubleThrowsFormatException()
throws Exception {
new BdfList().getDouble(-1, 1D);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForStringThrowsFormatException()
throws Exception {
@@ -166,6 +172,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalString(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultStringThrowsFormatException()
throws Exception {
new BdfList().getString(-1, "");
}
@Test(expected = FormatException.class)
public void testNegativeIndexForRawThrowsFormatException()
throws Exception {
@@ -178,6 +190,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalRaw(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultRawThrowsFormatException()
throws Exception {
new BdfList().getRaw(-1, new byte[0]);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForListThrowsFormatException()
throws Exception {
@@ -190,6 +208,11 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalList(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultListThrowsFormatException()
throws Exception {
new BdfList().getList(-1, new BdfList());
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDictionaryThrowsFormatException()
@@ -203,6 +226,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalDictionary(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultDictionaryThrowsFormatException()
throws Exception {
new BdfList().getDictionary(-1, new BdfDictionary());
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForBooleanThrowsFormatException()
throws Exception {
@@ -215,6 +244,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalBoolean(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultBooleanThrowsFormatException()
throws Exception {
new BdfList().getBoolean(0, true);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForLongThrowsFormatException()
throws Exception {
@@ -227,6 +262,30 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalLong(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultLongThrowsFormatException()
throws Exception {
new BdfList().getLong(0, 1L);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForIntThrowsFormatException()
throws Exception {
new BdfList().getInt(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForOptionalIntThrowsFormatException()
throws Exception {
new BdfList().getOptionalInt(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultIntThrowsFormatException()
throws Exception {
new BdfList().getInt(0, 1);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDoubleThrowsFormatException()
throws Exception {
@@ -239,6 +298,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalDouble(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultDoubleThrowsFormatException()
throws Exception {
new BdfList().getDouble(0, 1D);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForStringThrowsFormatException()
throws Exception {
@@ -251,6 +316,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalString(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultStringThrowsFormatException()
throws Exception {
new BdfList().getString(0, "");
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForRawThrowsFormatException()
throws Exception {
@@ -263,6 +334,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalRaw(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultRawThrowsFormatException()
throws Exception {
new BdfList().getRaw(0, new byte[0]);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForListThrowsFormatException()
throws Exception {
@@ -275,6 +352,11 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalList(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultListThrowsFormatException()
throws Exception {
new BdfList().getList(0, new BdfList());
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDictionaryThrowsFormatException()
@@ -287,6 +369,13 @@ public class BdfListTest extends BrambleTestCase {
throws Exception {
new BdfList().getOptionalDictionary(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultDictionaryThrowsFormatException()
throws Exception {
new BdfList().getDictionary(0, new BdfDictionary());
}
@Test(expected = FormatException.class)
public void testWrongTypeForBooleanThrowsFormatException()
throws Exception {
@@ -299,6 +388,12 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(123).getOptionalBoolean(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultBooleanThrowsFormatException()
throws Exception {
BdfList.of(123).getBoolean(0, true);
}
@Test(expected = FormatException.class)
public void testWrongTypeForLongThrowsFormatException() throws Exception {
BdfList.of(1.23).getLong(0);
@@ -310,6 +405,29 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(1.23).getOptionalLong(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultLongThrowsFormatException()
throws Exception {
BdfList.of(1.23).getLong(0, 1L);
}
@Test(expected = FormatException.class)
public void testWrongTypeForIntThrowsFormatException() throws Exception {
BdfList.of(1.23).getInt(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalIntThrowsFormatException()
throws Exception {
BdfList.of(1.23).getOptionalInt(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultIntThrowsFormatException()
throws Exception {
BdfList.of(1.23).getInt(0, 1);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDoubleThrowsFormatException() throws Exception {
BdfList.of(123).getDouble(0);
@@ -321,6 +439,12 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(123).getOptionalDouble(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultDoubleThrowsFormatException()
throws Exception {
BdfList.of(123).getDouble(0, 1D);
}
@Test(expected = FormatException.class)
public void testWrongTypeForStringThrowsFormatException() throws Exception {
BdfList.of(123).getString(0);
@@ -332,6 +456,12 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(123).getOptionalString(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultStringThrowsFormatException()
throws Exception {
BdfList.of(123).getString(0, "");
}
@Test(expected = FormatException.class)
public void testWrongTypeForRawThrowsFormatException() throws Exception {
BdfList.of(123).getRaw(0);
@@ -343,6 +473,12 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(123).getOptionalRaw(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultRawThrowsFormatException()
throws Exception {
BdfList.of(123).getRaw(0, new byte[0]);
}
@Test(expected = FormatException.class)
public void testWrongTypeForListThrowsFormatException() throws Exception {
BdfList.of(123).getList(0);
@@ -354,6 +490,11 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(123).getOptionalList(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultListThrowsFormatException()
throws Exception {
BdfList.of(123).getList(0, new BdfList());
}
@Test(expected = FormatException.class)
public void testWrongTypeForDictionaryThrowsFormatException()
@@ -366,4 +507,81 @@ public class BdfListTest extends BrambleTestCase {
throws Exception {
BdfList.of(123).getOptionalDictionary(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultDictionaryThrowsFormatException()
throws Exception {
BdfList.of(123).getDictionary(0, new BdfDictionary());
}
@Test(expected = FormatException.class)
public void testNullValueForBooleanThrowsFormatException()
throws Exception {
BdfList.of(NULL_VALUE).getBoolean(0);
}
@Test(expected = FormatException.class)
public void testNullValueForLongThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getLong(0);
}
@Test(expected = FormatException.class)
public void testNullValueForIntThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getInt(0);
}
@Test(expected = FormatException.class)
public void testNullValueForDoubleThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getDouble(0);
}
@Test(expected = FormatException.class)
public void testNullValueForStringThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getString(0);
}
@Test(expected = FormatException.class)
public void testNullValueForRawThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getRaw(0);
}
@Test(expected = FormatException.class)
public void testNullValueForListThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getList(0);
}
@Test(expected = FormatException.class)
public void testNullValueForDictionaryThrowsFormatException()
throws Exception {
BdfList.of(NULL_VALUE).getDictionary(0);
}
@Test
public void testOptionalMethodsReturnNullForNullValue() throws Exception {
BdfList list = BdfList.of(NULL_VALUE);
assertNull(list.getOptionalBoolean(0));
assertNull(list.getOptionalLong(0));
assertNull(list.getOptionalInt(0));
assertNull(list.getOptionalDouble(0));
assertNull(list.getOptionalString(0));
assertNull(list.getOptionalRaw(0));
assertNull(list.getOptionalList(0));
assertNull(list.getOptionalDictionary(0));
}
@Test
public void testDefaultMethodsReturnDefaultForNullValue() throws Exception {
BdfList list = BdfList.of(NULL_VALUE);
assertEquals(TRUE, list.getBoolean(0, TRUE));
assertEquals(Long.valueOf(123L), list.getLong(0, 123L));
assertEquals(Integer.valueOf(123), list.getInt(0, 123));
assertEquals(Double.valueOf(123D), list.getDouble(0, 123D));
assertEquals("123", list.getString(0, "123"));
byte[] defaultRaw = {1, 2, 3};
assertArrayEquals(defaultRaw, list.getRaw(0, defaultRaw));
BdfList defaultList = BdfList.of(1, 2, 3);
assertEquals(defaultList, list.getList(0, defaultList));
BdfDictionary defaultDict = BdfDictionary.of(new BdfEntry("123", 123));
assertEquals(defaultDict, list.getDictionary(0, defaultDict));
}
}

View File

@@ -33,7 +33,7 @@ dependencies {
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-imposters:$jmock_version"
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.3"
testImplementation "com.squareup.okhttp3:mockwebserver:$mockwebserver_version"
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"

View File

@@ -17,6 +17,7 @@ import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.mailbox.MailboxModule;
import org.briarproject.bramble.plugin.PluginModule;
import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.qrcode.QrCodeModule;
import org.briarproject.bramble.record.RecordModule;
import org.briarproject.bramble.reliability.ReliabilityModule;
import org.briarproject.bramble.rendezvous.RendezvousModule;
@@ -47,6 +48,7 @@ import dagger.Module;
MailboxModule.class,
PluginModule.class,
PropertiesModule.class,
QrCodeModule.class,
RecordModule.class,
ReliabilityModule.class,
RendezvousModule.class,

View File

@@ -19,7 +19,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.logging.Logger;
import javax.annotation.Nullable;
@@ -29,6 +28,7 @@ import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@@ -99,7 +99,7 @@ class AccountManagerImpl implements AccountManager {
}
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), Charset.forName("UTF-8")));
new FileInputStream(f), UTF_8));
String key = reader.readLine();
reader.close();
return key;
@@ -151,7 +151,7 @@ class AccountManagerImpl implements AccountManager {
@GuardedBy("stateChangeLock")
private void writeDbKeyToFile(String key, File f) throws IOException {
FileOutputStream out = new FileOutputStream(f);
out.write(key.getBytes(Charset.forName("UTF-8")));
out.write(key.getBytes(UTF_8));
out.flush();
out.close();
}

View File

@@ -155,7 +155,13 @@ class ClientHelperImpl implements ClientHelper {
@Override
public BdfList getMessageAsList(Transaction txn, MessageId m)
throws DbException, FormatException {
return toList(db.getMessage(txn, m).getBody());
return getMessageAsList(txn, m, true);
}
@Override
public BdfList getMessageAsList(Transaction txn, MessageId m,
boolean canonical) throws DbException, FormatException {
return toList(db.getMessage(txn, m), canonical);
}
@Override
@@ -313,8 +319,13 @@ class ClientHelperImpl implements ClientHelper {
@Override
public BdfList toList(byte[] b, int off, int len) throws FormatException {
return toList(b, off, len, true);
}
private BdfList toList(byte[] b, int off, int len, boolean canonical)
throws FormatException {
ByteArrayInputStream in = new ByteArrayInputStream(b, off, len);
BdfReader reader = bdfReaderFactory.createReader(in);
BdfReader reader = bdfReaderFactory.createReader(in, canonical);
try {
BdfList list = reader.readList();
if (!reader.eof()) throw new FormatException();
@@ -328,7 +339,7 @@ class ClientHelperImpl implements ClientHelper {
@Override
public BdfList toList(byte[] b) throws FormatException {
return toList(b, 0, b.length);
return toList(b, 0, b.length, true);
}
@Override
@@ -336,6 +347,12 @@ class ClientHelperImpl implements ClientHelper {
return toList(m.getBody());
}
@Override
public BdfList toList(Message m, boolean canonical) throws FormatException {
byte[] b = m.getBody();
return toList(b, 0, b.length, canonical);
}
@Override
public BdfList toList(Author a) {
return BdfList.of(a.getFormatVersion(), a.getName(), a.getPublicKey());
@@ -361,7 +378,7 @@ class ClientHelperImpl implements ClientHelper {
public Author parseAndValidateAuthor(BdfList author)
throws FormatException {
checkSize(author, 3);
int formatVersion = author.getLong(0).intValue();
int formatVersion = author.getInt(0);
if (formatVersion != FORMAT_VERSION) throw new FormatException();
String name = author.getString(1);
checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
@@ -472,8 +489,7 @@ class ClientHelperImpl implements ClientHelper {
if (element.size() != 2) {
throw new FormatException();
}
list.add(new MailboxVersion(element.getLong(0).intValue(),
element.getLong(1).intValue()));
list.add(new MailboxVersion(element.getInt(0), element.getInt(1)));
}
// Sort the list of versions for easier comparison
sort(list);
@@ -486,7 +502,7 @@ class ClientHelperImpl implements ClientHelper {
try {
BdfDictionary meta =
getGroupMetadataAsDictionary(txn, contactGroupId);
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
return new ContactId(meta.getInt(GROUP_KEY_CONTACT_ID));
} catch (FormatException e) {
throw new DbException(e); // Invalid group metadata
}

View File

@@ -29,7 +29,6 @@ import org.briarproject.nullsafety.NotNullByDefault;
import org.whispersystems.curve25519.Curve25519;
import org.whispersystems.curve25519.Curve25519KeyPair;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
@@ -51,6 +50,7 @@ import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHE
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.bramble.util.StringUtils.US_ASCII;
@NotNullByDefault
class CryptoComponentImpl implements CryptoComponent {
@@ -460,7 +460,7 @@ class CryptoComponentImpl implements CryptoComponent {
@Override
public String encodeOnion(byte[] publicKey) {
Digest digest = new SHA3Digest(256);
byte[] label = ".onion checksum".getBytes(Charset.forName("US-ASCII"));
byte[] label = ".onion checksum".getBytes(US_ASCII);
digest.update(label, 0, label.length);
digest.update(publicKey, 0, publicKey.length);
digest.update(ONION_HS_PROTOCOL_VERSION);

View File

@@ -39,12 +39,13 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.Scanner;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
@Immutable
@NotNullByDefault
public class MessageEncrypter {
@@ -228,7 +229,7 @@ public class MessageEncrypter {
PublicKey publicKey =
encrypter.getKeyParser().parsePublicKey(keyBytes);
String message = readFully(System.in);
byte[] plaintext = message.getBytes(Charset.forName("UTF-8"));
byte[] plaintext = message.getBytes(UTF_8);
byte[] ciphertext = encrypter.encrypt(publicKey, plaintext);
System.out.println(AsciiArmour.wrap(ciphertext, LINE_LENGTH));
}
@@ -242,7 +243,7 @@ public class MessageEncrypter {
encrypter.getKeyParser().parsePrivateKey(keyBytes);
byte[] ciphertext = AsciiArmour.unwrap(readFully(System.in));
byte[] plaintext = encrypter.decrypt(privateKey, ciphertext);
System.out.println(new String(plaintext, Charset.forName("UTF-8")));
System.out.println(new String(plaintext, UTF_8));
}
private static String readFully(InputStream in) throws IOException {

View File

@@ -18,12 +18,18 @@ class BdfReaderFactoryImpl implements BdfReaderFactory {
@Override
public BdfReader createReader(InputStream in) {
return new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT,
DEFAULT_MAX_BUFFER_SIZE);
DEFAULT_MAX_BUFFER_SIZE, true);
}
@Override
public BdfReader createReader(InputStream in, boolean canonical) {
return new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT,
DEFAULT_MAX_BUFFER_SIZE, canonical);
}
@Override
public BdfReader createReader(InputStream in, int nestedLimit,
int maxBufferSize) {
return new BdfReaderImpl(in, nestedLimit, maxBufferSize);
int maxBufferSize, boolean canonical) {
return new BdfReaderImpl(in, nestedLimit, maxBufferSize, canonical);
}
}

View File

@@ -33,21 +33,24 @@ import static org.briarproject.bramble.util.StringUtils.fromUtf8;
@NotThreadSafe
@NotNullByDefault
class BdfReaderImpl implements BdfReader {
final class BdfReaderImpl implements BdfReader {
private static final byte[] EMPTY_BUFFER = new byte[0];
private final InputStream in;
private final int nestedLimit, maxBufferSize;
private final boolean canonical;
private boolean hasLookahead = false, eof = false;
private byte next;
private byte[] buf = new byte[8];
BdfReaderImpl(InputStream in, int nestedLimit, int maxBufferSize) {
BdfReaderImpl(InputStream in, int nestedLimit, int maxBufferSize,
boolean canonical) {
this.in = in;
this.nestedLimit = nestedLimit;
this.maxBufferSize = maxBufferSize;
this.canonical = canonical;
}
private void readLookahead() throws IOException {
@@ -188,13 +191,22 @@ class BdfReaderImpl implements BdfReader {
private short readInt16() throws IOException {
readIntoBuffer(2);
return (short) (((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF));
short value = (short) (((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF));
if (canonical && value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
// Value could have been encoded as an INT_8
throw new FormatException();
}
return value;
}
private int readInt32() throws IOException {
readIntoBuffer(4);
int value = 0;
for (int i = 0; i < 4; i++) value |= (buf[i] & 0xFF) << (24 - i * 8);
if (canonical && value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
// Value could have been encoded as an INT_16
throw new FormatException();
}
return value;
}
@@ -202,6 +214,11 @@ class BdfReaderImpl implements BdfReader {
readIntoBuffer(8);
long value = 0;
for (int i = 0; i < 8; i++) value |= (buf[i] & 0xFFL) << (56 - i * 8);
if (canonical && value >= Integer.MIN_VALUE &&
value <= Integer.MAX_VALUE) {
// Value could have been encoded as an INT_32
throw new FormatException();
}
return value;
}
@@ -215,6 +232,31 @@ class BdfReaderImpl implements BdfReader {
hasLookahead = false;
}
@Override
public boolean hasInt() throws IOException {
if (!hasLookahead) readLookahead();
if (eof) return false;
return next == INT_8 || next == INT_16 || next == INT_32;
}
@Override
public int readInt() throws IOException {
if (!hasInt()) throw new FormatException();
hasLookahead = false;
if (next == INT_8) return readInt8();
if (next == INT_16) return readInt16();
return readInt32();
}
@Override
public void skipInt() throws IOException {
if (!hasInt()) throw new FormatException();
if (next == INT_8) skip(1);
else if (next == INT_16) skip(2);
else skip(4);
hasLookahead = false;
}
@Override
public boolean hasDouble() throws IOException {
if (!hasLookahead) readLookahead();
@@ -323,22 +365,11 @@ class BdfReaderImpl implements BdfReader {
private BdfList readList(int level) throws IOException {
if (!hasList()) throw new FormatException();
if (level > nestedLimit) throw new FormatException();
BdfList list = new BdfList();
readListStart();
while (!hasListEnd()) list.add(readObject(level + 1));
readListEnd();
return list;
}
@Override
public void readListStart() throws IOException {
if (!hasList()) throw new FormatException();
hasLookahead = false;
}
@Override
public boolean hasListEnd() throws IOException {
return hasEnd();
BdfList list = new BdfList();
while (!hasEnd()) list.add(readObject(level + 1));
readEnd();
return list;
}
private boolean hasEnd() throws IOException {
@@ -347,11 +378,6 @@ class BdfReaderImpl implements BdfReader {
return next == END;
}
@Override
public void readListEnd() throws IOException {
readEnd();
}
private void readEnd() throws IOException {
if (!hasEnd()) throw new FormatException();
hasLookahead = false;
@@ -361,7 +387,7 @@ class BdfReaderImpl implements BdfReader {
public void skipList() throws IOException {
if (!hasList()) throw new FormatException();
hasLookahead = false;
while (!hasListEnd()) skipObject();
while (!hasEnd()) skipObject();
hasLookahead = false;
}
@@ -380,35 +406,27 @@ class BdfReaderImpl implements BdfReader {
private BdfDictionary readDictionary(int level) throws IOException {
if (!hasDictionary()) throw new FormatException();
if (level > nestedLimit) throw new FormatException();
BdfDictionary dictionary = new BdfDictionary();
readDictionaryStart();
while (!hasDictionaryEnd())
dictionary.put(readString(), readObject(level + 1));
readDictionaryEnd();
return dictionary;
}
@Override
public void readDictionaryStart() throws IOException {
if (!hasDictionary()) throw new FormatException();
hasLookahead = false;
}
@Override
public boolean hasDictionaryEnd() throws IOException {
return hasEnd();
}
@Override
public void readDictionaryEnd() throws IOException {
BdfDictionary dictionary = new BdfDictionary();
String prevKey = null;
while (!hasEnd()) {
String key = readString();
if (canonical && prevKey != null && key.compareTo(prevKey) <= 0) {
// Keys not unique and sorted
throw new FormatException();
}
dictionary.put(key, readObject(level + 1));
prevKey = key;
}
readEnd();
return dictionary;
}
@Override
public void skipDictionary() throws IOException {
if (!hasDictionary()) throw new FormatException();
hasLookahead = false;
while (!hasDictionaryEnd()) {
while (!hasEnd()) {
skipString();
skipObject();
}

View File

@@ -2,11 +2,13 @@ package org.briarproject.bramble.data;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfWriter;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -15,6 +17,7 @@ import java.util.Map.Entry;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import static java.util.Collections.sort;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.briarproject.bramble.data.Types.DICTIONARY;
import static org.briarproject.bramble.data.Types.END;
@@ -33,10 +36,11 @@ import static org.briarproject.bramble.data.Types.STRING_16;
import static org.briarproject.bramble.data.Types.STRING_32;
import static org.briarproject.bramble.data.Types.STRING_8;
import static org.briarproject.bramble.data.Types.TRUE;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
@NotThreadSafe
@NotNullByDefault
class BdfWriterImpl implements BdfWriter {
final class BdfWriterImpl implements BdfWriter {
private final OutputStream out;
@@ -113,7 +117,7 @@ class BdfWriterImpl implements BdfWriter {
@Override
public void writeString(String s) throws IOException {
byte[] b = s.getBytes("UTF-8");
byte[] b = s.getBytes(UTF_8);
if (b.length <= Byte.MAX_VALUE) {
out.write(STRING_8);
out.write((byte) b.length);
@@ -161,39 +165,33 @@ class BdfWriterImpl implements BdfWriter {
else if (o instanceof String) writeString((String) o);
else if (o instanceof byte[]) writeRaw((byte[]) o);
else if (o instanceof Bytes) writeRaw(((Bytes) o).getBytes());
else if (o instanceof List) writeList((List) o);
else if (o instanceof Map) writeDictionary((Map) o);
else if (o instanceof List) writeList((List<?>) o);
else if (o instanceof Map) writeDictionary((Map<?, ?>) o);
else throw new FormatException();
}
@Override
public void writeListStart() throws IOException {
out.write(LIST);
}
@Override
public void writeListEnd() throws IOException {
out.write(END);
}
@Override
public void writeDictionary(Map<?, ?> m) throws IOException {
out.write(DICTIONARY);
for (Entry<?, ?> e : m.entrySet()) {
if (!(e.getKey() instanceof String)) throw new FormatException();
writeString((String) e.getKey());
writeObject(e.getValue());
if (m instanceof BdfDictionary) {
// Entries are already sorted and keys are known to be strings
for (Entry<String, Object> e : ((BdfDictionary) m).entrySet()) {
writeString(e.getKey());
writeObject(e.getValue());
}
} else {
// Check that keys are strings, write entries in canonical order
List<String> keys = new ArrayList<>(m.size());
for (Object k : m.keySet()) {
if (!(k instanceof String)) throw new FormatException();
keys.add((String) k);
}
sort(keys);
for (String key : keys) {
writeString(key);
writeObject(m.get(key));
}
}
out.write(END);
}
@Override
public void writeDictionaryStart() throws IOException {
out.write(DICTIONARY);
}
@Override
public void writeDictionaryEnd() throws IOException {
out.write(END);
}
}

View File

@@ -1,7 +1,5 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.data.BdfReaderFactory;
import org.briarproject.bramble.api.data.BdfWriterFactory;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
import org.briarproject.bramble.api.keyagreement.PayloadParser;
@@ -19,13 +17,13 @@ public class KeyAgreementModule {
}
@Provides
PayloadEncoder providePayloadEncoder(BdfWriterFactory bdfWriterFactory) {
return new PayloadEncoderImpl(bdfWriterFactory);
PayloadEncoder providePayloadEncoder(PayloadEncoderImpl payloadEncoder) {
return payloadEncoder;
}
@Provides
PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) {
return new PayloadParserImpl(bdfReaderFactory);
PayloadParser providePayloadParser(PayloadParserImpl payloadParser) {
return payloadParser;
}
@Provides

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfWriter;
import org.briarproject.bramble.api.data.BdfWriterFactory;
import org.briarproject.bramble.api.keyagreement.Payload;
@@ -13,7 +14,8 @@ import java.io.IOException;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_ID;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_VERSION;
@Immutable
@NotNullByDefault
@@ -29,14 +31,16 @@ class PayloadEncoderImpl implements PayloadEncoder {
@Override
public byte[] encode(Payload p) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(PROTOCOL_VERSION);
int formatIdAndVersion = (QR_FORMAT_ID << 5) | QR_FORMAT_VERSION;
out.write(formatIdAndVersion);
BdfList payload = new BdfList();
payload.add(p.getCommitment());
for (TransportDescriptor d : p.getTransportDescriptors()) {
payload.add(d.getDescriptor());
}
BdfWriter w = bdfWriterFactory.createWriter(out);
try {
w.writeListStart(); // Payload start
w.writeRaw(p.getCommitment());
for (TransportDescriptor d : p.getTransportDescriptors())
w.writeList(d.getDescriptor());
w.writeListEnd(); // Payload end
w.writeList(payload);
} catch (IOException e) {
// Shouldn't happen with ByteArrayOutputStream
throw new AssertionError(e);

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfReader;
@@ -11,6 +12,9 @@ import org.briarproject.bramble.api.keyagreement.TransportDescriptor;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.bramble.api.qrcode.WrongQrCodeTypeException;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.ByteArrayInputStream;
@@ -21,34 +25,42 @@ import java.util.List;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.BETA_PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
@Immutable
@NotNullByDefault
class PayloadParserImpl implements PayloadParser {
private final BdfReaderFactory bdfReaderFactory;
private final QrCodeClassifier qrCodeClassifier;
@Inject
PayloadParserImpl(BdfReaderFactory bdfReaderFactory) {
PayloadParserImpl(BdfReaderFactory bdfReaderFactory,
QrCodeClassifier qrCodeClassifier) {
this.bdfReaderFactory = bdfReaderFactory;
this.qrCodeClassifier = qrCodeClassifier;
}
@Override
public Payload parse(byte[] raw) throws IOException {
ByteArrayInputStream in = new ByteArrayInputStream(raw);
// First byte: the protocol version
int protocolVersion = in.read();
if (protocolVersion == -1) throw new FormatException();
if (protocolVersion != PROTOCOL_VERSION) {
boolean tooOld = protocolVersion < PROTOCOL_VERSION ||
protocolVersion == BETA_PROTOCOL_VERSION;
public Payload parse(String payloadString) throws IOException {
Pair<QrCodeType, Integer> typeAndVersion =
qrCodeClassifier.classifyQrCode(payloadString);
QrCodeType qrCodeType = typeAndVersion.getFirst();
if (qrCodeType != BQP) throw new WrongQrCodeTypeException(qrCodeType);
int formatVersion = typeAndVersion.getSecond();
if (formatVersion != QR_FORMAT_VERSION) {
boolean tooOld = formatVersion < QR_FORMAT_VERSION;
throw new UnsupportedVersionException(tooOld);
}
byte[] raw = payloadString.getBytes(ISO_8859_1);
ByteArrayInputStream in = new ByteArrayInputStream(raw);
// First byte: the format identifier and version (already parsed)
if (in.read() == -1) throw new AssertionError();
// The rest of the payload is a BDF list with one or more elements
BdfReader r = bdfReaderFactory.createReader(in);
BdfList payload = r.readList();
@@ -61,7 +73,7 @@ class PayloadParserImpl implements PayloadParser {
List<TransportDescriptor> recognised = new ArrayList<>();
for (int i = 1; i < payload.size(); i++) {
BdfList descriptor = payload.getList(i);
long transportId = descriptor.getLong(0);
int transportId = descriptor.getInt(0);
if (transportId == TRANSPORT_ID_BLUETOOTH) {
TransportId id = BluetoothConstants.ID;
recognised.add(new TransportDescriptor(id, descriptor));

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
@@ -11,12 +12,15 @@ import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.Base32;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
@@ -26,6 +30,7 @@ import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
@Immutable
@NotNullByDefault
@@ -98,6 +103,22 @@ class MailboxManagerImpl implements MailboxManager {
return created;
}
@Override
public String convertBase32Payload(String base32Payload)
throws FormatException {
Pattern regex = Pattern.compile("(briar-mailbox://)?([a-z2-7]{104})");
Matcher matcher = regex.matcher(base32Payload);
if (!matcher.find()) throw new FormatException();
String base32 = matcher.group(2);
byte[] payloadBytes;
try {
payloadBytes = Base32.decode(base32, false);
} catch (IllegalArgumentException e) {
throw new FormatException();
}
return new String(payloadBytes, ISO_8859_1);
}
@Override
public boolean checkConnection() {
List<MailboxVersion> versions = null;

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.nullsafety.NotNullByDefault;
@@ -25,6 +26,7 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager;
private final QrCodeClassifier qrCodeClassifier;
@Inject
MailboxPairingTaskFactoryImpl(
@@ -34,7 +36,8 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
Clock clock,
MailboxApi api,
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager) {
MailboxUpdateManager mailboxUpdateManager,
QrCodeClassifier qrCodeClassifier) {
this.eventExecutor = eventExecutor;
this.db = db;
this.crypto = crypto;
@@ -42,12 +45,13 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager;
this.qrCodeClassifier = qrCodeClassifier;
}
@Override
public MailboxPairingTask createPairingTask(String qrCodePayload) {
return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db,
crypto, clock, api, mailboxSettingsManager,
mailboxUpdateManager);
mailboxUpdateManager, qrCodeClassifier);
}
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -9,18 +10,26 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.ConnectionError;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.InvalidQrCode;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.MailboxAlreadyPaired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Paired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Pairing;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.QrCodeReceived;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.UnexpectedError;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxAlreadyPairedException;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -32,7 +41,10 @@ import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
@ThreadSafe
@NotNullByDefault
@@ -40,9 +52,6 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
private final static Logger LOG =
getLogger(MailboxPairingTaskImpl.class.getName());
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
private static final int VERSION_REQUIRED = 32;
private final String payload;
private final Executor eventExecutor;
@@ -52,6 +61,8 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager;
private final QrCodeClassifier qrCodeClassifier;
private final long timeStarted;
private final Object lock = new Object();
@GuardedBy("lock")
@@ -68,7 +79,8 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
Clock clock,
MailboxApi api,
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager) {
MailboxUpdateManager mailboxUpdateManager,
QrCodeClassifier qrCodeClassifier) {
this.payload = payload;
this.eventExecutor = eventExecutor;
this.db = db;
@@ -77,7 +89,9 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager;
state = new MailboxPairingState.QrCodeReceived();
this.qrCodeClassifier = qrCodeClassifier;
timeStarted = clock.currentTimeMillis();
state = new QrCodeReceived(timeStarted);
}
@Override
@@ -99,22 +113,30 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
@Override
public void run() {
Pair<QrCodeType, Integer> typeAndVersion =
qrCodeClassifier.classifyQrCode(payload);
QrCodeType qrCodeType = typeAndVersion.getFirst();
int formatVersion = typeAndVersion.getSecond();
if (qrCodeType != MAILBOX || formatVersion != QR_FORMAT_VERSION) {
setState(new InvalidQrCode(qrCodeType, formatVersion));
return;
}
try {
pairMailbox();
} catch (FormatException e) {
onMailboxError(e, new MailboxPairingState.InvalidQrCode());
onMailboxError(e, new InvalidQrCode(qrCodeType, formatVersion));
} catch (MailboxAlreadyPairedException e) {
onMailboxError(e, new MailboxPairingState.MailboxAlreadyPaired());
onMailboxError(e, new MailboxAlreadyPaired());
} catch (IOException e) {
onMailboxError(e, new MailboxPairingState.ConnectionError());
onMailboxError(e, new ConnectionError());
} catch (ApiException | DbException e) {
onMailboxError(e, new MailboxPairingState.UnexpectedError());
onMailboxError(e, new UnexpectedError());
}
}
private void pairMailbox() throws IOException, ApiException, DbException {
MailboxProperties mailboxProperties = decodeQrCodePayload(payload);
setState(new MailboxPairingState.Pairing());
setState(new Pairing(timeStarted));
MailboxProperties ownerProperties = api.setup(mailboxProperties);
long time = clock.currentTimeMillis();
db.transaction(false, txn -> {
@@ -133,7 +155,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
}
}
});
setState(new MailboxPairingState.Paired());
setState(new Paired());
}
private void onMailboxError(Exception e, MailboxPairingState state) {
@@ -167,14 +189,6 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
}
throw new FormatException();
}
int version = bytes[0] & 0xFF;
if (version != VERSION_REQUIRED) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("QR code has not version " + VERSION_REQUIRED +
": " + version);
}
throw new FormatException();
}
LOG.info("QR code is valid");
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
String onion = crypto.encodeOnion(onionPubKey);

View File

@@ -419,7 +419,7 @@ class LanTcpPlugin extends TcpPlugin {
private InetSocketAddress parseSocketAddress(BdfList descriptor)
throws FormatException {
byte[] address = descriptor.getRaw(1);
int port = descriptor.getLong(2).intValue();
int port = descriptor.getInt(2);
if (port < 1 || port > MAX_16_BIT_UNSIGNED) throw new FormatException();
try {
InetAddress addr = InetAddress.getByAddress(address);

View File

@@ -46,7 +46,6 @@ import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -56,7 +55,6 @@ import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.zip.ZipInputStream;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
@@ -100,7 +98,9 @@ import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -140,7 +140,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final long maxLatency;
private final int maxIdleTime;
private final int socketTimeout;
private final File torDirectory, geoIpFile, configFile;
private final File torDirectory;
private final File configFile;
private final int torSocksPort;
private final int torControlPort;
private final File doneFile, cookieFile;
@@ -195,7 +196,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
this.torDirectory = torDirectory;
this.torSocksPort = torSocksPort;
this.torControlPort = torControlPort;
geoIpFile = new File(torDirectory, "geoip");
configFile = new File(torDirectory, "torrc");
doneFile = new File(torDirectory, "done");
cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
@@ -332,12 +332,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// The done file may already exist from a previous installation
//noinspection ResultOfMethodCallIgnored
doneFile.delete();
// The GeoIP file may exist from a previous installation - we can
// save some space by deleting it.
// TODO: Remove after a reasonable migration period
// (added 2022-03-29)
//noinspection ResultOfMethodCallIgnored
geoIpFile.delete();
installTorExecutable();
installObfs4Executable();
installSnowflakeExecutable();
@@ -354,7 +348,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (LOG.isLoggable(INFO))
LOG.info("Installing Tor binary for " + architecture);
File torFile = getTorExecutableFile();
extract(getTorInputStream(), torFile);
extract(getExecutableInputStream("tor"), torFile);
if (!torFile.setExecutable(true, true)) throw new IOException();
}
@@ -362,7 +356,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (LOG.isLoggable(INFO))
LOG.info("Installing obfs4proxy binary for " + architecture);
File obfs4File = getObfs4ExecutableFile();
extract(getObfs4InputStream(), obfs4File);
extract(getExecutableInputStream("obfs4proxy"), obfs4File);
if (!obfs4File.setExecutable(true, true)) throw new IOException();
}
@@ -370,28 +364,18 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (LOG.isLoggable(INFO))
LOG.info("Installing snowflake binary for " + architecture);
File snowflakeFile = getSnowflakeExecutableFile();
extract(getSnowflakeInputStream(), snowflakeFile);
extract(getExecutableInputStream("snowflake"), snowflakeFile);
if (!snowflakeFile.setExecutable(true, true)) throw new IOException();
}
private InputStream getTorInputStream() throws IOException {
return getZipInputStream("tor");
private InputStream getExecutableInputStream(String basename) {
String ext = getExecutableExtension();
return requireNonNull(resourceProvider
.getResourceInputStream(architecture + "/" + basename, ext));
}
private InputStream getObfs4InputStream() throws IOException {
return getZipInputStream("obfs4proxy");
}
private InputStream getSnowflakeInputStream() throws IOException {
return getZipInputStream("snowflake");
}
private InputStream getZipInputStream(String basename) throws IOException {
InputStream in = resourceProvider
.getResourceInputStream(basename + "_" + architecture, ".zip");
ZipInputStream zin = new ZipInputStream(in);
if (zin.getNextEntry() == null) throw new IOException();
return zin;
protected String getExecutableExtension() {
return "";
}
private static void append(StringBuilder strb, String name, Object value) {
@@ -419,9 +403,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path);
String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath();
append(strb, "ClientTransportPlugin snowflake exec", snowflakePath);
//noinspection CharsetObjectCanBeUsed
return new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("UTF-8")));
return new ByteArrayInputStream(strb.toString().getBytes(UTF_8));
}
private void listFiles(File f) {

View File

@@ -7,7 +7,7 @@ import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import org.bouncycastle.util.encoders.Base64;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import java.nio.charset.Charset;
import static org.briarproject.bramble.util.StringUtils.US_ASCII;
class TorRendezvousCryptoImpl implements TorRendezvousCrypto {
@@ -31,6 +31,6 @@ class TorRendezvousCryptoImpl implements TorRendezvousCrypto {
EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC);
byte[] hash = spec.getH();
byte[] base64 = Base64.encode(hash);
return "ED25519-V3:" + new String(base64, Charset.forName("US-ASCII"));
return "ED25519-V3:" + new String(base64, US_ASCII);
}
}

View File

@@ -201,7 +201,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
// Retrieve and parse the latest local properties
for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) {
BdfList message = clientHelper.getMessageAsList(txn,
e.getValue().messageId);
e.getValue().messageId, false);
local.put(e.getKey(), parseProperties(message));
}
return local;
@@ -222,7 +222,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
if (latest != null) {
// Retrieve and parse the latest local properties
BdfList message = clientHelper.getMessageAsList(txn,
latest.messageId);
latest.messageId, false);
p = parseProperties(message);
}
return p == null ? new TransportProperties() : p;
@@ -252,7 +252,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
local = new TransportProperties();
} else {
BdfList message = clientHelper.getMessageAsList(txn,
latest.messageId);
latest.messageId, false);
local = parseProperties(message);
}
storeLocalProperties(txn, c, t, local);
@@ -272,8 +272,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
remote = new TransportProperties();
} else {
// Retrieve and parse the latest remote properties
BdfList message =
clientHelper.getMessageAsList(txn, latest.messageId);
BdfList message = clientHelper.getMessageAsList(txn,
latest.messageId, false);
remote = parseProperties(message);
}
// Merge in any discovered properties
@@ -317,7 +317,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
changed = true;
} else {
BdfList message = clientHelper.getMessageAsList(txn,
latest.messageId);
latest.messageId, false);
TransportProperties old = parseProperties(message);
merged = new TransportProperties(old);
for (Entry<String, String> e : p.entrySet()) {

View File

@@ -27,7 +27,10 @@ class TransportPropertyValidator extends BdfMessageValidator {
TransportPropertyValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
super(clientHelper, metadataEncoder, clock);
// Accept transport properties in non-canonical form
// TODO: Remove this after a reasonable migration period
// (added 2023-02-17)
super(clientHelper, metadataEncoder, clock, false);
}
@Override

View File

@@ -0,0 +1,42 @@
package org.briarproject.bramble.qrcode;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConstants;
import org.briarproject.bramble.api.mailbox.MailboxConstants;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.UNKNOWN;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
@Immutable
@NotNullByDefault
class QrCodeClassifierImpl implements QrCodeClassifier {
@Inject
QrCodeClassifierImpl() {
}
@Override
public Pair<QrCodeType, Integer> classifyQrCode(String payload) {
byte[] bytes = payload.getBytes(ISO_8859_1);
if (bytes.length == 0) return new Pair<>(UNKNOWN, 0);
// If this is a Bramble QR code then the first byte encodes the
// format ID (3 bits) and version (5 bits)
int formatIdAndVersion = bytes[0] & 0xFF;
int formatId = formatIdAndVersion >> 5;
int formatVersion = formatIdAndVersion & 0x1F;
if (formatId == KeyAgreementConstants.QR_FORMAT_ID) {
return new Pair<>(BQP, formatVersion);
}
if (formatId == MailboxConstants.QR_FORMAT_ID) {
return new Pair<>(MAILBOX, formatVersion);
}
return new Pair<>(UNKNOWN, 0);
}
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.bramble.qrcode;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import dagger.Module;
import dagger.Provides;
@Module
public class QrCodeModule {
@Provides
QrCodeClassifier provideQrCodeClassifier(
QrCodeClassifierImpl qrCodeClassifier) {
return qrCodeClassifier;
}
}

View File

@@ -30,6 +30,7 @@ import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
import static org.briarproject.bramble.api.sync.RecordTypes.PRIORITY;
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
import static org.briarproject.bramble.api.sync.RecordTypes.VERSIONS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_SUPPORTED_VERSIONS;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PRIORITY_NONCE_BYTES;
@@ -126,6 +127,8 @@ class SyncRecordReaderImpl implements SyncRecordReader {
byte[] payload = nextRecord.getPayload();
if (payload.length <= MESSAGE_HEADER_LENGTH)
throw new FormatException();
if (payload.length > MAX_MESSAGE_LENGTH)
throw new FormatException();
// Validate timestamp
long timestamp = ByteUtils.readUint64(payload, UniqueId.LENGTH);
if (timestamp < 0) throw new FormatException();

View File

@@ -32,8 +32,7 @@ class SessionParserImpl implements SessionParser {
@Override
public Session parseSession(BdfDictionary meta) throws FormatException {
State state =
State.fromValue(meta.getLong(SESSION_KEY_STATE).intValue());
State state = State.fromValue(meta.getInt(SESSION_KEY_STATE));
MessageId lastLocalMessageId = null;
byte[] lastLocalMessageIdBytes =
@@ -56,9 +55,9 @@ class SessionParserImpl implements SessionParser {
Long localTimestamp = meta.getOptionalLong(SESSION_KEY_LOCAL_TIMESTAMP);
KeySetId keySetId = null;
Long keySetIdLong = meta.getOptionalLong(SESSION_KEY_KEY_SET_ID);
if (keySetIdLong != null) {
keySetId = new KeySetId(keySetIdLong.intValue());
Integer keySetIdInt = meta.getOptionalInt(SESSION_KEY_KEY_SET_ID);
if (keySetIdInt != null) {
keySetId = new KeySetId(keySetIdInt);
}
return new Session(state, lastLocalMessageId, localKeyPair,

View File

@@ -177,8 +177,8 @@ class TransportKeyAgreementManagerImpl extends BdfIncomingMessageHook
protected DeliveryAction incomingMessage(Transaction txn, Message m,
BdfList body, BdfDictionary meta)
throws DbException, FormatException {
MessageType type = MessageType.fromValue(
meta.getLong(MSG_KEY_MESSAGE_TYPE).intValue());
MessageType type =
MessageType.fromValue(meta.getInt(MSG_KEY_MESSAGE_TYPE));
TransportId t = new TransportId(meta.getString(MSG_KEY_TRANSPORT_ID));
if (LOG.isLoggable(INFO)) {
LOG.info("Received " + type + " message for " + t);

View File

@@ -42,7 +42,7 @@ class TransportKeyAgreementValidator extends BdfMessageValidator {
@Override
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException {
MessageType type = MessageType.fromValue(body.getLong(0).intValue());
MessageType type = MessageType.fromValue(body.getInt(0));
if (type == KEY) return validateKeyMessage(m.getTimestamp(), body);
else if (type == ACTIVATE) return validateActivateMessage(body);
else throw new AssertionError();

View File

@@ -301,8 +301,8 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
for (int i = 0; i < size; i++) {
BdfList cv = body.getList(i);
ClientId clientId = new ClientId(cv.getString(0));
int majorVersion = cv.getLong(1).intValue();
int minorVersion = cv.getLong(2).intValue();
int majorVersion = cv.getInt(1);
int minorVersion = cv.getInt(2);
parsed.add(new ClientVersion(clientId, majorVersion, minorVersion));
}
return parsed;
@@ -408,8 +408,8 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
throws FormatException {
// Client ID, major version, minor version, active
ClientId clientId = new ClientId(clientState.getString(0));
int majorVersion = clientState.getLong(1).intValue();
int minorVersion = clientState.getLong(2).intValue();
int majorVersion = clientState.getInt(1);
int minorVersion = clientState.getInt(2);
boolean active = clientState.getBoolean(3);
return new ClientState(clientId, majorVersion, minorVersion, active);
}

View File

@@ -43,9 +43,9 @@ class ClientVersioningValidator extends BdfMessageValidator {
checkSize(clientState, 4);
String clientId = clientState.getString(0);
checkLength(clientId, 1, MAX_CLIENT_ID_LENGTH);
int majorVersion = clientState.getLong(1).intValue();
int majorVersion = clientState.getInt(1);
if (majorVersion < 0) throw new FormatException();
int minorVersion = clientState.getLong(2).intValue();
int minorVersion = clientState.getInt(2);
if (minorVersion < 0) throw new FormatException();
clientState.getBoolean(3);
}

View File

@@ -10,22 +10,21 @@ d Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K
d Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
n Bridge obfs4 93.95.226.151:41185 460B0CFFC0CF1D965F3DE064E08BA1915E7C916A cert=inluPzp5Jp5OzZar1eQb4dcQ/YlAj/v0kHAUCoCr3rmLt03+pVuVTjoH4mRy4+acXpn+Gw iat-mode=0
n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0
n Bridge obfs4 185.177.207.138:8443 53716FE26F23C8C6A71A2BC5D9D8DC64747278C7 cert=6jcYVilMEzxdsWghSrQFpYYJlkkir/GPIXw/EnddUK3S8ToVpMG8u1SwMOEdEs735RrMHw iat-mode=0
n Bridge obfs4 104.168.68.90:443 ED55B3C321E44EA7E50EF568C8A63CF75E89A58C cert=fgonxDvltTp8nmcOE9sUG94eOAALxETVVXAwnTZJLPpf7rjPuTp+abKl4VyFkxfcLRr5KQ iat-mode=0
n Bridge obfs4 45.142.181.131:42069 6EBCF6B02DA2B982F4080A7119D737366AFB74FA cert=9HyWH/BCwWzNirZdTQtluCgJk+gFhpOqydIuyQ1iDvpnqsAynKF+zcPE/INZFefm86UlBg iat-mode=0
n Bridge obfs4 85.214.28.204:47111 78A36E46BB082A471848239D3F4390A8F8C6084D cert=96sr3eaUFBDu4wFVAQIfNFElh0UNuutJ/3/Fh2Vu2PHfacQ8bwfY02bwG351U8BZpLnfUQ iat-mode=0
n Bridge obfs4 152.67.77.101:4096 B82DB9CDDF887AB8A859420E07DF298E30AF8A6E cert=21OWn3yFo+hulmQNAOtF5uwwOqWtdT5PrLhk8BG9DpOd0/k5DEkQEYPyDdXbS9nZ0E5BJA iat-mode=0
n Bridge obfs4 185.103.252.72:443 75F15E9339FF572F88F5588D429FEA379744BC53 cert=nOZ/SaRE3L1dChvjfe0Ks/wM/F8iFhwd3g2G5zgtcLB8x+wiZRWCwjRrbbiQyb3Gh2mxRQ iat-mode=0
n Bridge obfs4 76.255.201.112:8888 96CF36C2ECCFB7376AB6BE905BECD2C2AE8AEFCD cert=+q0pjaiM0JMqHL/BKqCRD+pjflaw/S406eUDF7CnFgamvQW3l2HVLJhQ6uX9P8zff0PLGg iat-mode=0
n Bridge obfs4 94.142.246.132:8088 135C158527AA9FE9A2F26EC515EB6999D813D347 cert=wTUz0/5FhAZRkitil5MprGbSF3JzjxjxI1kAmxAdSeDy98NgcLr11f/qUXWDC76Y97RiSg iat-mode=0
n Bridge obfs4 20.102.79.78:22022 B5705F7E616DAB0F477E3E1ADC23E40413F683FE cert=1Cc/hwPtPjzFKGHVOP0j/qmBgnvquRx8+im35/u5TIYjDQ3FlMfA5VvTrQ/hbX8BZZooLQ iat-mode=0
n Bridge obfs4 207.154.242.137:80 8E67A1B2A342652EE27376BD61BECF5806700E7F cert=qUrR9fan3XPNGNOwn9WGlXLJNZZx0grXH4AZXR+yoBbtbbj5Ak1n4a7TtjYgXcWcs/gHXw iat-mode=0
n Bridge obfs4 152.70.180.20:1993 3327C43587E66AD5F874C4234A1D72C938AD7318 cert=s7xLRUO2psaX7TMUP2YhXdxItR4U6K7D+E3gQaS/+yWUppevtazIibq4dN1g5Reu6dD2QQ iat-mode=0
n Bridge obfs4 144.202.12.254:10002 4E220F45CD404C8A3082A36326A5ED19BB8D4404 cert=iLz5YYWO4pUw7U7MRNOSvE0qO+IVeE4kVfFVWPO3coH3FmZtrkvlaTklfXxHZaCcXWBgaA iat-mode=0
n Bridge obfs4 15.235.47.204:42058 869133925B3CD07683BDF01805C36448D090CE88 cert=PFwh4mzZlSTUdcEskpe20t998n5jbr81s+XoX7gmazqzUGHNhkendK5K1j2gOxesz9AkBw iat-mode=0
n Bridge obfs4 51.75.93.136:45532 8402B84833527BC249B21AC885134197E624FB5A cert=LwXEf/Dgo0tKdMJByXdlvWiJqyyPw4T284Cg5qygDuIJJNFuz3ED9UhGil6H4Of3gM7wSg iat-mode=0
n Bridge obfs4 109.14.168.159:5082 BFE1416DEFFE969581F016A4A319A87FFB26BA91 cert=n3X1CDdKBPXPIzfKh83p3ydfMzb0AD9gKC+/gIpHb7+xjjAnYO9x3LT+T/MvOIfAXxYySg iat-mode=0
n Bridge obfs4 185.177.207.132:8443 4FB781F7A9DD39DA53A7996907817FC479874D19 cert=UL2gCAXWW5kEWY4TQ0lNeu6OAmzh40bXYVhMnTWVG8USnyy/zEKGSIPgmwTDMumWr9c1Pg iat-mode=0
n Bridge obfs4 213.108.110.149:7499 519344140473CF91030B08F91521F9A6C144ED6C cert=k9fSL/d491qAkGmi2VeSwVlfuyO02jBeN54qxzzQISxpfm3b+a6kJpo8/Bfy1ACbHZIJUg iat-mode=0
n Bridge obfs4 158.174.114.97:3456 32665CD4CBE19092CA47A53D317B8BFF5810441C cert=ne5Zt0TcMedSGmFwAs/AV6J6E9Hn7mG5mR6vQNpEfyuCZK1VRpQvU1LvvtesSu4CXqZtYQ iat-mode=0
n Bridge obfs4 64.4.175.62:8000 8B72740D150795ACB5101AA5F95D1ACDA4FE6B3E cert=vduuNhJ5U/8hjZmllP6AFfXSlSZsnrimdR8Tm8DY9dxWS4n2j92fNc0qHihUwRqwcOfIcg iat-mode=0
n Bridge obfs4 82.64.115.17:990 B08238781C2CD80DBD95AEABEB6F6C75F2E2CEB6 cert=1udeMlFNs3sJ20zwpPE6nShZqqwDb3F1ET4KzfSfD+fktkue9zNx9H3t+yLCPAsg+6UTUA iat-mode=1
n Bridge obfs4 87.161.120.147:9292 9418EEBE8AEAE32CC381AF51610366E8B24651E0 cert=DFRm74qsD1i2/ypaGochpX6CS1j9JTFAKEYaHXrgrx6M2LG5Cvppdt3Ob7lULfhqgtAUdg iat-mode=0
n Bridge obfs4 157.90.245.231:8599 C23CD468EC04555E2B37BE81A771E681049DEA6A cert=UsmDelrbwg4jc6BMvZJ0TS8klUIa2qkbRu3xwQc3ZXPEgpMqyTYUxcVwyPbIU5KmAHsmAA iat-mode=0
v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
v Bridge 213.108.108.145:17674 A39C0FE47963B6E8CFE9815549864DE544935A31
v Bridge 213.196.191.96:9060 05E222E2A8C234234FE0CEB58B08A93B8FC360DB

View File

@@ -20,7 +20,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import javax.annotation.Nullable;
@@ -34,6 +33,7 @@ import static org.briarproject.bramble.test.TestUtils.getIdentity;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.junit.Assert.assertArrayEquals;
@@ -342,7 +342,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
private void storeDatabaseKey(File f, String hex) throws IOException {
f.getParentFile().mkdirs();
FileOutputStream out = new FileOutputStream(f);
out.write(hex.getBytes(Charset.forName("UTF-8")));
out.write(hex.getBytes(UTF_8));
out.flush();
out.close();
}
@@ -350,7 +350,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
@Nullable
private String loadDatabaseKey(File f) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), Charset.forName("UTF-8")));
new FileInputStream(f), UTF_8));
String hex = reader.readLine();
reader.close();
return hex;

View File

@@ -56,7 +56,7 @@ public class BdfMessageValidatorTest extends ValidatorTestCase {
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(timestamp - MAX_CLOCK_DIFFERENCE));
oneOf(clientHelper).toList(message.getBody());
oneOf(clientHelper).toList(message, true);
will(returnValue(body));
oneOf(metadataEncoder).encode(dictionary);
will(returnValue(meta));
@@ -86,7 +86,7 @@ public class BdfMessageValidatorTest extends ValidatorTestCase {
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(timestamp));
oneOf(clientHelper).toList(shortMessage.getBody());
oneOf(clientHelper).toList(shortMessage, true);
will(returnValue(body));
oneOf(metadataEncoder).encode(dictionary);
will(returnValue(meta));
@@ -114,7 +114,7 @@ public class BdfMessageValidatorTest extends ValidatorTestCase {
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(timestamp));
oneOf(clientHelper).toList(message.getBody());
oneOf(clientHelper).toList(message, true);
will(throwException(new FormatException()));
}});
@@ -126,7 +126,7 @@ public class BdfMessageValidatorTest extends ValidatorTestCase {
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(timestamp));
oneOf(clientHelper).toList(message.getBody());
oneOf(clientHelper).toList(message, true);
will(returnValue(body));
}});

View File

@@ -546,7 +546,7 @@ public class ClientHelperImplTest extends BrambleMockTestCase {
context.checking(new Expectations() {{
oneOf(bdfReaderFactory)
.createReader(with(any(InputStream.class)));
.createReader(with(any(InputStream.class)), with(true));
will(returnValue(bdfReader));
oneOf(bdfReader).readList();
will(returnValue(list));

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.data;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Before;
import org.junit.Test;
@@ -31,11 +32,14 @@ public class BdfReaderImplFuzzingTest extends BrambleTestCase {
buf[1] = 0x14; // Length 20 bytes
in.reset();
BdfReaderImpl r = new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT,
DEFAULT_MAX_BUFFER_SIZE);
int length = r.readString().length();
assertTrue(length >= 0);
assertTrue(length <= 20);
assertTrue(r.eof());
DEFAULT_MAX_BUFFER_SIZE, true);
try {
int length = r.readString().length();
assertTrue(length <= 20);
assertTrue(r.eof());
} catch (FormatException e) {
// Expected when bytes are not valid UTF-8
}
}
}
}

View File

@@ -11,6 +11,7 @@ import java.io.ByteArrayInputStream;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.briarproject.bramble.api.data.BdfReader.DEFAULT_MAX_BUFFER_SIZE;
import static org.briarproject.bramble.data.BdfReaderImpl.DEFAULT_NESTED_LIMIT;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@@ -88,6 +89,18 @@ public class BdfReaderImplTest extends BrambleTestCase {
assertTrue(r.eof());
}
@Test(expected = FormatException.class)
public void testReadLong16CouldHaveBeenLong8Max() throws Exception {
setContents("22" + "007F");
r.readLong();
}
@Test(expected = FormatException.class)
public void testReadLong16CouldHaveBeenLong8Min() throws Exception {
setContents("22" + "FF80");
r.readLong();
}
@Test
public void testSkipLong16() throws Exception {
setContents("22" + "0080");
@@ -106,6 +119,18 @@ public class BdfReaderImplTest extends BrambleTestCase {
assertTrue(r.eof());
}
@Test(expected = FormatException.class)
public void testReadLong32CouldHaveBeenLong16Max() throws Exception {
setContents("24" + "00007FFF");
r.readLong();
}
@Test(expected = FormatException.class)
public void testReadLong32CouldHaveBeenLong16Min() throws Exception {
setContents("24" + "FFFF8000");
r.readLong();
}
@Test
public void testSkipLong32() throws Exception {
setContents("24" + "00008000");
@@ -124,13 +149,48 @@ public class BdfReaderImplTest extends BrambleTestCase {
assertTrue(r.eof());
}
@Test(expected = FormatException.class)
public void testReadLong64CouldHaveBeenLong32Max() throws Exception {
setContents("28" + "000000007FFFFFFF");
r.readLong();
}
@Test(expected = FormatException.class)
public void testReadLong64CouldHaveBeenLong32Min() throws Exception {
setContents("28" + "FFFFFFFF80000000");
r.readLong();
}
@Test
public void testSkipLong() throws Exception {
public void testSkipLong64() throws Exception {
setContents("28" + "0000000080000000");
r.skipLong();
assertTrue(r.eof());
}
@Test
public void testReadInt() throws Exception {
setContents("21" + "7F" + "21" + "80"
+ "22" + "7FFF" + "22" + "8000"
+ "24" + "7FFFFFFF" + "24" + "80000000");
assertEquals(Byte.MAX_VALUE, r.readInt());
assertEquals(Byte.MIN_VALUE, r.readInt());
assertEquals(Short.MAX_VALUE, r.readInt());
assertEquals(Short.MIN_VALUE, r.readInt());
assertEquals(Integer.MAX_VALUE, r.readInt());
assertEquals(Integer.MIN_VALUE, r.readInt());
assertTrue(r.eof());
}
@Test
public void testSkipInt() throws Exception {
setContents("21" + "7F" + "22" + "7FFF" + "24" + "7FFFFFFF");
r.skipInt();
r.skipInt();
r.skipInt();
assertTrue(r.eof());
}
@Test
public void testReadDouble() throws Exception {
// http://babbage.cs.qc.edu/IEEE-754/Decimal.html
@@ -162,7 +222,7 @@ public class BdfReaderImplTest extends BrambleTestCase {
@Test
public void testReadString8() throws Exception {
String longest = getRandomString(Byte.MAX_VALUE);
String longHex = toHexString(longest.getBytes("UTF-8"));
String longHex = toHexString(longest.getBytes(UTF_8));
// "foo", the empty string, and 127 random letters
setContents("41" + "03" + "666F6F" + "41" + "00" +
"41" + "7F" + longHex);
@@ -186,7 +246,7 @@ public class BdfReaderImplTest extends BrambleTestCase {
@Test
public void testSkipString8() throws Exception {
String longest = getRandomString(Byte.MAX_VALUE);
String longHex = toHexString(longest.getBytes("UTF-8"));
String longHex = toHexString(longest.getBytes(UTF_8));
// "foo", the empty string, and 127 random letters
setContents("41" + "03" + "666F6F" + "41" + "00" +
"41" + "7F" + longHex);
@@ -199,9 +259,9 @@ public class BdfReaderImplTest extends BrambleTestCase {
@Test
public void testReadString16() throws Exception {
String shortest = getRandomString(Byte.MAX_VALUE + 1);
String shortHex = toHexString(shortest.getBytes("UTF-8"));
String shortHex = toHexString(shortest.getBytes(UTF_8));
String longest = getRandomString(Short.MAX_VALUE);
String longHex = toHexString(longest.getBytes("UTF-8"));
String longHex = toHexString(longest.getBytes(UTF_8));
// 128 random letters and 2^15 -1 random letters
setContents("42" + "0080" + shortHex + "42" + "7FFF" + longHex);
assertEquals(shortest, r.readString());
@@ -213,7 +273,7 @@ public class BdfReaderImplTest extends BrambleTestCase {
public void testReadString16ChecksMaxLength() throws Exception {
int maxBufferSize = Byte.MAX_VALUE + 1;
String valid = getRandomString(Byte.MAX_VALUE + 1);
String validHex = toHexString(valid.getBytes("UTF-8"));
String validHex = toHexString(valid.getBytes(UTF_8));
String invalidhex = validHex + "20";
// 128 random letters, the same plus a space
setContents("42" + "0080" + validHex
@@ -223,12 +283,20 @@ public class BdfReaderImplTest extends BrambleTestCase {
r.readString();
}
@Test(expected = FormatException.class)
public void testReadString16CouldHaveBeenString8() throws Exception {
String longest = getRandomString(Byte.MAX_VALUE);
String longHex = toHexString(longest.getBytes(UTF_8));
setContents("42" + "007F" + longHex);
r.readString();
}
@Test
public void testSkipString16() throws Exception {
String shortest = getRandomString(Byte.MAX_VALUE + 1);
String shortHex = toHexString(shortest.getBytes("UTF-8"));
String shortHex = toHexString(shortest.getBytes(UTF_8));
String longest = getRandomString(Short.MAX_VALUE);
String longHex = toHexString(longest.getBytes("UTF-8"));
String longHex = toHexString(longest.getBytes(UTF_8));
// 128 random letters and 2^15 - 1 random letters
setContents("42" + "0080" + shortHex + "42" + "7FFF" + longHex);
r.skipString();
@@ -239,7 +307,7 @@ public class BdfReaderImplTest extends BrambleTestCase {
@Test
public void testReadString32() throws Exception {
String shortest = getRandomString(Short.MAX_VALUE + 1);
String shortHex = toHexString(shortest.getBytes("UTF-8"));
String shortHex = toHexString(shortest.getBytes(UTF_8));
// 2^15 random letters
setContents("44" + "00008000" + shortHex);
assertEquals(shortest, r.readString());
@@ -250,7 +318,7 @@ public class BdfReaderImplTest extends BrambleTestCase {
public void testReadString32ChecksMaxLength() throws Exception {
int maxBufferSize = Short.MAX_VALUE + 1;
String valid = getRandomString(maxBufferSize);
String validHex = toHexString(valid.getBytes("UTF-8"));
String validHex = toHexString(valid.getBytes(UTF_8));
String invalidHex = validHex + "20";
// 2^15 random letters, the same plus a space
setContents("44" + "00008000" + validHex +
@@ -260,10 +328,18 @@ public class BdfReaderImplTest extends BrambleTestCase {
r.readString();
}
@Test(expected = FormatException.class)
public void testReadString32CouldHaveBeenString16() throws Exception {
String longest = getRandomString(Short.MAX_VALUE);
String longHex = toHexString(longest.getBytes(UTF_8));
setContents("44" + "00007FFF" + longHex);
r.readString();
}
@Test
public void testSkipString32() throws Exception {
String shortest = getRandomString(Short.MAX_VALUE + 1);
String shortHex = toHexString(shortest.getBytes("UTF-8"));
String shortHex = toHexString(shortest.getBytes(UTF_8));
// 2^15 random letters, twice
setContents("44" + "00008000" + shortHex +
"44" + "00008000" + shortHex);
@@ -275,7 +351,7 @@ public class BdfReaderImplTest extends BrambleTestCase {
@Test
public void testReadUtf8String() throws Exception {
String unicode = "\uFDD0\uFDD1\uFDD2\uFDD3";
String hex = toHexString(unicode.getBytes("UTF-8"));
String hex = toHexString(unicode.getBytes(UTF_8));
// STRING_8 tag, "foo", the empty string, and the test string
setContents("41" + "03" + "666F6F" + "41" + "00" + "41" + "0C" + hex);
assertEquals("foo", r.readString());
@@ -348,6 +424,14 @@ public class BdfReaderImplTest extends BrambleTestCase {
r.readRaw();
}
@Test(expected = FormatException.class)
public void testReadRaw16CouldHaveBeenRaw8() throws Exception {
byte[] longest = new byte[Byte.MAX_VALUE];
String longHex = toHexString(longest);
setContents("52" + "007F" + longHex);
r.readRaw();
}
@Test
public void testSkipRaw16() throws Exception {
byte[] shortest = new byte[Byte.MAX_VALUE + 1];
@@ -385,6 +469,14 @@ public class BdfReaderImplTest extends BrambleTestCase {
r.readRaw();
}
@Test(expected = FormatException.class)
public void testReadRaw32CouldHaveBeenRaw16() throws Exception {
byte[] longest = new byte[Short.MAX_VALUE];
String longHex = toHexString(longest);
setContents("54" + "00007FFF" + longHex);
r.readRaw();
}
@Test
public void testSkipRaw32() throws Exception {
byte[] shortest = new byte[Short.MAX_VALUE + 1];
@@ -434,25 +526,6 @@ public class BdfReaderImplTest extends BrambleTestCase {
r.readList();
}
@Test
public void testReadListManually() throws Exception {
// A list containing 1, "foo", and null
setContents("60" + "21" + "01" +
"41" + "03" + "666F6F" +
"00" + "80");
r.readListStart();
assertFalse(r.hasListEnd());
assertEquals(1, r.readLong());
assertFalse(r.hasListEnd());
assertEquals("foo", r.readString());
assertFalse(r.hasListEnd());
assertTrue(r.hasNull());
r.readNull();
assertTrue(r.hasListEnd());
r.readListEnd();
assertTrue(r.eof());
}
@Test
public void testSkipList() throws Exception {
// A list containing 1, "foo", and 128
@@ -465,9 +538,9 @@ public class BdfReaderImplTest extends BrambleTestCase {
@Test
public void testReadDictionary() throws Exception {
// A dictionary containing "foo" -> 123 and "bar" -> null
setContents("70" + "41" + "03" + "666F6F" + "21" + "7B" +
"41" + "03" + "626172" + "00" + "80");
// A dictionary containing "bar" -> null and "foo" -> 123
setContents("70" + "41" + "03" + "626172" + "00" +
"41" + "03" + "666F6F" + "21" + "7B" + "80");
BdfDictionary dictionary = r.readDictionary();
assertEquals(2, dictionary.size());
assertTrue(dictionary.containsKey("foo"));
@@ -517,26 +590,6 @@ public class BdfReaderImplTest extends BrambleTestCase {
r.readDictionary();
}
@Test
public void testReadDictionaryManually() throws Exception {
// A dictionary containing "foo" -> 123 and "bar" -> null
setContents("70" + "41" + "03" + "666F6F" + "21" + "7B" +
"41" + "03" + "626172" + "00" + "80");
r.readDictionaryStart();
assertFalse(r.hasDictionaryEnd());
assertEquals("foo", r.readString());
assertFalse(r.hasDictionaryEnd());
assertEquals(123, r.readLong());
assertFalse(r.hasDictionaryEnd());
assertEquals("bar", r.readString());
assertFalse(r.hasDictionaryEnd());
assertTrue(r.hasNull());
r.readNull();
assertTrue(r.hasDictionaryEnd());
r.readDictionaryEnd();
assertTrue(r.eof());
}
@Test
public void testSkipDictionary() throws Exception {
// A map containing "foo" -> 123 and "bar" -> null
@@ -557,10 +610,10 @@ public class BdfReaderImplTest extends BrambleTestCase {
@Test
public void testNestedListWithinDepthLimit() throws Exception {
// A list containing a list containing a list containing a list...
String lists = "";
for (int i = 1; i <= DEFAULT_NESTED_LIMIT; i++) lists += "60";
for (int i = 1; i <= DEFAULT_NESTED_LIMIT; i++) lists += "80";
setContents(lists);
StringBuilder lists = new StringBuilder();
for (int i = 1; i <= DEFAULT_NESTED_LIMIT; i++) lists.append("60");
for (int i = 1; i <= DEFAULT_NESTED_LIMIT; i++) lists.append("80");
setContents(lists.toString());
r.readList();
assertTrue(r.eof());
}
@@ -568,23 +621,25 @@ public class BdfReaderImplTest extends BrambleTestCase {
@Test(expected = FormatException.class)
public void testNestedListOutsideDepthLimit() throws Exception {
// A list containing a list containing a list containing a list...
String lists = "";
for (int i = 1; i <= DEFAULT_NESTED_LIMIT + 1; i++) lists += "60";
for (int i = 1; i <= DEFAULT_NESTED_LIMIT + 1; i++) lists += "80";
setContents(lists);
StringBuilder lists = new StringBuilder();
for (int i = 1; i <= DEFAULT_NESTED_LIMIT + 1; i++) lists.append("60");
for (int i = 1; i <= DEFAULT_NESTED_LIMIT + 1; i++) lists.append("80");
setContents(lists.toString());
r.readList();
}
@Test
public void testNestedDictionaryWithinDepthLimit() throws Exception {
// A dictionary containing a dictionary containing a dictionary...
String dicts = "";
for (int i = 1; i <= DEFAULT_NESTED_LIMIT; i++)
dicts += "70" + "41" + "03" + "666F6F";
dicts += "11";
for (int i = 1; i <= DEFAULT_NESTED_LIMIT; i++)
dicts += "80";
setContents(dicts);
StringBuilder dicts = new StringBuilder();
for (int i = 1; i <= DEFAULT_NESTED_LIMIT; i++) {
dicts.append("70").append("41").append("03").append("666F6F");
}
dicts.append("11");
for (int i = 1; i <= DEFAULT_NESTED_LIMIT; i++) {
dicts.append("80");
}
setContents(dicts.toString());
r.readDictionary();
assertTrue(r.eof());
}
@@ -592,13 +647,15 @@ public class BdfReaderImplTest extends BrambleTestCase {
@Test(expected = FormatException.class)
public void testNestedDictionaryOutsideDepthLimit() throws Exception {
// A dictionary containing a dictionary containing a dictionary...
String dicts = "";
for (int i = 1; i <= DEFAULT_NESTED_LIMIT + 1; i++)
dicts += "70" + "41" + "03" + "666F6F";
dicts += "11";
for (int i = 1; i <= DEFAULT_NESTED_LIMIT + 1; i++)
dicts += "80";
setContents(dicts);
StringBuilder dicts = new StringBuilder();
for (int i = 1; i <= DEFAULT_NESTED_LIMIT + 1; i++) {
dicts.append("70").append("41").append("03").append("666F6F");
}
dicts.append("11");
for (int i = 1; i <= DEFAULT_NESTED_LIMIT + 1; i++) {
dicts.append("80");
}
setContents(dicts.toString());
r.readDictionary();
}
@@ -625,6 +682,6 @@ public class BdfReaderImplTest extends BrambleTestCase {
private void setContents(String hex, int maxBufferSize)
throws FormatException {
ByteArrayInputStream in = new ByteArrayInputStream(fromHexString(hex));
r = new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT, maxBufferSize);
r = new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT, maxBufferSize, true);
}
}

View File

@@ -0,0 +1,80 @@
package org.briarproject.bramble.data;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfReader;
import org.briarproject.bramble.api.data.BdfWriter;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import static org.briarproject.bramble.api.data.BdfReader.DEFAULT_MAX_BUFFER_SIZE;
import static org.briarproject.bramble.api.data.BdfReader.DEFAULT_NESTED_LIMIT;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class BdfReaderWriterIntegrationTest extends BrambleTestCase {
@Test
public void testConvertStringToCanonicalForm() throws Exception {
// 'foo' as a STRING_16 (not canonical, should be a STRING_8)
String hexIn = "42" + "0003" + "666F6F";
InputStream in = new ByteArrayInputStream(fromHexString(hexIn));
BdfReader r = new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT,
DEFAULT_MAX_BUFFER_SIZE, false); // Accept non-canonical
String s = r.readString();
assertEquals("foo", s);
assertTrue(r.eof());
// Convert the string back to BDF
ByteArrayOutputStream out = new ByteArrayOutputStream();
BdfWriter w = new BdfWriterImpl(out);
w.writeString(s);
w.flush();
String hexOut = toHexString(out.toByteArray());
// The BDF should now be in canonical form
assertEquals("41" + "03" + "666F6F", hexOut);
}
@Test
public void testConvertDictionaryToCanonicalForm() throws Exception {
// A dictionary with keys in non-canonical order: 'foo' then 'bar'
String hexIn = "70" + "41" + "03" + "666F6F" + "21" + "01"
+ "41" + "03" + "626172" + "21" + "02" + "80";
InputStream in = new ByteArrayInputStream(fromHexString(hexIn));
BdfReader r = new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT,
DEFAULT_MAX_BUFFER_SIZE, false); // Accept non-canonical
BdfDictionary d = r.readDictionary();
assertEquals(2, d.size());
assertTrue(r.eof());
// The entries should be returned in canonical order
Iterator<Entry<String, Object>> it = d.entrySet().iterator();
Entry<String, Object> first = it.next();
assertEquals("bar", first.getKey());
assertEquals(2L, first.getValue());
Entry<String, Object> second = it.next();
assertEquals("foo", second.getKey());
assertEquals(1L, second.getValue());
// Convert a non-canonical map to BDF (use LinkedHashMap so we know
// the entries will be iterated over in non-canonical order)
Map<String, Object> m = new LinkedHashMap<>();
m.put("foo", 1);
m.put("bar", 2);
ByteArrayOutputStream out = new ByteArrayOutputStream();
BdfWriter w = new BdfWriterImpl(out);
w.writeDictionary(m);
w.flush();
String hexOut = toHexString(out.toByteArray());
// The entries should be in canonical order: 'bar' then 'foo'
assertEquals("70" + "41" + "03" + "626172" + "21" + "02"
+ "41" + "03" + "666F6F" + "21" + "01" + "80", hexOut);
}
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.data;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.util.StringUtils;
import org.junit.Test;
@@ -168,9 +169,11 @@ public class BdfWriterImplTest extends BrambleTestCase {
@Test
public void testWriteDictionary() throws IOException {
// Use LinkedHashMap to get predictable iteration order
// Add entries to dictionary in descending order - they should be
// output in ascending order. Use LinkedHashMap to get predictable
// iteration order
Map<String, Object> m = new LinkedHashMap<>();
for (int i = 0; i < 4; i++) m.put(String.valueOf(i), i);
for (int i = 3; i >= 0; i--) m.put(String.valueOf(i), i);
w.writeDictionary(m);
// DICTIONARY tag, keys as strings and values as integers, END tag
checkContents("70" + "41" + "01" + "30" + "21" + "00" +
@@ -180,30 +183,17 @@ public class BdfWriterImplTest extends BrambleTestCase {
}
@Test
public void testWriteDelimitedList() throws IOException {
w.writeListStart();
w.writeLong(1);
w.writeString("foo");
w.writeLong(128);
w.writeListEnd();
// LIST tag, 1 as integer, "foo" as string, 128 as integer, END tag
checkContents("60" + "21" + "01" +
"41" + "03" + "666F6F" +
"22" + "0080" + "80");
}
@Test
public void testWriteDelimitedDictionary() throws IOException {
w.writeDictionaryStart();
w.writeString("foo");
w.writeLong(123);
w.writeString("bar");
w.writeNull();
w.writeDictionaryEnd();
// DICTIONARY tag, "foo" as string, 123 as integer, "bar" as string,
// NULL tag, END tag
checkContents("70" + "41" + "03" + "666F6F" +
"21" + "7B" + "41" + "03" + "626172" + "00" + "80");
public void testWriteBdfDictionary() throws IOException {
// Add entries to dictionary in descending order - they should be
// output in ascending order
BdfDictionary d = new BdfDictionary();
for (int i = 3; i >= 0; i--) d.put(String.valueOf(i), i);
w.writeDictionary(d);
// DICTIONARY tag, keys as strings and values as integers, END tag
checkContents("70" + "41" + "01" + "30" + "21" + "00" +
"41" + "01" + "31" + "21" + "01" +
"41" + "01" + "32" + "21" + "02" +
"41" + "01" + "33" + "21" + "03" + "80");
}
@Test

View File

@@ -2,21 +2,27 @@ package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfReader;
import org.briarproject.bramble.api.data.BdfReaderFactory;
import org.briarproject.bramble.api.keyagreement.Payload;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.bramble.api.qrcode.WrongQrCodeTypeException;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.BETA_PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -26,32 +32,29 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
private final BdfReaderFactory bdfReaderFactory =
context.mock(BdfReaderFactory.class);
private final QrCodeClassifier qrCodeClassifier =
context.mock(QrCodeClassifier.class);
private final BdfReader bdfReader = context.mock(BdfReader.class);
private final PayloadParserImpl payloadParser =
new PayloadParserImpl(bdfReaderFactory);
private final String payload = getRandomString(123);
@Test(expected = FormatException.class)
public void testThrowsFormatExceptionIfPayloadIsEmpty() throws Exception {
payloadParser.parse(new byte[0]);
private final PayloadParserImpl payloadParser =
new PayloadParserImpl(bdfReaderFactory, qrCodeClassifier);
@Test(expected = WrongQrCodeTypeException.class)
public void testThrowsExceptionForWrongQrCodeType() throws Exception {
expectClassifyQrCode(payload, MAILBOX, QR_FORMAT_VERSION);
payloadParser.parse(payload);
}
@Test
public void testThrowsUnsupportedVersionExceptionForOldVersion()
throws Exception {
try {
payloadParser.parse(new byte[] {PROTOCOL_VERSION - 1});
fail();
} catch (UnsupportedVersionException e) {
assertTrue(e.isTooOld());
}
}
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION - 1);
@Test
public void testThrowsUnsupportedVersionExceptionForBetaVersion()
throws Exception {
try {
payloadParser.parse(new byte[] {BETA_PROTOCOL_VERSION});
payloadParser.parse(payload);
fail();
} catch (UnsupportedVersionException e) {
assertTrue(e.isTooOld());
@@ -61,8 +64,10 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
@Test
public void testThrowsUnsupportedVersionExceptionForNewVersion()
throws Exception {
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION + 1);
try {
payloadParser.parse(new byte[] {PROTOCOL_VERSION + 1});
payloadParser.parse(payload);
fail();
} catch (UnsupportedVersionException e) {
assertFalse(e.isTooOld());
@@ -71,6 +76,8 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
@Test(expected = FormatException.class)
public void testThrowsFormatExceptionForEmptyList() throws Exception {
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class)));
@@ -79,7 +86,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(new BdfList()));
}});
payloadParser.parse(new byte[] {PROTOCOL_VERSION});
payloadParser.parse(payload);
}
@Test(expected = FormatException.class)
@@ -87,6 +94,8 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
throws Exception {
byte[] commitment = getRandomBytes(COMMIT_LENGTH);
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class)));
@@ -97,7 +106,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(false));
}});
payloadParser.parse(new byte[] {PROTOCOL_VERSION});
payloadParser.parse(payload);
}
@Test(expected = FormatException.class)
@@ -105,6 +114,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
throws Exception {
byte[] commitment = getRandomBytes(COMMIT_LENGTH - 1);
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class)));
@@ -115,7 +125,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(true));
}});
payloadParser.parse(new byte[] {PROTOCOL_VERSION});
payloadParser.parse(payload);
}
@Test(expected = FormatException.class)
@@ -123,6 +133,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
throws Exception {
byte[] commitment = getRandomBytes(COMMIT_LENGTH + 1);
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class)));
@@ -133,12 +144,14 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(true));
}});
payloadParser.parse(new byte[] {PROTOCOL_VERSION});
payloadParser.parse(payload);
}
@Test
public void testAcceptsPayloadWithNoDescriptors() throws Exception {
byte[] commitment = getRandomBytes(COMMIT_LENGTH);
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class)));
@@ -149,8 +162,16 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(true));
}});
Payload p = payloadParser.parse(new byte[] {PROTOCOL_VERSION});
Payload p = payloadParser.parse(payload);
assertArrayEquals(commitment, p.getCommitment());
assertTrue(p.getTransportDescriptors().isEmpty());
}
private void expectClassifyQrCode(String payload, QrCodeType qrCodeType,
int formatVersion) {
context.checking(new Expectations() {{
oneOf(qrCodeClassifier).classifyQrCode(payload);
will(returnValue(new Pair<>(qrCodeType, formatVersion)));
}});
}
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
@@ -9,17 +10,23 @@ import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEven
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.util.Base32;
import org.junit.Test;
import java.io.IOException;
import java.util.Locale;
import java.util.Random;
import java.util.concurrent.Executor;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.hasEvent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class MailboxManagerImplTest extends BrambleMockTestCase {
@@ -125,4 +132,32 @@ public class MailboxManagerImplTest extends BrambleMockTestCase {
assertTrue(manager.checkConnection());
}
@Test
public void testConvertBase32Payload() throws FormatException {
byte[] payload = getRandomBytes(65);
String base32payload = Base32.encode(payload).toLowerCase(Locale.ROOT);
String expected = new String(payload, ISO_8859_1);
try {
manager.convertBase32Payload("foo bar");
fail();
} catch (FormatException e) {
// expected
}
try { // doesn't work with shorter link
manager.convertBase32Payload("briar-mailbox://" +
base32payload.substring(0, base32payload.length() - 1));
fail();
} catch (FormatException e) {
// expected
}
// works with white-spaces
assertEquals(expected, manager.convertBase32Payload(
"foo bar briar-mailbox://" + base32payload + " foo bar"));
// even works without white-space at the end
assertEquals(expected, manager.convertBase32Payload(
"foo bar briar-mailbox://" + base32payload + "foobar"));
// even works without schema and extra chars at end
assertEquals(expected, manager.convertBase32Payload(
"foo bar " + base32payload + "foobar"));
}
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -7,13 +8,24 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.ConnectionError;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.InvalidQrCode;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.MailboxAlreadyPaired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Paired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Pairing;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.QrCodeReceived;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.UnexpectedError;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxAlreadyPairedException;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.ImmediateExecutor;
@@ -27,6 +39,9 @@ import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.bramble.mailbox.MailboxTestUtils.getQrCodePayload;
import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
@@ -48,9 +63,8 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
context.mock(MailboxSettingsManager.class);
private final MailboxUpdateManager mailboxUpdateManager =
context.mock(MailboxUpdateManager.class);
private final MailboxPairingTaskFactory factory =
new MailboxPairingTaskFactoryImpl(executor, db, crypto, clock, api,
mailboxSettingsManager, mailboxUpdateManager);
private final QrCodeClassifier qrCodeClassifier =
context.mock(QrCodeClassifier.class);
private final String onion = getRandomString(56);
private final byte[] onionBytes = getRandomBytes(32);
@@ -68,32 +82,50 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
@Test
public void testInitialQrCodeReceivedState() {
MailboxPairingTask task =
factory.createPairingTask(getRandomString(42));
MailboxPairingTask task = createPairingTask(getRandomString(42));
task.addObserver(state ->
assertTrue(state instanceof MailboxPairingState.QrCodeReceived)
);
assertTrue(state instanceof QrCodeReceived));
}
@Test
public void testInvalidQrCode() {
MailboxPairingTask task1 =
factory.createPairingTask(getRandomString(42));
task1.run();
task1.addObserver(state ->
assertTrue(state instanceof MailboxPairingState.InvalidQrCode)
);
public void testInvalidQrCodeType() {
String payload = getRandomString(65);
MailboxPairingTask task = createPairingTask(payload);
String goodLength = "00" + getRandomString(63);
MailboxPairingTask task2 = factory.createPairingTask(goodLength);
task2.run();
task2.addObserver(state ->
assertTrue(state instanceof MailboxPairingState.InvalidQrCode)
);
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
task.run();
task.addObserver(state ->
assertTrue(state instanceof InvalidQrCode));
}
@Test
public void testInvalidQrCodeVersion() {
String payload = getRandomString(65);
MailboxPairingTask task = createPairingTask(payload);
expectClassifyQrCode(payload, MAILBOX, QR_FORMAT_VERSION + 1);
task.run();
task.addObserver(state ->
assertTrue(state instanceof InvalidQrCode));
}
@Test
public void testInvalidQrCodeLength() {
String payload = getRandomString(42);
MailboxPairingTask task = createPairingTask(payload);
expectClassifyQrCode(payload, MAILBOX, QR_FORMAT_VERSION);
task.run();
task.addObserver(state ->
assertTrue(state instanceof InvalidQrCode));
}
@Test
public void testSuccessfulPairing() throws Exception {
expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION);
context.checking(new Expectations() {{
oneOf(crypto).encodeOnion(onionBytes);
will(returnValue(onion));
@@ -121,17 +153,14 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
}});
AtomicInteger i = new AtomicInteger(0);
MailboxPairingTask task = factory.createPairingTask(validPayload);
MailboxPairingTask task = createPairingTask(validPayload);
task.addObserver(state -> {
if (i.get() == 0) {
assertEquals(MailboxPairingState.QrCodeReceived.class,
state.getClass());
assertEquals(QrCodeReceived.class, state.getClass());
} else if (i.get() == 1) {
assertEquals(MailboxPairingState.Pairing.class,
state.getClass());
assertEquals(Pairing.class, state.getClass());
} else if (i.get() == 2) {
assertEquals(MailboxPairingState.Paired.class,
state.getClass());
assertEquals(Paired.class, state.getClass());
} else fail("Unexpected change of state " + state.getClass());
i.getAndIncrement();
});
@@ -140,24 +169,23 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
@Test
public void testAlreadyPaired() throws Exception {
testApiException(new MailboxApi.MailboxAlreadyPairedException(),
MailboxPairingState.MailboxAlreadyPaired.class);
testApiException(new MailboxAlreadyPairedException(),
MailboxAlreadyPaired.class);
}
@Test
public void testMailboxApiException() throws Exception {
testApiException(new MailboxApi.ApiException(),
MailboxPairingState.UnexpectedError.class);
testApiException(new ApiException(), UnexpectedError.class);
}
@Test
public void testApiIOException() throws Exception {
testApiException(new IOException(),
MailboxPairingState.ConnectionError.class);
testApiException(new IOException(), ConnectionError.class);
}
private void testApiException(Exception e,
Class<? extends MailboxPairingState> s) throws Exception {
expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION);
context.checking(new Expectations() {{
oneOf(crypto).encodeOnion(onionBytes);
will(returnValue(onion));
@@ -165,13 +193,14 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
will(throwException(e));
}});
MailboxPairingTask task = factory.createPairingTask(validPayload);
MailboxPairingTask task = createPairingTask(validPayload);
task.run();
task.addObserver(state -> assertEquals(state.getClass(), s));
}
@Test
public void testDbException() throws Exception {
expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION);
context.checking(new Expectations() {{
oneOf(crypto).encodeOnion(onionBytes);
will(returnValue(onion));
@@ -188,10 +217,10 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
will(throwException(new DbException()));
}});
MailboxPairingTask task = factory.createPairingTask(validPayload);
MailboxPairingTask task = createPairingTask(validPayload);
task.run();
task.addObserver(state -> assertEquals(state.getClass(),
MailboxPairingState.UnexpectedError.class));
task.addObserver(state ->
assertEquals(state.getClass(), UnexpectedError.class));
}
private PredicateMatcher<MailboxProperties> matches(MailboxProperties p2) {
@@ -202,4 +231,22 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
p1.getServerSupports().equals(p2.getServerSupports()));
}
private MailboxPairingTask createPairingTask(String qrCodePayload) {
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(time));
}});
return new MailboxPairingTaskImpl(qrCodePayload, executor, db,
crypto, clock, api, mailboxSettingsManager,
mailboxUpdateManager, qrCodeClassifier);
}
private void expectClassifyQrCode(String payload, QrCodeType qrCodeType,
int formatVersion) {
context.checking(new Expectations() {{
oneOf(qrCodeClassifier).classifyQrCode(payload);
will(returnValue(new Pair<>(qrCodeType, formatVersion)));
}});
}
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.WeakSingletonProvider;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import javax.annotation.Nonnull;
import javax.net.SocketFactory;
@@ -11,20 +10,24 @@ import javax.net.SocketFactory;
import okhttp3.OkHttpClient;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_ID;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
class MailboxTestUtils {
static String getQrCodePayload(byte[] onionBytes, byte[] setupToken) {
int formatIdAndVersion = (QR_FORMAT_ID << 5) | QR_FORMAT_VERSION;
byte[] payloadBytes = ByteBuffer.allocate(65)
.put((byte) 32) // 1
.put((byte) formatIdAndVersion) // 1
.put(onionBytes) // 32
.put(setupToken) // 32
.array();
//noinspection CharsetObjectCanBeUsed
return new String(payloadBytes, Charset.forName("ISO-8859-1"));
return new String(payloadBytes, ISO_8859_1);
}
// Used by mailbox integration tests
static String getQrCodePayload(byte[] setupToken) {
return getQrCodePayload(getRandomId(), setupToken);
}

View File

@@ -217,13 +217,13 @@ public class LanTcpPluginTest extends BrambleTestCase {
// The plugin should have bound a socket and stored the port number
BdfList descriptor = kal.getDescriptor();
assertEquals(3, descriptor.size());
assertEquals(TRANSPORT_ID_LAN, descriptor.getLong(0).longValue());
assertEquals(TRANSPORT_ID_LAN, descriptor.getInt(0).intValue());
byte[] address = descriptor.getRaw(1);
InetAddress addr = InetAddress.getByAddress(address);
assertTrue(addr instanceof Inet4Address);
assertFalse(addr.isLoopbackAddress());
assertTrue(addr.isLinkLocalAddress() || addr.isSiteLocalAddress());
int port = descriptor.getLong(2).intValue();
int port = descriptor.getInt(2);
assertTrue(port > 0 && port < 65536);
// The plugin should be listening on the port
InetSocketAddress socketAddr = new InetSocketAddress(addr, port);

View File

@@ -404,7 +404,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
localGroup.getId());
will(returnValue(messageMetadata));
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId, false);
will(returnValue(fooUpdate));
oneOf(clientHelper).parseAndValidateTransportProperties(
fooPropertiesDict);
@@ -471,7 +471,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup2.getId());
will(returnValue(messageMetadata));
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId, false);
will(returnValue(fooUpdate));
oneOf(clientHelper).parseAndValidateTransportProperties(
fooPropertiesDict);
@@ -526,7 +526,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(messageMetadata));
oneOf(clientHelper).getMessageAsList(txn, updateId);
oneOf(clientHelper).getMessageAsList(txn, updateId, false);
will(returnValue(update));
oneOf(clientHelper).parseAndValidateTransportProperties(
fooPropertiesDict);
@@ -564,7 +564,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
localGroup.getId());
will(returnValue(messageMetadata));
oneOf(clientHelper).getMessageAsList(txn, updateId);
oneOf(clientHelper).getMessageAsList(txn, updateId, false);
will(returnValue(update));
oneOf(clientHelper).parseAndValidateTransportProperties(
fooPropertiesDict);
@@ -695,7 +695,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
localGroup.getId());
will(returnValue(localGroupMessageMetadata));
oneOf(clientHelper).getMessageAsList(txn, localGroupUpdateId);
oneOf(clientHelper).getMessageAsList(txn, localGroupUpdateId,
false);
will(returnValue(oldUpdate));
oneOf(clientHelper).parseAndValidateTransportProperties(
oldPropertiesDict);
@@ -760,7 +761,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
localGroup.getId());
will(returnValue(localGroupMessageMetadata));
oneOf(clientHelper).getMessageAsList(txn, localGroupUpdateId);
oneOf(clientHelper).getMessageAsList(txn, localGroupUpdateId,
false);
will(returnValue(oldUpdate));
oneOf(clientHelper).parseAndValidateTransportProperties(
oldPropertiesDict);
@@ -819,12 +821,12 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
localGroup.getId());
will(returnValue(messageMetadata));
// Retrieve and parse the latest local properties
oneOf(clientHelper).getMessageAsList(txn, fooVersion999);
oneOf(clientHelper).getMessageAsList(txn, fooVersion999, false);
will(returnValue(fooUpdate));
oneOf(clientHelper).parseAndValidateTransportProperties(
fooPropertiesDict);
will(returnValue(fooProperties));
oneOf(clientHelper).getMessageAsList(txn, barVersion3);
oneOf(clientHelper).getMessageAsList(txn, barVersion3, false);
will(returnValue(barUpdate));
oneOf(clientHelper).parseAndValidateTransportProperties(
barPropertiesDict);

View File

@@ -0,0 +1,70 @@
package org.briarproject.bramble.qrcode;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConstants;
import org.briarproject.bramble.api.mailbox.MailboxConstants;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.UNKNOWN;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
import static org.junit.Assert.assertEquals;
public class QrCodeClassifierImplTest extends BrambleTestCase {
private final QrCodeClassifier classifier = new QrCodeClassifierImpl();
@Test
public void testClassifiesEmptyStringAsUnknown() {
Pair<QrCodeType, Integer> result = classifier.classifyQrCode("");
assertEquals(UNKNOWN, result.getFirst());
assertEquals(0, result.getSecond().intValue());
}
@Test
public void testClassifiesKeyAgreement() {
byte[] payloadBytes = getRandomBytes(123);
for (int version = 0; version < 32; version++) {
int typeAndVersion =
(KeyAgreementConstants.QR_FORMAT_ID << 5) | version;
payloadBytes[0] = (byte) typeAndVersion;
String payload = new String(payloadBytes, ISO_8859_1);
Pair<QrCodeType, Integer> result =
classifier.classifyQrCode(payload);
assertEquals(BQP, result.getFirst());
assertEquals(version, result.getSecond().intValue());
}
}
@Test
public void testClassifiesMailbox() {
byte[] payloadBytes = getRandomBytes(123);
for (int version = 0; version < 32; version++) {
int typeAndVersion =
(MailboxConstants.QR_FORMAT_ID << 5) | version;
payloadBytes[0] = (byte) typeAndVersion;
String payload = new String(payloadBytes, ISO_8859_1);
Pair<QrCodeType, Integer> result =
classifier.classifyQrCode(payload);
assertEquals(MAILBOX, result.getFirst());
assertEquals(version, result.getSecond().intValue());
}
}
@Test
public void testClassifiesUnknownFormatIdAsUnknown() {
byte[] payloadBytes = getRandomBytes(123);
int unknownFormatId = MailboxConstants.QR_FORMAT_ID + 1;
int typeAndVersion = unknownFormatId << 5;
payloadBytes[0] = (byte) typeAndVersion;
String payload = new String(payloadBytes, ISO_8859_1);
Pair<QrCodeType, Integer> result = classifier.classifyQrCode(payload);
assertEquals(UNKNOWN, result.getFirst());
assertEquals(0, result.getSecond().intValue());
}
}

View File

@@ -6,13 +6,18 @@ import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.Priority;
import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.SyncRecordReader;
import org.briarproject.bramble.api.sync.Versions;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.PredicateMatcher;
import org.hamcrest.Matcher;
import org.jmock.Expectations;
import org.junit.Test;
@@ -23,12 +28,15 @@ import javax.annotation.Nullable;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
import static org.briarproject.bramble.api.sync.RecordTypes.PRIORITY;
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
import static org.briarproject.bramble.api.sync.RecordTypes.VERSIONS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_SUPPORTED_VERSIONS;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PRIORITY_NONCE_BYTES;
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
@@ -46,6 +54,38 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
private final SyncRecordReader reader =
new SyncRecordReaderImpl(messageFactory, recordReader);
@Test
public void testNoFormatExceptionIfMessageIsMinimumSize() throws Exception {
expectReadRecord(createMessage(MESSAGE_HEADER_LENGTH + 1));
expectCreateMessage(1);
reader.readMessage();
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfMessageIsTooSmall() throws Exception {
expectReadRecord(createMessage(MESSAGE_HEADER_LENGTH));
reader.readMessage();
}
@Test
public void testNoFormatExceptionIfMessageIsMaximumSize() throws Exception {
expectReadRecord(createMessage(MESSAGE_HEADER_LENGTH
+ MAX_MESSAGE_BODY_LENGTH));
expectCreateMessage(MAX_MESSAGE_BODY_LENGTH);
reader.readMessage();
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfMessageIsTooLarge() throws Exception {
expectReadRecord(createMessage(MESSAGE_HEADER_LENGTH
+ MAX_MESSAGE_BODY_LENGTH + 1));
reader.readMessage();
}
@Test
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
expectReadRecord(createAck());
@@ -158,6 +198,20 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
assertTrue(reader.eof());
}
private void expectCreateMessage(int bodyLength) {
MessageId messageId = new MessageId(getRandomId());
GroupId groupId = new GroupId(getRandomId());
long timestamp = System.currentTimeMillis();
context.checking(new Expectations() {{
Matcher<byte[]> matcher = new PredicateMatcher<>(byte[].class,
b -> b.length == MESSAGE_HEADER_LENGTH + bodyLength);
oneOf(messageFactory).createMessage(with(matcher));
will(returnValue(new Message(messageId, groupId, timestamp,
new byte[bodyLength])));
}});
}
private void expectReadRecord(@Nullable Record record) throws Exception {
context.checking(new Expectations() {{
//noinspection unchecked
@@ -167,6 +221,10 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
}});
}
private Record createMessage(int payloadLength) {
return new Record(PROTOCOL_VERSION, MESSAGE, new byte[payloadLength]);
}
private Record createAck() throws Exception {
return new Record(PROTOCOL_VERSION, ACK, createPayload());
}

View File

@@ -88,51 +88,58 @@ public class StringUtilsTest extends BrambleTestCase {
}
@Test
public void testFromUtf8AcceptsNullCharacterUsingStandardUtf8() {
public void testFromUtf8AcceptsNullCharacterUsingStandardUtf8()
throws Exception {
// The UTF-8 encoding of the null character is valid
assertEquals("\u0000", StringUtils.fromUtf8(new byte[1]));
byte[] utf8 = new byte[1];
String actual = StringUtils.fromUtf8(utf8);
assertEquals("\u0000", actual);
// When we convert back to UTF-8 we should get the original encoding
assertArrayEquals(utf8, StringUtils.toUtf8(actual));
}
@Test
public void testFromUtf8RemovesNullCharacterUsingModifiedUtf8() {
@Test(expected = FormatException.class)
public void testFromUtf8RejectsNullCharacterUsingModifiedUtf8()
throws Exception {
// The modified UTF-8 encoding of the null character is not valid
byte[] b = new byte[] {
(byte) 0xC0, (byte) 0x80, // Null character as modified UTF-8
(byte) 0xC8, (byte) 0x85 // U+0205
};
// Conversion should ignore the invalid character and return the rest
String expected = "\u0205";
assertEquals(expected, StringUtils.fromUtf8(b));
StringUtils.fromUtf8(b);
}
@Test
public void testFromUtf8AcceptsSupplementaryCharacterUsingStandardUtf8() {
public void testFromUtf8AcceptsSupplementaryCharacterUsingStandardUtf8()
throws Exception {
// The UTF-8 encoding of a supplementary character is valid and should
// be converted to a surrogate pair
byte[] b = new byte[] {
byte[] utf8 = new byte[] {
(byte) 0xF0, (byte) 0x90, (byte) 0x90, (byte) 0x80, // U+10400
(byte) 0xC8, (byte) 0x85 // U+0205
};
String expected = "\uD801\uDC00\u0205"; // Surrogate pair
assertEquals(expected, StringUtils.fromUtf8(b));
String actual = StringUtils.fromUtf8(utf8);
assertEquals(expected, actual);
// When we convert back to UTF-8 we should get the original encoding
assertArrayEquals(utf8, StringUtils.toUtf8(actual));
}
@Test
public void testFromUtf8RemovesSupplementaryCharacterUsingModifiedUtf8() {
@Test(expected = FormatException.class)
public void testFromUtf8RejectsSupplementaryCharacterUsingModifiedUtf8()
throws Exception {
// The CESU-8 or modified UTF-8 encoding of a supplementary character
// is not valid
byte[] b = new byte[] {
byte[] utf8 = new byte[] {
(byte) 0xED, (byte) 0xA0, (byte) 0x81, // U+10400 as CSEU-8
(byte) 0xED, (byte) 0xB0, (byte) 0x80,
(byte) 0xC8, (byte) 0x85 // U+0205
};
// Conversion should ignore the invalid character and return the rest
String expected = "\u0205";
assertEquals(expected, StringUtils.fromUtf8(b));
StringUtils.fromUtf8(utf8);
}
@Test
public void testFromUtf8EmptyInput() {
public void testFromUtf8EmptyInput() throws Exception {
assertEquals("", StringUtils.fromUtf8(new byte[0]));
}

View File

@@ -7,10 +7,6 @@ apply plugin: 'witness'
apply from: 'witness.gradle'
apply from: '../dagger.gradle'
configurations {
tor
}
dependencies {
implementation project(':bramble-core')
@@ -19,12 +15,9 @@ dependencies {
implementation "net.java.dev.jna:jna:$jna_version"
implementation "net.java.dev.jna:jna-platform:$jna_version"
tor "org.briarproject:tor-linux:$tor_version"
tor "org.briarproject:tor-windows:$tor_version"
tor "org.briarproject:obfs4proxy-linux:$obfs4proxy_version"
tor "org.briarproject:obfs4proxy-windows:$obfs4proxy_version"
tor "org.briarproject:snowflake-linux:$snowflake_version"
tor "org.briarproject:snowflake-windows:$snowflake_version"
testImplementation "org.briarproject:tor-linux:$tor_version"
testImplementation "org.briarproject:obfs4proxy-linux:$obfs4proxy_version"
testImplementation "org.briarproject:snowflake-linux:$snowflake_version"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
@@ -39,31 +32,6 @@ dependencies {
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
}
def torBinariesDir = 'src/main/resources'
task cleanTorBinaries {
doLast {
delete fileTree(torBinariesDir) { include '*.zip' }
}
}
clean.dependsOn cleanTorBinaries
task unpackTorBinaries {
doLast {
copy {
from configurations.tor.collect { zipTree(it) }
into torBinariesDir
}
}
dependsOn cleanTorBinaries
}
processResources {
inputs.dir torBinariesDir
dependsOn unpackTorBinaries
}
tasks.withType(Test) {
systemProperty 'java.library.path', 'libs'
}

View File

@@ -12,7 +12,6 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
@@ -33,6 +32,7 @@ import static java.util.logging.Level.WARNING;
import static jssc.SerialPort.PURGE_RXCLEAR;
import static jssc.SerialPort.PURGE_TXCLEAR;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.US_ASCII;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -41,7 +41,6 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
private static final Logger LOG =
Logger.getLogger(ModemImpl.class.getName());
private static final Charset US_ASCII = Charset.forName("US-ASCII");
private static final int MAX_LINE_LENGTH = 256;
private static final int[] BAUD_RATES = {
256000, 128000, 115200, 57600, 38400, 19200, 14400, 9600, 4800, 1200

View File

@@ -62,9 +62,9 @@ public class UnixTorPluginFactory extends TorPluginFactory {
if (LOG.isLoggable(INFO)) {
LOG.info("System's os.arch is " + arch);
}
if (arch.equals("amd64")) return "linux-x86_64";
else if (arch.equals("aarch64")) return "linux-aarch64";
else if (arch.equals("arm")) return "linux-armhf";
if (arch.equals("amd64")) return "x86_64";
else if (arch.equals("aarch64")) return "aarch64";
else if (arch.equals("arm")) return "armhf";
return null;
}

View File

@@ -94,4 +94,8 @@ class WindowsTorPlugin extends JavaTorPlugin {
if (!success.take()) throw new PluginException();
}
@Override
protected String getExecutableExtension() {
return ".exe";
}
}

View File

@@ -62,7 +62,7 @@ public class WindowsTorPluginFactory extends TorPluginFactory {
if (LOG.isLoggable(INFO)) {
LOG.info("System's os.arch is " + arch);
}
if (arch.equals("amd64")) return "windows-x86_64";
if (arch.equals("amd64")) return "x86_64";
return null;
}

View File

@@ -0,0 +1,40 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.util.OsUtils;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assume.assumeTrue;
public class TestResources {
@Before
public void isRunningOnLinux() {
assumeTrue(OsUtils.isLinux());
}
@Test
public void canReadTorLinux() {
InputStream input = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("x86_64/tor");
assertNotNull(input);
}
@Test
public void canReadObfs4ProxyLinux() {
InputStream input = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("x86_64/obfs4proxy");
assertNotNull(input);
}
@Test
public void canReadSnowflakeLinux() {
InputStream input = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("x86_64/snowflake");
assertNotNull(input);
}
}

View File

@@ -26,12 +26,9 @@ dependencyVerification {
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.briarproject:obfs4proxy-linux:0.0.14-tor1:obfs4proxy-linux-0.0.14-tor1.jar:9783b9c7ec588a5246f534a9c5782783c8c9821825f81c3e0c6f1ecee61cfcbb',
'org.briarproject:obfs4proxy-windows:0.0.14-tor1:obfs4proxy-windows-0.0.14-tor1.jar:9dd122b31b3cd1616f168091dcdb01de049d1e052fe5c089b7627618a8a2694b',
'org.briarproject:snowflake-linux:2.3.1:snowflake-linux-2.3.1.jar:99ecf4546d8f79eb8408168c09380fec596558ac934554bf7d4247ea7ef2c9f3',
'org.briarproject:snowflake-windows:2.3.1:snowflake-windows-2.3.1.jar:d011f1a72c00a221f56380c19aad8ff11db8c2bb1adb0784125572d80b4d275a',
'org.briarproject:tor-linux:0.4.5.14:tor-linux-0.4.5.14.jar:1844e54cf6df0c85cec219381a3364c759ae444a6b63f7558b757becb7d41d08',
'org.briarproject:tor-windows:0.4.5.14:tor-windows-0.4.5.14.jar:d337afa1043f0cfa7e6e8c2473d682a5663a2c8052bb97a770450893c78c9b4f',
'org.briarproject:obfs4proxy-linux:0.0.14-tor2:obfs4proxy-linux-0.0.14-tor2.jar:bb2431092b5ad998ad620b0223e725c0f7e43f1b02af2f097a2544edc1fd9738',
'org.briarproject:snowflake-linux:2.5.1:snowflake-linux-2.5.1.jar:edc807dcb7758365970d95525e4749349a27f462d0e2df6505ad1ca65fb296d2',
'org.briarproject:tor-linux:0.4.7.13-2:tor-linux-0.4.7.13-2.jar:1e4ca9e0f724e1f17fcce570832704942cc3be26c4c2eccbe5aae29f35afa307',
'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a',
'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',

View File

@@ -26,8 +26,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 31
versionCode 10418
versionName "1.4.18"
versionCode 10423
versionName "1.4.23"
applicationId "org.briarproject.briar.android"
buildConfigField "String", "TorVersion", "\"$tor_version\""

View File

@@ -52,7 +52,6 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResul
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
import static org.briarproject.bramble.util.AndroidUtils.isUiThread;
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_OLD_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID;
@@ -141,11 +140,6 @@ public class BriarService extends Service {
ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET);
ongoingChannel.setShowBadge(false);
nm.createNotificationChannel(ongoingChannel);
// Delete the unused channel previously used for startup
// failure notifications
// TODO: Remove this ID after a reasonable upgrade period
// (added 2021-07-12)
nm.deleteNotificationChannel(FAILURE_CHANNEL_ID);
}
Notification foregroundNotification =
notificationManager.getForegroundNotification();

View File

@@ -41,7 +41,6 @@ import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.util.HtmlUtils.ARTICLE;
@NotNullByDefault
abstract class BaseViewModel extends DbViewModel implements EventListener {
@@ -115,7 +114,7 @@ abstract class BaseViewModel extends DbViewModel implements EventListener {
@DatabaseExecutor
private String getPostText(Transaction txn, MessageId m)
throws DbException {
return HtmlUtils.clean(blogManager.getPostText(txn, m), ARTICLE);
return HtmlUtils.cleanArticle(blogManager.getPostText(txn, m));
}
LiveData<LiveResult<BlogPostItem>> loadBlogPost(GroupId g, MessageId m) {

View File

@@ -3,6 +3,7 @@ package org.briarproject.briar.android.blog;
import android.content.Context;
import android.content.Intent;
import android.text.Spanned;
import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -170,7 +171,12 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
// TODO make author clickable #624
text.setText(c.getComment());
if (fullText) text.setTextIsSelectable(true);
Linkify.addLinks(text, Linkify.WEB_URLS);
text.setMovementMethod(null);
if (fullText) {
text.setTextIsSelectable(true);
makeLinksClickable(text, listener::onLinkClick);
}
commentContainer.addView(v);
}

View File

@@ -1,24 +1,28 @@
package org.briarproject.briar.android.blog;
import android.os.Bundle;
import android.widget.Toast;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.blog.RssImportResult.FileImportError;
import org.briarproject.briar.android.blog.RssImportResult.FileImportSuccess;
import org.briarproject.briar.android.blog.RssImportResult.UrlImportError;
import org.briarproject.briar.android.blog.RssImportResult.UrlImportSuccess;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.fragment.ErrorFragment;
import org.briarproject.nullsafety.MethodsNotNullByDefault;
import org.briarproject.nullsafety.ParametersNotNullByDefault;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.EXISTS;
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.FAILED;
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.IMPORTED;
import static androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
import static org.briarproject.briar.android.util.UiUtils.showFragment;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -50,24 +54,29 @@ public class RssFeedActivity extends BriarActivity
viewModel.getImportResult().observeEvent(this, this::onImportResult);
}
private void onImportResult(RssFeedViewModel.ImportResult result) {
if (result == IMPORTED) {
FragmentManager fm = getSupportFragmentManager();
private void onImportResult(@Nullable RssImportResult result) {
FragmentManager fm = getSupportFragmentManager();
if (result instanceof UrlImportSuccess) {
if (fm.findFragmentByTag(RssFeedImportFragment.TAG) != null) {
onBackPressed();
}
} else if (result == FAILED) {
String url = viewModel.getUrlFailedImport();
if (url == null) {
throw new AssertionError();
}
} else if (result instanceof UrlImportError) {
String url = ((UrlImportError) result).url;
RssFeedImportFailedDialogFragment dialog =
RssFeedImportFailedDialogFragment.newInstance(url);
dialog.show(getSupportFragmentManager(),
RssFeedImportFailedDialogFragment.TAG);
} else if (result == EXISTS) {
Toast.makeText(this, R.string.blogs_rss_feeds_import_exists,
Toast.LENGTH_LONG).show();
dialog.show(fm, RssFeedImportFailedDialogFragment.TAG);
} else if (result instanceof FileImportSuccess) {
// pop stack back to before the initial import fragment
fm.popBackStackImmediate(RssFeedImportFragment.TAG,
POP_BACK_STACK_INCLUSIVE);
} else if (result instanceof FileImportError) {
// pop stack back to initial import fragment
fm.popBackStackImmediate(RssFeedImportFragment.TAG, 0);
// show error fragment
Fragment f = ErrorFragment.newInstance(
getString(R.string.blogs_rss_feeds_import_error));
String tag = ErrorFragment.TAG;
showFragment(fm, f, tag);
}
}
}

View File

@@ -28,8 +28,7 @@ class RssFeedAdapter extends ListAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
super(new DiffUtil.ItemCallback<Feed>() {
@Override
public boolean areItemsTheSame(Feed a, Feed b) {
return a.getUrl().equals(b.getUrl()) &&
a.getBlogId().equals(b.getBlogId()) &&
return a.getBlogId().equals(b.getBlogId()) &&
a.getAdded() == b.getAdded();
}
@@ -86,8 +85,8 @@ class RssFeedAdapter extends ListAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
delete.setOnClickListener(v -> listener.onDeleteClick(item));
// Author
if (item.getRssAuthor() != null) {
author.setText(item.getRssAuthor());
if (item.getProperties().getAuthor() != null) {
author.setText(item.getProperties().getAuthor());
author.setVisibility(VISIBLE);
authorLabel.setVisibility(VISIBLE);
} else {
@@ -100,8 +99,8 @@ class RssFeedAdapter extends ListAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
updated.setText(formatDate(ctx, item.getUpdated()));
// Description
if (item.getDescription() != null) {
description.setText(item.getDescription());
if (item.getProperties().getDescription() != null) {
description.setText(item.getProperties().getDescription());
description.setVisibility(VISIBLE);
} else {
description.setVisibility(GONE);

View File

@@ -1,9 +1,13 @@
package org.briarproject.briar.android.blog;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
@@ -13,18 +17,27 @@ import android.widget.ProgressBar;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.ProgressFragment;
import org.briarproject.briar.android.util.ActivityLaunchers.GetContentAdvanced;
import org.briarproject.briar.android.util.ActivityLaunchers.OpenDocumentAdvanced;
import org.briarproject.nullsafety.MethodsNotNullByDefault;
import org.briarproject.nullsafety.ParametersNotNullByDefault;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
import static org.briarproject.briar.android.util.UiUtils.launchActivityToOpenFile;
import static org.briarproject.briar.android.util.UiUtils.showFragment;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -39,6 +52,15 @@ public class RssFeedImportFragment extends BaseFragment {
private Button importButton;
private ProgressBar progressBar;
@RequiresApi(19)
private final ActivityResultLauncher<String[]> docLauncher =
registerForActivityResult(new OpenDocumentAdvanced(),
this::onFileChosen);
private final ActivityResultLauncher<String> contentLauncher =
registerForActivityResult(new GetContentAdvanced(),
this::onFileChosen);
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
@@ -52,6 +74,7 @@ public class RssFeedImportFragment extends BaseFragment {
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
requireActivity().setTitle(getString(R.string.blogs_rss_feeds_import));
if (SDK_INT >= 19) setHasOptionsMenu(true);
View v = inflater.inflate(R.layout.fragment_rss_feed_import,
container, false);
@@ -92,11 +115,40 @@ public class RssFeedImportFragment extends BaseFragment {
return v;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (SDK_INT >= 19) {
inflater.inflate(R.menu.rss_feed_import_actions, menu);
}
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_import_file && SDK_INT >= 19) {
launchActivityToOpenFile(requireContext(), docLauncher,
contentLauncher, "*/*");
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public String getUniqueTag() {
return TAG;
}
private void onFileChosen(@Nullable Uri uri) {
if (uri == null) return;
// show progress fragment
Fragment f = ProgressFragment.newInstance(
getString(R.string.blogs_rss_feeds_import_progress));
String tag = ProgressFragment.TAG;
showFragment(getParentFragmentManager(), f, tag);
// view model will import and change state that activity will react to
viewModel.importFeed(uri);
}
private void enableOrDisableImportButton() {
String url = urlInput.getText().toString();
importButton.setEnabled(viewModel.validateAndNormaliseUrl(url) != null);

View File

@@ -1,6 +1,8 @@
package org.briarproject.briar.android.blog;
import android.app.Application;
import android.content.ContentResolver;
import android.net.Uri;
import android.util.Patterns;
import org.briarproject.bramble.api.db.DatabaseExecutor;
@@ -11,6 +13,10 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.blog.RssImportResult.FileImportError;
import org.briarproject.briar.android.blog.RssImportResult.FileImportSuccess;
import org.briarproject.briar.android.blog.RssImportResult.UrlImportError;
import org.briarproject.briar.android.blog.RssImportResult.UrlImportSuccess;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.LiveResult;
@@ -20,9 +26,10 @@ import org.briarproject.briar.api.feed.FeedManager;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -30,6 +37,7 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
@@ -38,13 +46,9 @@ import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.EXISTS;
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.FAILED;
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.IMPORTED;
@NotNullByDefault
class RssFeedViewModel extends DbViewModel {
enum ImportResult {IMPORTED, FAILED, EXISTS}
private static final Logger LOG =
getLogger(RssFeedViewModel.class.getName());
@@ -56,11 +60,9 @@ class RssFeedViewModel extends DbViewModel {
private final MutableLiveData<LiveResult<List<Feed>>> feeds =
new MutableLiveData<>();
@Nullable
private volatile String urlFailedImport = null;
private final MutableLiveData<Boolean> isImporting =
new MutableLiveData<>(false);
private final MutableLiveEvent<ImportResult> importResult =
private final MutableLiveEvent<RssImportResult> importResult =
new MutableLiveEvent<>();
@Inject
@@ -101,7 +103,6 @@ class RssFeedViewModel extends DbViewModel {
private List<Feed> loadFeeds(Transaction txn) throws DbException {
long start = now();
List<Feed> feeds = feedManager.getFeeds(txn);
Collections.sort(feeds);
logDuration(LOG, "Loading feeds", start);
return feeds;
}
@@ -125,7 +126,7 @@ class RssFeedViewModel extends DbViewModel {
});
}
LiveEvent<ImportResult> getImportResult() {
LiveEvent<RssImportResult> getImportResult() {
return importResult;
}
@@ -135,44 +136,52 @@ class RssFeedViewModel extends DbViewModel {
void importFeed(String url) {
isImporting.setValue(true);
urlFailedImport = null;
ioExecutor.execute(() -> {
try {
if (exists(url)) {
importResult.postEvent(EXISTS);
return;
}
Feed feed = feedManager.addFeed(url);
List<Feed> updated = addListItem(getList(feeds), feed);
if (updated != null) {
Collections.sort(updated);
feeds.postValue(new LiveResult<>(updated));
// Update the feed if it was already present
List<Feed> feedList = getList(feeds);
if (feedList == null) feedList = new ArrayList<>();
List<Feed> updated = updateListItems(feedList,
f -> f.equals(feed), f -> feed);
// Add the feed if it wasn't already present
if (updated == null) {
feedList.add(feed);
updated = feedList;
}
importResult.postEvent(IMPORTED);
feeds.postValue(new LiveResult<>(updated));
importResult.postEvent(new UrlImportSuccess());
} catch (DbException | IOException e) {
logException(LOG, WARNING, e);
urlFailedImport = url;
importResult.postEvent(FAILED);
importResult.postEvent(new UrlImportError(url));
} finally {
isImporting.postValue(false);
}
});
}
@Nullable
String getUrlFailedImport() {
return urlFailedImport;
}
private boolean exists(String url) {
List<Feed> list = getList(feeds);
if (list != null) {
for (Feed feed : list) {
if (url.equals(feed.getUrl())) {
return true;
@UiThread
void importFeed(Uri uri) {
ContentResolver contentResolver = getApplication().getContentResolver();
ioExecutor.execute(() -> {
try (InputStream is = contentResolver.openInputStream(uri)) {
Feed feed = feedManager.addFeed(is);
// Update the feed if it was already present
List<Feed> feedList = getList(feeds);
if (feedList == null) feedList = new ArrayList<>();
List<Feed> updated = updateListItems(feedList,
f -> f.equals(feed), f -> feed);
// Add the feed if it wasn't already present
if (updated == null) {
feedList.add(feed);
updated = feedList;
}
feeds.postValue(new LiveResult<>(updated));
importResult.postEvent(new FileImportSuccess());
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
importResult.postEvent(new FileImportError());
}
}
return false;
});
}
}

View File

@@ -0,0 +1,24 @@
package org.briarproject.briar.android.blog;
import org.briarproject.nullsafety.NotNullByDefault;
@NotNullByDefault
abstract class RssImportResult {
static class UrlImportSuccess extends RssImportResult {
}
static class UrlImportError extends RssImportResult {
final String url;
UrlImportError(String url) {
this.url = url;
}
}
static class FileImportSuccess extends RssImportResult {
}
static class FileImportError extends RssImportResult {
}
}

View File

@@ -3,12 +3,14 @@ package org.briarproject.briar.android.contact.add.nearby;
import android.graphics.Bitmap;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import androidx.annotation.Nullable;
abstract class AddContactState {
static class KeyAgreementListening extends AddContactState {
final Bitmap qrCode;
KeyAgreementListening(Bitmap qrCode) {
@@ -29,6 +31,7 @@ abstract class AddContactState {
}
static class ContactExchangeFinished extends AddContactState {
final ContactExchangeResult result;
ContactExchangeFinished(ContactExchangeResult result) {
@@ -37,25 +40,34 @@ abstract class AddContactState {
}
static class Failed extends AddContactState {
/**
* Non-null if failed due to the scanned QR code version.
* True if the app producing the code is too old.
* False if the scanning app is too old.
*/
@Nullable
final Boolean qrCodeTooOld;
Failed(@Nullable Boolean qrCodeTooOld) {
this.qrCodeTooOld = qrCodeTooOld;
static class WrongQrCodeType extends Failed {
final QrCodeType qrCodeType;
WrongQrCodeType(QrCodeType qrCodeType) {
this.qrCodeType = qrCodeType;
}
}
Failed() {
this(null);
static class WrongQrCodeVersion extends Failed {
/**
* True if the app producing the code is too old.
* False if the scanning app is too old.
*/
final boolean qrCodeTooOld;
WrongQrCodeVersion(boolean qrCodeTooOld) {
this.qrCodeTooOld = qrCodeTooOld;
}
}
}
abstract static class ContactExchangeResult {
static class Success extends ContactExchangeResult {
final Author remoteAuthor;
Success(Author remoteAuthor) {
@@ -64,6 +76,7 @@ abstract class AddContactState {
}
static class Error extends ContactExchangeResult {
@Nullable
final Author duplicateAuthor;

View File

@@ -6,12 +6,15 @@ import android.view.MenuItem;
import android.widget.Toast;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeFinished;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed.WrongQrCodeType;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed.WrongQrCodeVersion;
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
@@ -34,6 +37,7 @@ import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.ACCEPTED;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED;
@@ -141,9 +145,15 @@ public class AddNearbyContactActivity extends BriarActivity
ContactExchangeResult result =
((ContactExchangeFinished) state).result;
onContactExchangeResult(result);
} else if (state instanceof WrongQrCodeType) {
QrCodeType qrCodeType = ((WrongQrCodeType) state).qrCodeType;
if (qrCodeType == MAILBOX) onMailboxQrCodeScanned();
else onWrongQrCodeType();
} else if (state instanceof WrongQrCodeVersion) {
boolean qrCodeTooOld = ((WrongQrCodeVersion) state).qrCodeTooOld;
onWrongQrCodeVersion(qrCodeTooOld);
} else if (state instanceof Failed) {
Boolean qrCodeTooOld = ((Failed) state).qrCodeTooOld;
onAddingContactFailed(qrCodeTooOld);
showErrorFragment();
}
}
@@ -170,15 +180,27 @@ public class AddNearbyContactActivity extends BriarActivity
} else throw new AssertionError();
}
private void onAddingContactFailed(@Nullable Boolean qrCodeTooOld) {
if (qrCodeTooOld == null) {
showErrorFragment();
} else {
String msg;
if (qrCodeTooOld) msg = getString(R.string.qr_code_too_old_1);
else msg = getString(R.string.qr_code_too_new_1);
showNextFragment(AddNearbyContactErrorFragment.newInstance(msg));
}
private void onMailboxQrCodeScanned() {
String title = getString(R.string.qr_code_invalid);
String msg = getString(R.string.mailbox_qr_code_for_contact);
showNextFragment(
AddNearbyContactErrorFragment.newInstance(title, msg, false));
}
private void onWrongQrCodeType() {
String title = getString(R.string.qr_code_invalid);
String msg = getString(R.string.qr_code_format_unknown);
showNextFragment(
AddNearbyContactErrorFragment.newInstance(title, msg, false));
}
private void onWrongQrCodeVersion(boolean qrCodeTooOld) {
String title = getString(R.string.qr_code_invalid);
String msg;
if (qrCodeTooOld) msg = getString(R.string.qr_code_too_old_1);
else msg = getString(R.string.qr_code_too_new_1);
showNextFragment(
AddNearbyContactErrorFragment.newInstance(title, msg, false));
}
private void showErrorFragment() {

View File

@@ -22,6 +22,7 @@ import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.view.View.GONE;
import static org.briarproject.briar.android.util.UiUtils.hideViewOnSmallScreen;
import static org.briarproject.briar.android.util.UiUtils.onSingleLinkClick;
@@ -31,17 +32,22 @@ public class AddNearbyContactErrorFragment extends BaseFragment {
public static final String TAG =
AddNearbyContactErrorFragment.class.getName();
private static final String ERROR_MSG = "errorMessage";
private static final String ARG_TITLE = "title";
private static final String ARG_ERROR_MSG = "message";
private static final String ARG_FEEDBACK = "feedback";
@Inject
ViewModelProvider.Factory viewModelFactory;
private AddNearbyContactViewModel viewModel;
public static AddNearbyContactErrorFragment newInstance(String errorMsg) {
public static AddNearbyContactErrorFragment newInstance(String title,
String errorMessage, boolean feedback) {
AddNearbyContactErrorFragment f = new AddNearbyContactErrorFragment();
Bundle args = new Bundle();
args.putString(ERROR_MSG, errorMsg);
args.putString(ARG_TITLE, title);
args.putString(ARG_ERROR_MSG, errorMessage);
args.putBoolean(ARG_FEEDBACK, feedback);
f.setArguments(args);
return f;
}
@@ -66,19 +72,32 @@ public class AddNearbyContactErrorFragment extends BaseFragment {
View v = inflater.inflate(R.layout.fragment_error_contact_exchange,
container, false);
// set optional error message
TextView explanation = v.findViewById(R.id.errorMessage);
String title = null, errorMessage = null;
boolean feedback = true;
Bundle args = getArguments();
String errorMessage = args == null ? null : args.getString(ERROR_MSG);
if (errorMessage == null) {
explanation.setText(getString(R.string.add_contact_error_two_way));
} else {
explanation.setText(args.getString(ERROR_MSG));
if (args != null) {
title = args.getString(ARG_TITLE);
errorMessage = args.getString(ARG_ERROR_MSG);
feedback = args.getBoolean(ARG_FEEDBACK, true);
}
if (title != null) {
TextView titleView = v.findViewById(R.id.errorTitle);
titleView.setText(title);
}
if (errorMessage != null) {
TextView messageView = v.findViewById(R.id.errorMessage);
messageView.setText(errorMessage);
}
// make feedback link clickable
TextView sendFeedback = v.findViewById(R.id.sendFeedback);
onSingleLinkClick(sendFeedback, this::triggerFeedback);
if (feedback) {
// make feedback link clickable
onSingleLinkClick(sendFeedback, this::triggerFeedback);
} else {
sendFeedback.setVisibility(GONE);
}
// buttons
Button tryAgain = v.findViewById(R.id.tryAgainButton);

View File

@@ -43,6 +43,7 @@ import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
import org.briarproject.bramble.api.qrcode.WrongQrCodeTypeException;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.plugin.bluetooth.BluetoothPlugin;
import org.briarproject.briar.R;
@@ -50,9 +51,13 @@ import org.briarproject.briar.android.contact.add.nearby.AddContactState.Contact
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult.Error;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult.Success;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeStarted;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed.WrongQrCodeType;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed.WrongQrCodeVersion;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementListening;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementStarted;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementWaiting;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.QrCodeScanned;
import org.briarproject.briar.android.qrcode.QrCodeDecoder;
import org.briarproject.briar.android.qrcode.QrCodeUtils;
import org.briarproject.briar.android.viewmodel.LiveEvent;
@@ -60,7 +65,6 @@ import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -85,6 +89,7 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager.areEssentialPermissionsGranted;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.NO_ADAPTER;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED;
@@ -126,9 +131,6 @@ class AddNearbyContactViewModel extends AndroidViewModel
REFUSED
}
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
private final EventBus eventBus;
private final AndroidExecutor androidExecutor;
private final Executor ioExecutor;
@@ -376,11 +378,11 @@ class AddNearbyContactViewModel extends AndroidViewModel
} else if (e instanceof KeyAgreementAbortedEvent) {
LOG.info("KeyAgreementAbortedEvent received");
resetPayloadFlags();
state.setValue(new AddContactState.Failed());
state.setValue(new Failed());
} else if (e instanceof KeyAgreementFailedEvent) {
LOG.info("KeyAgreementFailedEvent received");
resetPayloadFlags();
state.setValue(new AddContactState.Failed());
state.setValue(new Failed());
}
}
@@ -446,22 +448,22 @@ class AddNearbyContactViewModel extends AndroidViewModel
// Ignore results until the KeyAgreementTask is ready
if (!gotLocalPayload || gotRemotePayload || currentTask == null) return;
try {
byte[] payloadBytes = result.getText().getBytes(ISO_8859_1);
if (LOG.isLoggable(INFO))
LOG.info("Remote payload is " + payloadBytes.length + " bytes");
Payload remotePayload = payloadParser.parse(payloadBytes);
Payload remotePayload = payloadParser.parse(result.getText());
gotRemotePayload = true;
currentTask.connectAndRunProtocol(remotePayload);
state.postValue(new AddContactState.QrCodeScanned());
state.postValue(new QrCodeScanned());
} catch (WrongQrCodeTypeException e) {
resetPayloadFlags();
state.postValue(new WrongQrCodeType(e.getQrCodeType()));
} catch (UnsupportedVersionException e) {
resetPayloadFlags();
state.postValue(new AddContactState.Failed(e.isTooOld()));
state.postValue(new WrongQrCodeVersion(e.isTooOld()));
} catch (IOException | IllegalArgumentException e) {
LOG.log(WARNING, "QR Code Invalid", e);
androidExecutor.runOnUiThread(() -> Toast.makeText(getApplication(),
R.string.qr_code_invalid, LENGTH_LONG).show());
resetPayloadFlags();
state.postValue(new AddContactState.Failed());
state.postValue(new Failed());
}
}

View File

@@ -12,7 +12,7 @@ import java.util.ArrayList;
import java.util.Collection;
@NotNullByDefault
public abstract class BaseContactSelectorAdapter<I extends SelectableContactItem, H extends ContactItemViewHolder<I>>
public abstract class BaseContactSelectorAdapter<I extends BaseSelectableContactItem, H extends ContactItemViewHolder<I>>
extends BaseContactListAdapter<I, H> {
public BaseContactSelectorAdapter(Context context, Class<I> c,
@@ -24,7 +24,7 @@ public abstract class BaseContactSelectorAdapter<I extends SelectableContactItem
Collection<ContactId> selected = new ArrayList<>();
for (int i = 0; i < items.size(); i++) {
SelectableContactItem item = items.get(i);
BaseSelectableContactItem item = items.get(i);
if (item.isSelected()) selected.add(item.getContact().getId());
}
return selected;

View File

@@ -33,7 +33,7 @@ import static org.briarproject.briar.android.contactselection.ContactSelectorAct
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public abstract class BaseContactSelectorFragment<I extends SelectableContactItem, A extends BaseContactSelectorAdapter<I, ? extends ContactItemViewHolder<I>>>
public abstract class BaseContactSelectorFragment<I extends BaseSelectableContactItem, A extends BaseContactSelectorAdapter<I, ? extends ContactItemViewHolder<I>>>
extends BaseFragment
implements OnContactClickListener<I> {

View File

@@ -17,7 +17,7 @@ import static org.briarproject.briar.android.util.UiUtils.GREY_OUT;
@UiThread
@NotNullByDefault
public class BaseSelectableContactHolder<I extends SelectableContactItem>
public abstract class BaseSelectableContactHolder<I extends BaseSelectableContactItem>
extends ContactItemViewHolder<I> {
private final CheckBox checkBox;

View File

@@ -0,0 +1,32 @@
package org.briarproject.briar.android.contactselection;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.briar.android.contact.ContactItem;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
@NotNullByDefault
public abstract class BaseSelectableContactItem extends ContactItem {
private boolean selected;
public BaseSelectableContactItem(Contact contact, AuthorInfo authorInfo,
boolean selected) {
super(contact, authorInfo);
this.selected = selected;
}
boolean isSelected() {
return selected;
}
void toggleSelected() {
selected = !selected;
}
public abstract boolean isDisabled();
}

View File

@@ -10,7 +10,7 @@ import org.briarproject.nullsafety.NotNullByDefault;
import java.util.Collection;
@NotNullByDefault
public interface ContactSelectorController<I extends SelectableContactItem>
public interface ContactSelectorController<I extends BaseSelectableContactItem>
extends DbController {
void loadContacts(GroupId g, Collection<ContactId> selection,

View File

@@ -11,6 +11,7 @@ import org.briarproject.briar.android.controller.DbControllerImpl;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager;
import org.briarproject.briar.api.sharing.SharingManager.SharingStatus;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.ArrayList;
@@ -53,10 +54,8 @@ public abstract class ContactSelectorControllerImpl
AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
// was this contact already selected?
boolean selected = selection.contains(c.getId());
// can this contact be selected?
boolean disabled = isDisabled(g, c);
contacts.add(new SelectableContactItem(c, authorInfo,
selected, disabled));
selected, getSharingStatus(g, c)));
}
handler.onResult(contacts);
} catch (DbException e) {
@@ -67,7 +66,7 @@ public abstract class ContactSelectorControllerImpl
}
@DatabaseExecutor
protected abstract boolean isDisabled(GroupId g, Contact c)
protected abstract SharingStatus getSharingStatus(GroupId g, Contact c)
throws DbException;
}

View File

@@ -2,15 +2,22 @@ package org.briarproject.briar.android.contactselection;
import android.view.View;
import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.OnContactClickListener;
import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.UiThread;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static org.briarproject.briar.api.sharing.SharingManager.SharingStatus.ERROR;
import static org.briarproject.briar.api.sharing.SharingManager.SharingStatus.INVITE_RECEIVED;
import static org.briarproject.briar.api.sharing.SharingManager.SharingStatus.INVITE_SENT;
import static org.briarproject.briar.api.sharing.SharingManager.SharingStatus.NOT_SUPPORTED;
import static org.briarproject.briar.api.sharing.SharingManager.SharingStatus.SHARING;
@UiThread
@NotNullByDefault
@@ -27,6 +34,19 @@ class SelectableContactHolder
super.bind(item, listener);
if (item.isDisabled()) {
@StringRes int strRes;
if (item.getSharingStatus() == SHARING) {
strRes = R.string.forum_invitation_already_sharing;
} else if (item.getSharingStatus() == INVITE_SENT) {
strRes = R.string.forum_invitation_already_invited;
} else if (item.getSharingStatus() == INVITE_RECEIVED) {
strRes = R.string.forum_invitation_invite_received;
} else if (item.getSharingStatus() == NOT_SUPPORTED) {
strRes = R.string.forum_invitation_not_supported;
} else if (item.getSharingStatus() == ERROR) {
strRes = R.string.forum_invitation_error;
} else throw new AssertionError("Unhandled SharingStatus");
info.setText(strRes);
info.setVisibility(VISIBLE);
} else {
info.setVisibility(GONE);

View File

@@ -1,36 +1,33 @@
package org.briarproject.briar.android.contactselection;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.briar.android.contact.ContactItem;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.sharing.SharingManager.SharingStatus;
import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.briar.api.sharing.SharingManager.SharingStatus.SHAREABLE;
@NotThreadSafe
@NotNullByDefault
public class SelectableContactItem extends ContactItem {
public class SelectableContactItem extends BaseSelectableContactItem {
private boolean selected;
private final boolean disabled;
private final SharingStatus sharingStatus;
public SelectableContactItem(Contact contact, AuthorInfo authorInfo,
boolean selected, boolean disabled) {
super(contact, authorInfo);
this.selected = selected;
this.disabled = disabled;
boolean selected, SharingStatus sharingStatus) {
super(contact, authorInfo, selected);
this.sharingStatus = sharingStatus;
}
boolean isSelected() {
return selected;
}
void toggleSelected() {
selected = !selected;
public SharingStatus getSharingStatus() {
return sharingStatus;
}
@Override
public boolean isDisabled() {
return disabled;
return sharingStatus != SHAREABLE;
}
}

View File

@@ -66,6 +66,7 @@ import org.briarproject.briar.android.view.TextAttachmentController.AttachmentLi
import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendState;
import org.briarproject.briar.android.widget.LinkDialogFragment;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.autodelete.event.ConversationMessagesDeletedEvent;
@@ -476,6 +477,12 @@ public class ConversationActivity extends BriarActivity
actionMode = null;
}
@Override
public void onLinkClick(String url) {
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
f.show(getSupportFragmentManager(), f.getUniqueTag());
}
private void addSelectionTracker() {
RecyclerView recyclerView = list.getRecyclerView();
if (recyclerView.getAdapter() != adapter)
@@ -925,6 +932,7 @@ public class ConversationActivity extends BriarActivity
}
private void removeContact() {
list.showProgressBar();
runOnDbThread(() -> {
try {
contactManager.removeContact(contactId);

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.android.conversation;
import android.content.Context;
import android.text.util.Linkify;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
@@ -19,6 +20,7 @@ import static android.view.View.VISIBLE;
import static org.briarproject.bramble.util.StringUtils.trim;
import static org.briarproject.briar.android.util.UiUtils.formatDate;
import static org.briarproject.briar.android.util.UiUtils.formatDuration;
import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
@UiThread
@@ -58,6 +60,8 @@ abstract class ConversationItemViewHolder extends ViewHolder {
if (item.getText() != null) {
text.setText(trim(item.getText()));
Linkify.addLinks(text, Linkify.WEB_URLS);
makeLinksClickable(text, listener::onLinkClick);
}
long timestamp = item.getTime();

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