mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Compare commits
89 Commits
beta-1.4.1
...
alpha-1.4.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
070165f608 | ||
|
|
445f174275 | ||
|
|
ea5af72878 | ||
|
|
ecf2e75424 | ||
|
|
feebd89029 | ||
|
|
cf723f8002 | ||
|
|
b8e743021c | ||
|
|
b785b6c10f | ||
|
|
26ec200f50 | ||
|
|
82efb0d044 | ||
|
|
4ac4ba13d4 | ||
|
|
0844cd3547 | ||
|
|
69e6648ded | ||
|
|
518aeb38b9 | ||
|
|
7e5e61fc05 | ||
|
|
6ecb44bcaa | ||
|
|
f02bbebf6c | ||
|
|
b8612715f8 | ||
|
|
b86ddfa22f | ||
|
|
0dd4d86f4a | ||
|
|
17e0829f42 | ||
|
|
938d8b71a0 | ||
|
|
1b808584b6 | ||
|
|
36db5b48ef | ||
|
|
ccd6ed9ff0 | ||
|
|
0ced10b3a9 | ||
|
|
98064e9efe | ||
|
|
63172ef2e4 | ||
|
|
7a854e70cb | ||
|
|
ac8a4db457 | ||
|
|
5a09530670 | ||
|
|
4fe91bacc6 | ||
|
|
7f70a1519b | ||
|
|
c92ee0458e | ||
|
|
10b1fe756d | ||
|
|
1a2a250be0 | ||
|
|
a621b8077e | ||
|
|
19084d4060 | ||
|
|
2f73ee1b57 | ||
|
|
45fa12c0b3 | ||
|
|
4253bbaaf5 | ||
|
|
8c2e58796b | ||
|
|
3f13e7e9c3 | ||
|
|
421a93b9a6 | ||
|
|
8a088638db | ||
|
|
a888c5f632 | ||
|
|
0b94814620 | ||
|
|
e82e11acfa | ||
|
|
795461d9a8 | ||
|
|
7b8d01cfe0 | ||
|
|
abd04ee7f5 | ||
|
|
cc5365eaf0 | ||
|
|
6b20b03698 | ||
|
|
9da7fbf4f6 | ||
|
|
f64f442fcf | ||
|
|
6eda2f6d13 | ||
|
|
6faa095dfb | ||
|
|
4007fca668 | ||
|
|
28a747f7f3 | ||
|
|
fd2d5c9173 | ||
|
|
8f7bb9d26b | ||
|
|
dc220200b6 | ||
|
|
0cea137d75 | ||
|
|
2eef34f424 | ||
|
|
a68fff9dd2 | ||
|
|
ddc8f4a7d7 | ||
|
|
f961b6a80b | ||
|
|
93439d9c17 | ||
|
|
f3ee884816 | ||
|
|
8ca22043cf | ||
|
|
9353b78da8 | ||
|
|
429bbe1275 | ||
|
|
c5fb1416bd | ||
|
|
e52cbd896e | ||
|
|
ab1b8784b7 | ||
|
|
55a4daa92f | ||
|
|
e52250f1e4 | ||
|
|
33d01aac8c | ||
|
|
b920382e44 | ||
|
|
1a2f85f701 | ||
|
|
186bcc0b47 | ||
|
|
8b9140f477 | ||
|
|
f959c32935 | ||
|
|
1c060bc6db | ||
|
|
5e44e4d308 | ||
|
|
75d5dec45f | ||
|
|
d825227eb5 | ||
|
|
967dd1f18d | ||
|
|
7a3ffcbae6 |
@@ -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
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 31
|
||||
versionCode 10419
|
||||
versionName "1.4.19"
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -14,6 +14,7 @@ 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")
|
||||
@@ -52,26 +53,38 @@ public class StringUtils {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -32,13 +33,14 @@ class PayloadEncoderImpl implements PayloadEncoder {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
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);
|
||||
|
||||
@@ -73,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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -55,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;
|
||||
@@ -101,6 +100,7 @@ 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) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}});
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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]));
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,4 +94,8 @@ class WindowsTorPlugin extends JavaTorPlugin {
|
||||
if (!success.take()) throw new PluginException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getExecutableExtension() {
|
||||
return ".exe";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -26,8 +26,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 31
|
||||
versionCode 10419
|
||||
versionName "1.4.19"
|
||||
versionCode 10423
|
||||
versionName "1.4.23"
|
||||
applicationId "org.briarproject.briar.android"
|
||||
buildConfigField "String", "TorVersion", "\"$tor_version\""
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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> {
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -20,4 +20,6 @@ interface ConversationListener {
|
||||
|
||||
void onAutoDeleteTimerNoticeClicked();
|
||||
|
||||
void onLinkClick(String url);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.briar.android.conversation;
|
||||
|
||||
import android.text.util.Linkify;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -13,6 +14,7 @@ import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
import static org.briarproject.bramble.util.StringUtils.trim;
|
||||
import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
@@ -40,6 +42,8 @@ class ConversationNoticeViewHolder extends ConversationItemViewHolder {
|
||||
} else {
|
||||
msgText.setVisibility(VISIBLE);
|
||||
msgText.setText(trim(text));
|
||||
Linkify.addLinks(msgText, Linkify.WEB_URLS);
|
||||
makeLinksClickable(msgText, listener::onLinkClick);
|
||||
layout.setBackgroundResource(isIncoming() ?
|
||||
R.drawable.notice_in_bottom : R.drawable.notice_out_bottom);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.briarproject.briar.android.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class ProgressFragment extends BaseFragment {
|
||||
|
||||
public static final String TAG = ProgressFragment.class.getName();
|
||||
|
||||
private static final String PROGRESS_MSG = "progressMessage";
|
||||
|
||||
public static ProgressFragment newInstance(String message) {
|
||||
ProgressFragment f = new ProgressFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(PROGRESS_MSG, message);
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
}
|
||||
|
||||
private String progressMessage;
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = requireArguments();
|
||||
progressMessage = args.getString(PROGRESS_MSG);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
View v = inflater
|
||||
.inflate(R.layout.fragment_progress, container, false);
|
||||
TextView msg = v.findViewById(R.id.progressMessage);
|
||||
msg.setText(progressMessage);
|
||||
return v;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -245,6 +245,7 @@ public class MailboxActivity extends BriarActivity {
|
||||
}
|
||||
|
||||
private void onUnPaired(boolean tellUserToWipeMailbox) {
|
||||
viewModel.clearProblemNotification();
|
||||
if (tellUserToWipeMailbox) {
|
||||
showFragment(getSupportFragmentManager(), new BlankFragment(),
|
||||
BlankFragment.TAG);
|
||||
|
||||
@@ -145,19 +145,7 @@ public class MailboxStatusFragment extends Fragment {
|
||||
@DrawableRes int iconRes;
|
||||
String title;
|
||||
String message = null;
|
||||
if (status.hasProblem(System.currentTimeMillis())) {
|
||||
tintRes = R.color.briar_red_500;
|
||||
title = getString(R.string.mailbox_status_failure_title);
|
||||
iconRes = R.drawable.alerts_and_states_error;
|
||||
showUnlinkWarning = false;
|
||||
wizardButton.setVisibility(VISIBLE);
|
||||
} else if (status.getAttemptsSinceSuccess() > 0) {
|
||||
iconRes = R.drawable.ic_help_outline_white;
|
||||
title = getString(R.string.mailbox_status_problem_title);
|
||||
tintRes = R.color.briar_orange_500;
|
||||
showUnlinkWarning = false;
|
||||
wizardButton.setVisibility(VISIBLE);
|
||||
} else if (status.getMailboxCompatibility() < 0) {
|
||||
if (status.getMailboxCompatibility() < 0) {
|
||||
tintRes = R.color.briar_red_500;
|
||||
if (status.getMailboxCompatibility() == API_CLIENT_TOO_OLD) {
|
||||
title = getString(R.string.mailbox_status_app_too_old_title);
|
||||
@@ -172,6 +160,18 @@ public class MailboxStatusFragment extends Fragment {
|
||||
iconRes = R.drawable.alerts_and_states_error;
|
||||
showUnlinkWarning = true;
|
||||
wizardButton.setVisibility(GONE);
|
||||
} else if (status.hasProblem(System.currentTimeMillis())) {
|
||||
tintRes = R.color.briar_red_500;
|
||||
title = getString(R.string.mailbox_status_failure_title);
|
||||
iconRes = R.drawable.alerts_and_states_error;
|
||||
showUnlinkWarning = false;
|
||||
wizardButton.setVisibility(VISIBLE);
|
||||
} else if (status.getAttemptsSinceSuccess() > 0) {
|
||||
iconRes = R.drawable.ic_help_outline_white;
|
||||
title = getString(R.string.mailbox_status_problem_title);
|
||||
tintRes = R.color.briar_orange_500;
|
||||
showUnlinkWarning = false;
|
||||
wizardButton.setVisibility(VISIBLE);
|
||||
} else {
|
||||
iconRes = R.drawable.ic_check_circle_outline;
|
||||
title = getString(R.string.mailbox_status_connected_title);
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.briarproject.briar.android.privategroup.memberlist.GroupMemberListAct
|
||||
import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity;
|
||||
import org.briarproject.briar.android.threaded.ThreadListActivity;
|
||||
import org.briarproject.briar.android.threaded.ThreadListViewModel;
|
||||
import org.briarproject.briar.android.widget.LinkDialogFragment;
|
||||
import org.briarproject.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
||||
|
||||
@@ -158,6 +159,12 @@ public class GroupActivity extends
|
||||
if (isDissolved != null && !isDissolved) super.onReplyClick(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLinkClick(String url){
|
||||
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
|
||||
f.show(getSupportFragmentManager(), f.getUniqueTag());
|
||||
}
|
||||
|
||||
private void setGroupEnabled(boolean enabled) {
|
||||
sendController.setReady(enabled);
|
||||
list.getRecyclerView().setAlpha(enabled ? 1f : 0.5f);
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
||||
import org.briarproject.briar.api.sharing.SharingManager.SharingStatus;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -140,8 +141,8 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDisabled(GroupId g, Contact c) throws DbException {
|
||||
return !groupInvitationManager.isInvitationAllowed(c, g);
|
||||
protected SharingStatus getSharingStatus(GroupId g, Contact c) throws DbException {
|
||||
return groupInvitationManager.getSharingStatus(c, g);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -92,7 +92,7 @@ class RevealContactsControllerImpl extends DbControllerImpl
|
||||
boolean selected =
|
||||
disabled || selection.contains(c.getId());
|
||||
items.add(new RevealableContactItem(c, authorInfo, selected,
|
||||
disabled, m.getVisibility()));
|
||||
m.getVisibility()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
package org.briarproject.briar.android.privategroup.reveal;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.briar.android.contactselection.SelectableContactItem;
|
||||
import org.briarproject.briar.android.contactselection.BaseSelectableContactItem;
|
||||
import org.briarproject.briar.api.identity.AuthorInfo;
|
||||
import org.briarproject.briar.api.privategroup.Visibility;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import static org.briarproject.briar.api.privategroup.Visibility.INVISIBLE;
|
||||
|
||||
@NotThreadSafe
|
||||
@NotNullByDefault
|
||||
class RevealableContactItem extends SelectableContactItem {
|
||||
class RevealableContactItem extends BaseSelectableContactItem {
|
||||
|
||||
private final Visibility visibility;
|
||||
|
||||
RevealableContactItem(Contact contact, AuthorInfo authorInfo,
|
||||
boolean selected, boolean disabled, Visibility visibility) {
|
||||
super(contact, authorInfo, selected, disabled);
|
||||
boolean selected, Visibility visibility) {
|
||||
super(contact, authorInfo, selected);
|
||||
this.visibility = visibility;
|
||||
}
|
||||
|
||||
@@ -24,4 +26,8 @@ class RevealableContactItem extends SelectableContactItem {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisabled() {
|
||||
return visibility != INVISIBLE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING;
|
||||
import static org.briarproject.briar.android.settings.SettingsViewModel.BT_NAMESPACE;
|
||||
import static org.briarproject.briar.android.settings.SettingsViewModel.TOR_NAMESPACE;
|
||||
@@ -61,32 +60,18 @@ class ConnectionsManager {
|
||||
}
|
||||
|
||||
void updateTorSettings(Settings settings) {
|
||||
Settings torSettings = migrateTorSettings(settings);
|
||||
torEnabled.postValue(torSettings.getBoolean(PREF_PLUGIN_ENABLE,
|
||||
torEnabled.postValue(settings.getBoolean(PREF_PLUGIN_ENABLE,
|
||||
TorConstants.DEFAULT_PREF_PLUGIN_ENABLE));
|
||||
|
||||
int torNetworkSetting = torSettings.getInt(PREF_TOR_NETWORK,
|
||||
int torNetworkSetting = settings.getInt(PREF_TOR_NETWORK,
|
||||
DEFAULT_PREF_TOR_NETWORK);
|
||||
torNetwork.postValue(Integer.toString(torNetworkSetting));
|
||||
|
||||
torMobile.postValue(torSettings.getBoolean(PREF_TOR_MOBILE,
|
||||
torMobile.postValue(settings.getBoolean(PREF_TOR_MOBILE,
|
||||
DEFAULT_PREF_TOR_MOBILE));
|
||||
torCharging
|
||||
.postValue(torSettings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING,
|
||||
DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING));
|
||||
}
|
||||
|
||||
// TODO: Remove after a reasonable migration period (added 2020-06-25)
|
||||
private Settings migrateTorSettings(Settings s) {
|
||||
int network = s.getInt(PREF_TOR_NETWORK, DEFAULT_PREF_TOR_NETWORK);
|
||||
if (network == PREF_TOR_NETWORK_NEVER) {
|
||||
s.putInt(PREF_TOR_NETWORK, DEFAULT_PREF_TOR_NETWORK);
|
||||
s.putBoolean(PREF_PLUGIN_ENABLE, false);
|
||||
// We don't need to save the migrated settings - the Tor plugin is
|
||||
// responsible for that. This code just handles the case where the
|
||||
// settings are loaded before the plugin migrates them.
|
||||
}
|
||||
return s;
|
||||
torCharging.postValue(settings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING,
|
||||
DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING));
|
||||
}
|
||||
|
||||
LiveData<Boolean> btEnabled() {
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.briarproject.briar.android.contactselection.ContactSelectorController
|
||||
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
||||
import org.briarproject.briar.api.identity.AuthorManager;
|
||||
import org.briarproject.briar.api.sharing.SharingManager.SharingStatus;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -47,8 +48,9 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDisabled(GroupId g, Contact c) throws DbException {
|
||||
return !blogSharingManager.canBeShared(g, c);
|
||||
protected SharingStatus getSharingStatus(GroupId g, Contact c)
|
||||
throws DbException {
|
||||
return blogSharingManager.getSharingStatus(g, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.briarproject.briar.android.contactselection.ContactSelectorController
|
||||
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
||||
import org.briarproject.briar.api.forum.ForumSharingManager;
|
||||
import org.briarproject.briar.api.identity.AuthorManager;
|
||||
import org.briarproject.briar.api.sharing.SharingManager.SharingStatus;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -47,8 +48,9 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDisabled(GroupId g, Contact c) throws DbException {
|
||||
return !forumSharingManager.canBeShared(g, c);
|
||||
protected SharingStatus getSharingStatus(GroupId g, Contact c)
|
||||
throws DbException {
|
||||
return forumSharingManager.getSharingStatus(g, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.animation.Animator;
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
@@ -20,6 +21,7 @@ import androidx.annotation.UiThread;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import static androidx.core.content.ContextCompat.getColor;
|
||||
import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
@@ -43,6 +45,8 @@ public abstract class BaseThreadItemViewHolder<I extends ThreadItem>
|
||||
@CallSuper
|
||||
public void bind(I item, ThreadItemListener<I> listener) {
|
||||
textView.setText(StringUtils.trim(item.getText()));
|
||||
Linkify.addLinks(textView, Linkify.WEB_URLS);
|
||||
makeLinksClickable(textView, listener::onLinkClick);
|
||||
|
||||
author.setAuthor(item.getAuthor(), item.getAuthorInfo());
|
||||
author.setDate(item.getTimestamp());
|
||||
|
||||
@@ -137,6 +137,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
|
||||
public interface ThreadItemListener<I> {
|
||||
void onReplyClick(I item);
|
||||
void onLinkClick(String url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.briarproject.briar.android.view.TextSendController;
|
||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||
import org.briarproject.briar.android.view.TextSendController.SendState;
|
||||
import org.briarproject.briar.android.view.UnreadMessageButton;
|
||||
import org.briarproject.briar.android.widget.LinkDialogFragment;
|
||||
import org.briarproject.briar.api.attachment.AttachmentHeader;
|
||||
import org.briarproject.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
||||
@@ -202,6 +203,12 @@ public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadI
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLinkClick(String url) {
|
||||
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
|
||||
f.show(getSupportFragmentManager(), f.getUniqueTag());
|
||||
}
|
||||
|
||||
protected void setToolbarSubTitle(SharingInfo sharingInfo) {
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
|
||||
@@ -24,7 +24,6 @@ public interface AndroidNotificationManager {
|
||||
|
||||
// Notification IDs
|
||||
int ONGOING_NOTIFICATION_ID = 1;
|
||||
int FAILURE_NOTIFICATION_ID = 2;
|
||||
int REMINDER_NOTIFICATION_ID = 3;
|
||||
int PRIVATE_MESSAGE_NOTIFICATION_ID = 4;
|
||||
int GROUP_MESSAGE_NOTIFICATION_ID = 5;
|
||||
@@ -47,10 +46,6 @@ public interface AndroidNotificationManager {
|
||||
String HOTSPOT_CHANNEL_ID = "zHotspot";
|
||||
String MAILBOX_PROBLEM_CHANNEL_ID = "zMailboxProblem";
|
||||
|
||||
// This channel is no longer used - keep the ID so we can remove the
|
||||
// channel from existing installations
|
||||
String FAILURE_CHANNEL_ID = "zStartupFailure";
|
||||
|
||||
// Actions for pending intents
|
||||
String ACTION_DISMISS_REMINDER = "dismissReminder";
|
||||
String ACTION_STOP_HOTSPOT = "stopHotspot";
|
||||
|
||||
43
briar-android/src/main/res/layout/fragment_progress.xml
Normal file
43
briar-android/src/main/res/layout/fragment_progress.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_xlarge">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="@dimen/hero_square"
|
||||
android:layout_height="@dimen/hero_square"
|
||||
android:indeterminate="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/progressMessage"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.25"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/progressMessage"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/progress"
|
||||
tools:text="@string/blogs_rss_feeds_import_progress" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
@@ -33,6 +33,7 @@
|
||||
android:paddingBottom="@dimen/listitem_vertical_margin"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
android:textColorLink="@color/briar_text_link"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/authorView"
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
|
||||
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textColorLink="@color/briar_text_link"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/statusLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
|
||||
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
|
||||
android:textColor="@color/briar_text_primary_inverse"
|
||||
android:textColorLink="@color/briar_text_link_inverse"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/statusLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
android:layout_marginRight="@dimen/message_bubble_margin_non_tail"
|
||||
android:background="@drawable/msg_in_top"
|
||||
android:elevation="@dimen/message_bubble_elevation"
|
||||
android:textColorLink="@color/briar_text_link"
|
||||
tools:text="Short message"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
android:background="@drawable/msg_out_top"
|
||||
android:elevation="@dimen/message_bubble_elevation"
|
||||
android:textColor="@color/briar_text_primary_inverse"
|
||||
android:textColorLink="@color/briar_text_link_inverse"
|
||||
tools:text="This is a long long long message that spans over several lines.\n\nIt ends here."
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
android:background="@drawable/msg_in_top"
|
||||
android:elevation="@dimen/message_bubble_elevation"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textColorLink="@color/briar_text_link"
|
||||
tools:text="Short message"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
||||
@@ -114,6 +114,7 @@
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textColorLink="@color/briar_text_link"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
||||
12
briar-android/src/main/res/menu/rss_feed_import_actions.xml
Normal file
12
briar-android/src/main/res/menu/rss_feed_import_actions.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_import_file"
|
||||
android:icon="@drawable/ic_add_white"
|
||||
android:title="@string/blogs_rss_feeds_import_title"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
</menu>
|
||||
@@ -104,7 +104,7 @@
|
||||
<string name="bt_plugin_status_disabled">Briar е настроен да не използва Bluetooth</string>
|
||||
<!--Notifications-->
|
||||
<string name="reminder_notification_title">Отписани сте от Briar</string>
|
||||
<string name="reminder_notification_text">Докоснете за повторно влизане.</string>
|
||||
<string name="reminder_notification_text">За да влезете, докоснете</string>
|
||||
<string name="reminder_notification_channel_title">Напомняне за вход в Briar</string>
|
||||
<string name="reminder_notification_dismiss">Отказ</string>
|
||||
<string name="ongoing_notification_title">Вписани в Briar</string>
|
||||
@@ -235,6 +235,8 @@
|
||||
<string name="qr_code_invalid">Кодът за QR е недействителен</string>
|
||||
<string name="qr_code_too_old_1">Сканираният код за QR е от по-ранно издание на Briar.\n\nНека вашия контакт инсталира последното издание и да пробва отново.</string>
|
||||
<string name="qr_code_too_new_1">Сканираният код за QR е от по-ново издание на Briar.\n\nИнсталирайте последното издание и пробвайте отново.</string>
|
||||
<string name="mailbox_qr_code_for_contact">Сканираният код за QR е от Briar Пощенска кутия.\n\nАко искате да свържете Пощенска кутия използвайте Настройки > Пощенска кутия от менюто.</string>
|
||||
<string name="qr_code_format_unknown">Сканираният код за QR не предназначен за добавяне на контакт в Briar.\n\nЗа тази цел използвайте кода, на екрана на контакта ви.</string>
|
||||
<string name="camera_error">Грешка в камерата</string>
|
||||
<string name="connecting_to_device">Свързване с устройство\u2026</string>
|
||||
<string name="authenticating_with_device">Удостоверяване с устройство\u2026</string>
|
||||
@@ -480,7 +482,6 @@
|
||||
<string name="blogs_rss_feeds_import_button">Внасяне</string>
|
||||
<string name="blogs_rss_feeds_import_hint">Aдрес на емисия</string>
|
||||
<string name="blogs_rss_feeds_import_error">Грешка при внасяне на емисията.</string>
|
||||
<string name="blogs_rss_feeds_import_exists">Емисията вече е внесена.</string>
|
||||
<string name="blogs_rss_feeds">Емисии на RSS</string>
|
||||
<string name="blogs_rss_feeds_manage_imported">Внесена:</string>
|
||||
<string name="blogs_rss_feeds_manage_author">Автор:</string>
|
||||
@@ -591,7 +592,9 @@
|
||||
<string name="mailbox_setup_download_link">Споделяне на препратка за изтегляне</string>
|
||||
<string name="mailbox_setup_button_scan">Сканиране на кода за QR на пощенска кутия</string>
|
||||
<string name="permission_camera_qr_denied_body">Отказахте достъп до камерата, но достъп е необходим за сканиране на кодове за QR.\n\nОбмислете дали да не дадете разрешение.</string>
|
||||
<string name="mailbox_setup_connecting">Свързване с Пощенска кутия…</string>
|
||||
<!--This string is shown when connecting to a Mailbox for the first time. The placeholder will be replaced with a duration, e.g. "2 minutes".-->
|
||||
<string name="mailbox_setup_connecting_info">Може да отнеме %1s</string>
|
||||
<string name="mailbox_setup_already_paired_title">Пощенската кутия е вече свързана</string>
|
||||
<string name="mailbox_setup_already_paired_description">Прекъснете връзката с пощенската кутия от другото устройство и опитайте отново.</string>
|
||||
<string name="mailbox_setup_io_error_title">Грешка при свързване</string>
|
||||
|
||||
@@ -1,36 +1,35 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<!--Setup-->
|
||||
<string name="setup_title">Benvingut a Briar</string>
|
||||
<string name="setup_name_explanation">El vostre sobrenom etiquetarà tot el que publiqueu. Després de crear el compte ja no podreu canviar el sobrenom.</string>
|
||||
<string name="setup_next">Següent</string>
|
||||
<string name="setup_password_intro">Establiu una contrasenya</string>
|
||||
<string name="setup_password_explanation">El compte de Briar s\'emmagatzema xifrat en el vostre dispositiu, no en el núvol. Si oblideu la contrasenya o desinstal·leu Briar no podreu recuperar el vostre compte ni les dades associades.\n\nTrieu una contrasenya llarga que sigui difícil d\'endevinar, com ara quatre paraules aleatòries o deu lletres, números i símbols aleatoris.</string>
|
||||
<string name="dnkm_doze_title">Connexions en segon pla</string>
|
||||
<string name="dnkm_doze_intro">Per rebre missatges Briar necessita estar connectat en segon pla.</string>
|
||||
<string name="dnkm_doze_explanation">Per rebre missatges Briar necessita estar connectat en segon pla. Desactiveu les optimitzacions de la bateria per permetre que Briar resti sempre connectat.</string>
|
||||
<string name="dnkm_doze_button">Permet connexions</string>
|
||||
<string name="choose_nickname">Trieu el sobrenom</string>
|
||||
<string name="choose_password">Trieu la contrasenya</string>
|
||||
<string name="confirm_password">Confirmeu la contrasenya</string>
|
||||
<string name="name_too_long">El nom és massa llarg</string>
|
||||
<string name="password_too_weak">La contrasenya és massa feble</string>
|
||||
<string name="passwords_do_not_match">Les contrasenyes no coincideixen</string>
|
||||
<string name="create_account_button">Crea el compte</string>
|
||||
<string name="more_info">Més informació</string>
|
||||
<string name="don_t_ask_again">No tornis a preguntar-ho</string>
|
||||
<string name="dnkm_huawei_protected_text">Feu un toc sobre el botó següent i assegureu-vos de que Briar consta com a protegit a la pantalla «Aplicacions protegides».</string>
|
||||
<string name="dnkm_huawei_protected_button">Protegeix Briar</string>
|
||||
<string name="dnkm_huawei_protected_help">Si no afegiu Briar a la llista d\'aplicacions protegides, s\'evitarà que Briar s\'executi en segon pla.</string>
|
||||
<string name="dnkm_huawei_app_launch_text">Premeu el botó, obriu la pantalla «Llença app» i assegureu-vos que Briar està configurat com «Gestiona a ma».</string>
|
||||
<string name="dnkm_huawei_app_launch_button">Obriu la configuració de la bateria</string>
|
||||
<string name="dnkm_huawei_app_launch_help">Si Briar no està configurat com a «Gestiona manualment» a la pantalla «Llença app», no podrà executar-se en segon pla.</string>
|
||||
<string name="dnkm_xiaomi_text">Per executar-se en segon pla, Briar necessita ser blocat en la llista d\'aplicacions recents.</string>
|
||||
<string name="dnkm_xiaomi_button">Protegeix Briar</string>
|
||||
<string name="dnkm_xiaomi_help">Si Briar no està blocat a la llista d\'aplicacions recents, no podrà executar-se en segon pla.</string>
|
||||
<string name="dnkm_xiaomi_dialog_body_old">1. Obriu la llista d\'aplicacions recents (de canvi d\'aplicació)\n\n2. Desplaceu avall la icona de Briar per mostrar la icona del cadenat\n\n3. Si el cadenat és obert, premeu per tancar-lo.</string>
|
||||
<string name="dnkm_xiaomi_dialog_body_new">1. Obriu la llista d\'aplicacions recents (canvi d\'aplicació)\n\n2. Premeu la icona de Briar fins que es mostra la icona del cadenat\n\n3. Si el cadenat és obert, premeu per tancar-lo.</string>
|
||||
<string name="dnkm_warning_dozed">%s no s\'ha pogut executar en segon pla</string>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--Setup-->
|
||||
<string name="setup_title">Benvingut a Briar</string>
|
||||
<string name="setup_name_explanation">El vostre sobrenom etiquetarà tot el que publiqueu. Després de crear el compte ja no podreu canviar el sobrenom.</string>
|
||||
<string name="setup_next">Següent</string>
|
||||
<string name="setup_password_intro">Establiu una contrasenya</string>
|
||||
<string name="setup_password_explanation">El compte de Briar s\'emmagatzema xifrat en el vostre dispositiu, no en el núvol. Si oblideu la contrasenya o desinstal·leu Briar no podreu recuperar el vostre compte ni les dades associades.\n\nTrieu una contrasenya llarga que sigui difícil d\'endevinar, com ara quatre paraules aleatòries o deu lletres, números i símbols aleatoris.</string>
|
||||
<string name="dnkm_doze_intro">Per rebre missatges Briar necessita estar connectat en segon pla.</string>
|
||||
<string name="dnkm_doze_explanation">Per rebre missatges Briar necessita estar connectat en segon pla. Desactiveu les optimitzacions de la bateria per permetre que Briar resti sempre connectat.</string>
|
||||
<string name="choose_nickname">Trieu el sobrenom</string>
|
||||
<string name="choose_password">Trieu la contrasenya</string>
|
||||
<string name="confirm_password">Confirmeu la contrasenya</string>
|
||||
<string name="name_too_long">El nom és massa llarg</string>
|
||||
<string name="password_too_weak">La contrasenya és massa feble</string>
|
||||
<string name="passwords_do_not_match">Les contrasenyes no coincideixen</string>
|
||||
<string name="create_account_button">Crea el compte</string>
|
||||
<string name="more_info">Més informació</string>
|
||||
<string name="don_t_ask_again">No tornis a preguntar-ho</string>
|
||||
<string name="dnkm_huawei_protected_text">Feu un toc sobre el botó següent i assegureu-vos de que Briar consta com a protegit a la pantalla «Aplicacions protegides».</string>
|
||||
<string name="dnkm_huawei_protected_button">Protegeix Briar</string>
|
||||
<string name="dnkm_huawei_protected_help">Si no afegiu Briar a la llista d\'aplicacions protegides, s\'evitarà que Briar s\'executi en segon pla.</string>
|
||||
<string name="dnkm_huawei_app_launch_text">Premeu el botó, obriu la pantalla «Llença app» i assegureu-vos que Briar està configurat com «Gestiona a ma».</string>
|
||||
<string name="dnkm_huawei_app_launch_help">Si Briar no està configurat com a «Gestiona manualment» a la pantalla «Llença app», no podrà executar-se en segon pla.</string>
|
||||
<string name="dnkm_xiaomi_text">Per executar-se en segon pla, Briar necessita ser blocat en la llista d\'aplicacions recents.</string>
|
||||
<string name="dnkm_xiaomi_button">Protegeix Briar</string>
|
||||
<string name="dnkm_xiaomi_help">Si Briar no està blocat a la llista d\'aplicacions recents, no podrà executar-se en segon pla.</string>
|
||||
<string name="dnkm_xiaomi_dialog_body_old">1. Obriu la llista d\'aplicacions recents (de canvi d\'aplicació)\n\n2. Desplaceu avall la icona de Briar per mostrar la icona del cadenat\n\n3. Si el cadenat és obert, premeu per tancar-lo.</string>
|
||||
<string name="dnkm_xiaomi_dialog_body_new">1. Obriu la llista d\'aplicacions recents (també anomenada commutador d\'aplicacions)\n\n2. Si el Briar té una petita imatge d\'un cadenat al costat del seu nom, no cal que feu res\n\n3. Si no hi ha cadenat, manteniu premuda la imatge de Briar fins que aparegui el botó del cadenat i, a continuació, toqueu-lo</string>
|
||||
<string name="dnkm_xiaomi_lock_apps_text">Toqueu el botó següent per obrir la configuració de seguretat. Toqueu «Augmenta la velocitat» i, a continuació, toqueu «Bloca aplicacions» i assegureu-vos que el Briar estigui configurat a \"Blocat\".</string>
|
||||
<string name="dnkm_xiaomi_lock_apps_help">Si el Briar no està configurat com a «Blocat» a la pantalla «Bloca aplicacions», no es podrà executar en segon pla.</string>
|
||||
<string name="dnkm_warning_dozed_1">El Briar no va poder executar-se en segon pla</string>
|
||||
<!--Login-->
|
||||
<string name="enter_password">Contrasenya</string>
|
||||
<string name="try_again">La contrasenya és incorrecta, torneu a escriure-la</string>
|
||||
@@ -41,14 +40,25 @@
|
||||
<string name="dialog_title_lost_password">Contrasenya perduda</string>
|
||||
<string name="dialog_message_lost_password">El vostre compte de Briar s\'emmagatzema només en el vostre dispositiu i xifrat. La contrasenya, doncs, no es pot restablir. Voleu esborrar el compte i crear-ne un de nou?\n\nAtenció! Si esborreu el compte la vostra identitat, els contactes i els missatges antics es perdran per sempre.</string>
|
||||
<string name="startup_failed_activity_title">Error iniciant Briar</string>
|
||||
<string name="startup_failed_clock_error">El Briar no s\'ha pogut iniciar perquè l\'hora del dispositiu és incorrecte.\n\nConfigureu l\'hora del dispositiu correctament i torneu-ho a provar.</string>
|
||||
<string name="startup_failed_db_error">El Briar no ha pogut obrir la base de dades que conté el vostre compte, els vostres contactes i els vostres missatges.\n\nActualitzeu a la darrera versió de l\'aplicació i torneu-ho a provar, o configureu un compte nou seleccionant «He oblidat la meva contrasenya» a la sol·licitud de contrasenya.</string>
|
||||
<string name="startup_failed_data_too_old_error">El vostre compte s\'ha creat amb una versió antiga d\'aquesta aplicació i no es pot obrir amb aquesta versió.\n\nHeu de tornar a instal·lar la versió antiga o configurar un compte nou escollint «He oblidat la meva contrasenya» a la sol·licitud de contrasenya.</string>
|
||||
<string name="startup_failed_data_too_new_error">El vostre compte s\'ha creat amb una versió més recent d\'aquesta aplicació i no es pot obrir amb aquesta versió.\n\nActualitzeu a la versió més recent i torneu-ho a provar.</string>
|
||||
<string name="startup_failed_service_error">El Briar no ha pogut iniciar un component necessari.\n\nActualitzeu l\'aplicació a la darrera versió i torna-ho a provar.</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">Aquesta és una versió de prova de Briar. El vostre compte expira en %d dia i no es pot renovar.</item>
|
||||
<item quantity="other">Aquesta és una versió de prova de Briar. El vostre compte caducarà en %d dies i no es podrà renovar.</item>
|
||||
</plurals>
|
||||
<plurals name="old_android_expiry_warning">
|
||||
<item quantity="one">L\'Android 4 ja no és compatible. El Briar deixarà de funcionar a %s (d\'aquí a %d dia). Instal·leu el Briar en un dispositiu més nou i creeu un compte nou.</item>
|
||||
<item quantity="other">L\'Android 4 ja no és compatible. El Briar deixarà de funcionar a %s (d\'aquí a %d dies). Instal·leu el Briar en un dispositiu més nou i creeu un compte nou.</item>
|
||||
</plurals>
|
||||
<string name="expiry_date_reached">Aquesta versió de Briar ha caducat.\nGràcies per haver-lo provat!</string>
|
||||
<string name="download_briar">Per continuar utilitzant Briar, baixeu la darrera versió.</string>
|
||||
<string name="create_new_account">Haureu de crear un compte nou, però podeu utilitzar el mateix sobrenom.</string>
|
||||
<string name="download_briar_button">Descarrega la darrera versió</string>
|
||||
<string name="old_android_expiry_date_reached">El Briar ja no funciona amb l\'Android 4.\nInstal·leu el Briar en un dispositiu més nou.</string>
|
||||
<string name="old_android_delete_account">Podeu tocar el botó següent per suprimir el vostre compte d\'aquest dispositiu.</string>
|
||||
<string name="delete_account_button">Esborreu el compte</string>
|
||||
<string name="startup_open_database">S\'està desxifrant la base de dades...</string>
|
||||
<string name="startup_migrate_database">S\'està actualitzant la base de dades...</string>
|
||||
@@ -145,6 +155,7 @@
|
||||
<string name="error_start_activity">No està disponible en el vostre sistema</string>
|
||||
<string name="status_heading">Estat</string>
|
||||
<string name="error">Error</string>
|
||||
<string name="info">Informació</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">No hi ha cap contacte per mostrar</string>
|
||||
<string name="no_contacts_action">Toqueu la icona + per afegir un contacte</string>
|
||||
@@ -216,6 +227,7 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="menu_contact">Contacte</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Afegeix un contacte proper</string>
|
||||
<string name="add_contact_error_two_way">Tots dos heu escanejat el codi QR de l\'altre?</string>
|
||||
<string name="face_to_face">Heu de coincidir en el mateix lloc amb la persona que voleu afegir com a contacte.\n\nD\'aquesta manera evitareu que algú suplanti les vostres identitats o pugui llegir els vostres missatges en el futur.</string>
|
||||
<string name="continue_button">Continua</string>
|
||||
<string name="try_again_button">Torna-ho a provar</string>
|
||||
@@ -224,13 +236,16 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="contact_added_toast">S\'ha afegit el contacte %s</string>
|
||||
<string name="contact_already_exists">El contacte %s ja existia</string>
|
||||
<string name="qr_code_invalid">El codi QR és invàlid</string>
|
||||
<string name="qr_code_too_old">El codi QR que heu escanejat prové d\'una versió antiga de %s.\n\nDemaneu al vostre contacte que l\'actualitzi i torneu a provar-ho.</string>
|
||||
<string name="qr_code_too_new">El codi QR que heu escanejat prové d\'una versió de %s més nova que la vostra.\n\nActualitzeu la vostra aplicació i torneu a provar-ho.</string>
|
||||
<string name="qr_code_too_old_1">El codi QR que heu escanejat prové d\'una versió anterior del Briar.\n\nDemaneu al vostre contacte que actualitzi a la versió més recent i, a continuació, torneu-ho a provar.</string>
|
||||
<string name="qr_code_too_new_1">El codi QR que heu escanejat prové d\'una versió més recent del Briar.\n\nActualitzeu a la darrera versió i torneu-ho a provar.</string>
|
||||
<string name="mailbox_qr_code_for_contact">El codi QR que heu escanejat prové del Briar Mailbox.\n\nSi voleu enllaçar una bústia de correu, seleccioneu Configuració > Bústia del menú Briar.</string>
|
||||
<string name="qr_code_format_unknown">El codi QR que heu escanejat no serveix per afegir un contacte del Briar.\n\nEscanejeu el codi QR que es mostra a la pantalla del vostre contacte.</string>
|
||||
<string name="camera_error">Error de la càmera</string>
|
||||
<string name="connecting_to_device">Connectant-se al dispositiu\u2026</string>
|
||||
<string name="authenticating_with_device">Autenticant-se amb el dispositiu\u2026</string>
|
||||
<string name="connection_error_title">No ha pogut connectar-se al vostre contacte</string>
|
||||
<string name="connection_error_feedback">Si aquest problema persisteix, <a href="feedback">envieu-nos un comentari</a> per ajudar-nos a millorar l\'aplicació.</string>
|
||||
<string name="info_both_must_scan">Tots dos heu d\'escanejar el codi QR de l\'altre</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Afegeix un contacte llunyà</string>
|
||||
<string name="add_contact_nearby_title">Afegeix un contacte proper</string>
|
||||
@@ -277,6 +292,7 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="duplicate_link_dialog_text_1">Teniu una sol·licitud de contacte pendent amb l\'enllaç %s</string>
|
||||
<string name="duplicate_link_dialog_text_1_contact">Ja teniu un contacte amb aquest enllaç: %s</string>
|
||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||
<string name="duplicate_link_dialog_text_2">%1$s i %2$s són la mateixa persona?</string>
|
||||
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
||||
@@ -285,10 +301,17 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||
<string name="different_person_button">Persones diferents</string>
|
||||
<string name="duplicate_link_dialog_text_3">%1$s i %2$s us han enviat el mateix enllaç.\n\nÉs possible que un d\'ells estigui intentant descobrir qui són els teus contactes.\n\nNo els diguis que has rebut el mateix enllaç d\'una altra persona.</string>
|
||||
<string name="pending_contact_updated_toast">S\'ha actualitzat la sol·licitud de contacte pendent</string>
|
||||
<string name="info_both_must_enter_links">Tots dos heu d\'afegir l\'enllaç de l\'altre</string>
|
||||
<!--Peer trust levels-->
|
||||
<string name="peer_trust_level_unverified">Contacte no verificat</string>
|
||||
<string name="peer_trust_level_verified">Contacte verificat</string>
|
||||
<string name="peer_trust_level_ourselves">Jo</string>
|
||||
<string name="peer_trust_level_stranger">Estrany</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">Presenteu als vostres contactes</string>
|
||||
<string name="introduction_onboarding_text">Podeu presentar als vostres contactes entre si, de manera que no necessitin trobar-se en persona per a relacionar-se a través de Briar.</string>
|
||||
<string name="introduction_onboarding_text">Presenteu els vostres contactes entre ells perquè es puguin connectar al Briar.</string>
|
||||
<string name="introduction_menu_item">Presenta-li un altre contacte</string>
|
||||
<string name="introduction_activity_title">Trieu un contacte</string>
|
||||
<string name="introduction_not_possible">Ja teniu una presentació en marxa entre aquests contactes. Si us plau, primer deixeu que aquesta presentació acabi. Si vós o els contactes presentats sovint esteu desconnectats, la presentació pot trigar temps.</string>
|
||||
@@ -311,9 +334,14 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<!--Connect via Bluetooth-->
|
||||
<string name="menu_item_connect_via_bluetooth">Connecta via bluetooth</string>
|
||||
<string name="connect_via_bluetooth_title">Connecta via bluetooth</string>
|
||||
<string name="connect_via_bluetooth_intro">En cas que les connexions Bluetooth no funcionin automàticament, podeu utilitzar aquesta pantalla per connectar-vos manualment.\n\nEl vostre contacte ha d\'estar a prop perquè això funcioni.\n\nVós i el vostre contacte hauríeu de prémer «Inicia» alhora.</string>
|
||||
<string name="connect_via_bluetooth_already_discovering">Ja s\'està provant de connectar-se mitjançant Bluetooth. Torneu-ho a provar més tard.</string>
|
||||
<string name="connect_via_bluetooth_no_location_permission">No es pot continuar sense permís per obtenir la posició.</string>
|
||||
<string name="connect_via_bluetooth_no_bluetooth_permission">No es pot continuar sense el permís de dispositius propers</string>
|
||||
<string name="connect_via_bluetooth_start">Connectant-se via Bluetooth...</string>
|
||||
<string name="connect_via_bluetooth_success">S\'ha connectat via Bluetooth.</string>
|
||||
<string name="connect_via_bluetooth_error">No s\'ha pogut connectar mitjançant Bluetooth.</string>
|
||||
<string name="connect_via_bluetooth_error_not_supported">El Bluetooth no és compatible amb el dispositiu.</string>
|
||||
<!--Private Groups-->
|
||||
<string name="groups_list_empty">No hi ha cap grup per mostrar</string>
|
||||
<string name="groups_list_empty_action">Toqueu la icona + per crear un grup o demaneu als vostres contactes que us afegeixin als seus grups</string>
|
||||
@@ -406,6 +434,10 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="forum_declined_toast">Heu refusat la invitació</string>
|
||||
<string name="shared_by_format">Compartit per %s</string>
|
||||
<string name="forum_invitation_already_sharing">Ja l\'esteu compartint</string>
|
||||
<string name="forum_invitation_already_invited">La invitació ja s\'ha enviat</string>
|
||||
<string name="forum_invitation_invite_received">La invitació ja s\'ha rebut</string>
|
||||
<string name="forum_invitation_not_supported">Aquest contacte no és compatible</string>
|
||||
<string name="forum_invitation_error">Això és un error i no és culpa vostre</string>
|
||||
<string name="forum_invitation_response_accepted_sent">Heu acceptat la invitació al fòrum enviada per %s.</string>
|
||||
<string name="forum_invitation_response_declined_sent">Heu refusat la invitació al fòrum enviada per %s.</string>
|
||||
<string name="forum_invitation_response_declined_auto">La invitació al blog de %s s\'ha declinat automàticament.</string>
|
||||
@@ -457,7 +489,6 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="blogs_rss_feeds_import_button">Subscriu-me</string>
|
||||
<string name="blogs_rss_feeds_import_hint">Escriviu l\'URL del canal de notícies RSS</string>
|
||||
<string name="blogs_rss_feeds_import_error">Ens sap greu! S\'ha produït un error en subscriure-us al vostre canal de notícies.</string>
|
||||
<string name="blogs_rss_feeds_import_exists">Aquest canal ja s\'ha importat.</string>
|
||||
<string name="blogs_rss_feeds">Canals RSS</string>
|
||||
<string name="blogs_rss_feeds_manage_imported">Importat:</string>
|
||||
<string name="blogs_rss_feeds_manage_author">Autor:</string>
|
||||
@@ -560,11 +591,104 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="choose_ringtone_title">Trieu el so d\'avís</string>
|
||||
<string name="cannot_load_ringtone">No s\'ha pogut carregar el so d\'avís</string>
|
||||
<!--Mailbox-->
|
||||
<string name="mailbox_setup_connecting">S\'està connectant...</string>
|
||||
<string name="mailbox_settings_title">Bústia</string>
|
||||
<string name="mailbox_setup_title">Configuració de la bústia</string>
|
||||
<string name="mailbox_setup_intro">Una bústia permet als vostres contactes enviar-vos missatges mentre esteu fora de línia. La bústia rebrà els vostres missatges i els emmagatzemarà fins que us connecteu.\n
|
||||
\nPodeu instal·lar l\'aplicació Briar Mailbox en un dispositiu de recanvi. Mantingueu-lo connectat a l\'alimentació i a la Wi-Fi perquè estigui sempre en línia.</string>
|
||||
<string name="mailbox_setup_download">Primer, instal·leu l\'aplicació Mailbox en un altre dispositiu cercant «Briar Mailbox» a Google Play o allà on hageu baixat el Briar.\n
|
||||
\nA continuació, enllaceu la vostra bústia amb Briar escanejant el codi QR que mostra l\'aplicació Mailbox.</string>
|
||||
<string name="mailbox_setup_download_link">Comparteix l\'enllaç de baixada</string>
|
||||
<string name="mailbox_setup_button_scan">Escaneja el codi QR de la bústia</string>
|
||||
<string name="permission_camera_qr_denied_body">Heu denegat l\'accés a la càmera, però per escanejar un codi QR cal utilitzar-la.\n\nConsidereu concedir l\'accés.</string>
|
||||
<string name="mailbox_setup_connecting">S\'està connectant a la bústia...</string>
|
||||
<!--This string is shown when connecting to a Mailbox for the first time. The placeholder will be replaced with a duration, e.g. "2 minutes".-->
|
||||
<string name="mailbox_setup_connecting_info">Això pot trigar fins a %1s</string>
|
||||
<string name="mailbox_qr_code_too_old">El codi QR que heu escanejat prové d\'una versió anterior del Briar Mailbox.\n\nActualitzeu el Briar Mailbox a la darrera versió i torneu-ho a provar.</string>
|
||||
<string name="mailbox_qr_code_too_new">El codi QR que heu escanejat prové d\'una versió més recent de Briar Mailbox.\n\nActualitzeu el Briar a la darrera versió i torneu-ho a provar.</string>
|
||||
<string name="contact_qr_code_for_mailbox">El codi QR que heu escanejat serveix per afegir un contacte del Briar.\n\nSi voleu afegir un contacte, aneu a la llista de contactes i toqueu la icona +.</string>
|
||||
<string name="mailbox_setup_qr_code_wrong_description">El codi QR que heu escanejat no prové del Briar Mailbox.\n\nObriu l\'aplicació Briar Mailbox al dispositiu Mailbox i escanegeu el codi QR que hi apareix.</string>
|
||||
<string name="mailbox_setup_already_paired_title">La bústia ja està enllaçada</string>
|
||||
<string name="mailbox_setup_already_paired_description">Desenllaceu la bústia de l\'altre dispositiu i torneu-ho a provar.</string>
|
||||
<string name="mailbox_setup_io_error_title">No s\'ha pogut connectar</string>
|
||||
<string name="mailbox_setup_io_error_description">Assegureu-vos que tots dos dispositius estiguin connectats a Internet i torneu-ho a provar.</string>
|
||||
<string name="mailbox_setup_assertion_error_title">Error de bústia</string>
|
||||
<string name="mailbox_setup_assertion_error_description">Envieu comentaris (amb dades anònimes) mitjançant l\'aplicació Briar si el problema persisteix.</string>
|
||||
<string name="mailbox_setup_camera_error_description">No s\'ha pogut accedir a la càmera. Torneu-ho a provar després de reiniciar el dispositiu.</string>
|
||||
<string name="mailbox_setup_paired_title">Connectat</string>
|
||||
<string name="mailbox_setup_paired_description">La vostra bústia s\'ha enllaçat correctament amb el Briar.\n
|
||||
\nMantingueu la vostra bústia connectada a l\'alimentació i a la Wi-Fi perquè estigui sempre en línia.</string>
|
||||
<string name="tor_offline_title">Fora de línia</string>
|
||||
<string name="tor_offline_description">Assegureu-vos que aquest dispositiu estigui en línia i que es permeten les connexions a Internet.\n
|
||||
\nDesprés, espereu que la icona del globus terraqüi a la pantalla de configuració de connexió es torni verda.</string>
|
||||
<string name="tor_offline_button_check">Comproveu la configuració de la connexió</string>
|
||||
<string name="mailbox_status_title">Estat de la bústia</string>
|
||||
<string name="mailbox_status_connected_title">La bústia s\'està executant</string>
|
||||
<string name="mailbox_status_problem_title">El Briar té problemes per connectar-se a la bústia</string>
|
||||
<string name="mailbox_status_failure_title">La bústia no està disponible</string>
|
||||
<string name="mailbox_status_app_too_old_title">En Briar és massa antic</string>
|
||||
<string name="mailbox_status_app_too_old_message">Actualitzeu el Briar a la darrera versió i torneu-ho a provar.</string>
|
||||
<string name="mailbox_status_mailbox_too_old_title">La bústia és massa antiga</string>
|
||||
<string name="mailbox_status_mailbox_too_old_message">Actualitza la vostra bústia a la darrera versió i torneu-ho a provar.</string>
|
||||
<string name="mailbox_status_check_button">Comprova la connexió</string>
|
||||
<!--Example for string substitution: Last connection: 3min ago-->
|
||||
<string name="mailbox_status_connected_info">Última connexió: %s</string>
|
||||
<!--Indicates that there never was a connection to the mailbox. Last connection: Never-->
|
||||
<string name="mailbox_status_connected_never">Mai</string>
|
||||
<string name="mailbox_status_unlink_button">Desenllaça</string>
|
||||
<string name="mailbox_status_unlink_dialog_title">Voleu desenllaçar la bústia?</string>
|
||||
<string name="mailbox_status_unlink_dialog_question">Esteu segur que voleu desenllaçar la vostra bústia?</string>
|
||||
<string name="mailbox_status_unlink_dialog_warning">Si desenllaçeu la vostra bústia no podreu rebre missatges mentre el Briar estigui fora de línia.</string>
|
||||
<string name="mailbox_status_unlink_no_wipe_title">La vostra bústia s\'ha desenllaçat</string>
|
||||
<string name="mailbox_status_unlink_no_wipe_message">La propera vegada que tingueu accés al vostre dispositiu Mailbox, obriu l\'aplicació Mailbox i toqueu el botó «Desenllaça» per completar el procés.\n\nSi ja no teniu accés al dispositiu Mailbox, no us preocupeu. Les vostres dades estan xifrades, de manera que es mantindran segures encara que no completeu el procés.</string>
|
||||
<string name="mailbox_status_unlink_success">La vostra bústia s\'ha desenllaçat</string>
|
||||
<string name="mailbox_error_notification_channel_title">Problema amb la bústia Briar</string>
|
||||
<string name="mailbox_error_notification_title">La bústia Briar no està disponible</string>
|
||||
<string name="mailbox_error_notification_text">Toqueu per solucionar el problema.</string>
|
||||
<string name="mailbox_error_wizard_button">Soluciona el problema</string>
|
||||
<string name="mailbox_error_wizard_title">Assistent per a la resolució de problemes de la bústia</string>
|
||||
<string name="mailbox_error_wizard_question1">Teniu accés al vostre dispositiu Mailbox?</string>
|
||||
<string name="mailbox_error_wizard_answer1">Sí, hi tinc accés ara mateix.</string>
|
||||
<string name="mailbox_error_wizard_answer2">Ara mateix no, però hi puc accedir més tard.</string>
|
||||
<string name="mailbox_error_wizard_answer3">No, ja no hi tinc accés.</string>
|
||||
<string name="mailbox_error_wizard_info1_1">Comproveu que el dispositiu Mailbox estigui encès i connectat a Internet.</string>
|
||||
<string name="mailbox_error_wizard_question1_1">Obriu l\'aplicació Mailbox. Què veus?</string>
|
||||
<string name="mailbox_error_wizard_answer1_1">Veig instruccions per configurar la bústia</string>
|
||||
<string name="mailbox_error_wizard_answer1_2">Veig un codi QR</string>
|
||||
<string name="mailbox_error_wizard_answer1_3">Veig «El Mailbox s\'està executant»</string>
|
||||
<string name="mailbox_error_wizard_answer1_4">Veig «Dispositiu fora de línia»</string>
|
||||
<string name="mailbox_error_wizard_info1_1_1">Desenllaceu la vostra bústia mitjançant el botó següent i, a continuació, seguiu les instruccions del dispositiu Mailbox per enllaçar-la de nou.</string>
|
||||
<string name="mailbox_error_wizard_info_1_1_2">Desenllaceu la bústia amb el botó següent i escanegeu el codi QR per tornar-la a enllaçar.</string>
|
||||
<string name="mailbox_error_wizard_info1_1_3">Feu servir el botó següent per comprovar la connexió entre Briar i el Mailbox.\n\n
|
||||
Si la connexió torna a fallar:\n
|
||||
\u2022 Comproveu que les aplicacions Mailbox i Briar estiguin actualitzades a la darrera versió.\n
|
||||
\u2022 Reinicieu els vostres dispositius Mailbox i Briar i torneu-ho a provar.</string>
|
||||
<string name="mailbox_error_wizard_info1_1_4">Comproveu que el dispositiu Mailbox estigui connectat correctament a Internet.\n\nComproveu que l\'hora, data i zona horària del dispositiu Mailbox siguin correctes.\n\nComproveu que les aplicacions Mailbox i Briar estiguin actualitzades a la darrera versió.\ n\nReinicieu els vostres dispositius Mailbox i Briar i torneu-ho a provar.</string>
|
||||
<string name="mailbox_error_wizard_info2">Torneu a aquesta pantalla quan tingueu accés al dispositiu.</string>
|
||||
<string name="mailbox_error_wizard_info3">Desenllaceu la vostra bústia amb el botó següent.\n\nDesprés de desenllaçar la vostra bústia antiga, podeu configurar una bústia nova en qualsevol moment.</string>
|
||||
<!--About-->
|
||||
<string name="about_title">Quant a</string>
|
||||
<string name="briar_version">Versió del Briar: %s</string>
|
||||
<string name="tor_version">Versió del Tor: %s</string>
|
||||
<string name="links">Enllaços</string>
|
||||
<string name="briar_website">\u2022 <a href="">Lloc web</a></string>
|
||||
<string name="briar_source_code">\u2022 <a href="">Codi font</a></string>
|
||||
<string name="briar_changelog">\u2022 <a href="">Registre de canvis</a></string>
|
||||
<string name="briar_privacy_policy">\u2022 <a href="">Política de privadesa</a></string>
|
||||
<!--Here translators can add their names or Transifex usernames(eg "Thanks to all the contributors at the Localization Lab, especially Tom, Matthew and Jerry")-->
|
||||
<string name="translator_thanks">Gràcies a tots els col·laboradors del Localization Lab, especialment a Jaime Muñoz</string>
|
||||
<!--Conversation Settings-->
|
||||
<string name="disappearing_messages_title">Missatges efímers</string>
|
||||
<string name="disappearing_messages_explanation_long">Si activeu aquesta configuració, es crearà un/a nou/va
|
||||
els missatges d\'aquesta conversa desapareixen automàticament al cap de 7\u00A0dies.
|
||||
\n\nEl compte enrere per a la còpia del missatge del remitent comença després d\'haver estat lliurat.
|
||||
El compte enrere comença per al destinatari després que hagi llegit el missatge.
|
||||
\n\nEls missatges que desapareixeran estan marcats amb una icona de bomba.
|
||||
\n\nTingues en compte que els destinataris encara poden fer còpies dels missatges que envieu.
|
||||
\n\nSi canvieu aquesta configuració, s\'aplicarà als vostres missatges nous immediatament i als vostres
|
||||
missatges del contacte un cop rebin el vostre següent missatge.
|
||||
El vostre contacte també pot canviar aquesta configuració per a tots dos.</string>
|
||||
<string name="learn_more">Més informació</string>
|
||||
<string name="disappearing_messages_summary">Feu que els missatges futurs d\'aquesta conversa desapareguin automàticament al cap de 7\u00A0dies.</string>
|
||||
<!--Settings Actions-->
|
||||
<string name="pref_category_actions">Accions</string>
|
||||
<string name="send_feedback">Envieu comentaris</string>
|
||||
@@ -575,6 +699,7 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="link_warning_open_link">Obre l\'enllaç</string>
|
||||
<!--Crash Reporter-->
|
||||
<string name="crash_report_title">Informe de fallida de Briar</string>
|
||||
<string name="briar_crashed">El Briar ha fallat</string>
|
||||
<string name="not_your_fault">Això no és culpa vostra.</string>
|
||||
<string name="please_send_report">Ajudi\'ns a construir un Briar millor enviant-nos un informe de fallida.</string>
|
||||
<string name="report_is_encrypted">Us garantim que l\'informe es xifra i s\'envia de manera segura.</string>
|
||||
@@ -582,19 +707,35 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="describe_crash">Descriviu el que hi ha succeït (opcional)</string>
|
||||
<string name="enter_feedback">Escriviu els vostres comentaris</string>
|
||||
<string name="optional_contact_email">La vostra adreça de correu (opcional)</string>
|
||||
<string name="privacy_policy">En enviar-nos dades, accepteu la nostra <a href="">política de privadesa</a></string>
|
||||
<string name="include_debug_report_crash">Inclou dades anònimes sobre la fallida</string>
|
||||
<string name="include_debug_report_feedback">Inclou dades anònimes sobre el dispositiu</string>
|
||||
<string name="dev_report_user_info">Informació de l\'usuari</string>
|
||||
<string name="dev_report_basic_info">Informació bàsica</string>
|
||||
<string name="dev_report_device_info">Informació del dispositiu</string>
|
||||
<string name="dev_report_stacktrace">Traça de la pila</string>
|
||||
<string name="dev_report_time_info">Informació del temps</string>
|
||||
<string name="dev_report_memory">Memòria</string>
|
||||
<string name="dev_report_storage">Emmagatzematge</string>
|
||||
<string name="dev_report_connectivity">Connectivitat</string>
|
||||
<string name="dev_report_network_usage">Ús de la xarxa</string>
|
||||
<string name="dev_report_build_config">Configuració de la compilació</string>
|
||||
<string name="dev_report_logcat">Registre de l\'aplicació</string>
|
||||
<string name="dev_report_device_features">Característiques del dispositiu</string>
|
||||
<string name="send_report">Envia l\'informe</string>
|
||||
<string name="close">Tanca</string>
|
||||
<string name="dev_report_sending">S\'està enviant els comentaris...</string>
|
||||
<string name="dev_report_sent">Comentaris enviats</string>
|
||||
<string name="dev_report_saved">S\'ha desat l\'informe. Se us enviarà la propera vegada que inicieu sessió a Briar.</string>
|
||||
<string name="dev_report_error">Error: no s\'ha pogut enviar l\'informe</string>
|
||||
<!--Sign Out-->
|
||||
<string name="progress_title_logout">S\'està tancant la sessió de Briar...</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">S\'ha detectat superposició de la pantalla</string>
|
||||
<string name="screen_filter_body">Una altra aplicació es troba damunt de Briar. Per protegir la vostra seguretat, Briar no respondrà a les pulsacions quan una altra aplicació s\'hi hagi sobreposat.\n\nLes següents aplicacions poden estar sobreposades a Briar:\n\n%1$s</string>
|
||||
<string name="screen_filter_body_api_30">Una altra aplicació està dibuixant a sobre del Briar. Per protegir la vostra seguretat, el Briar no respondrà als tocs quan hi hagi una altra aplicació dibuixant a sobre.\n\nReviseu les aplicacions a continuació per trobar l\'aplicació responsable.</string>
|
||||
<string name="screen_filter_allow">Permet que aquestes aplicacions se sobreposin a Briar</string>
|
||||
<string name="screen_filter_review_apps">Revisa les aplicacions</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">Permís de la càmera</string>
|
||||
<string name="permission_camera_request_body">Per escanejar el codi QR, Briar necessita accedir a la càmera.</string>
|
||||
@@ -602,7 +743,17 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="permission_location_request_body">Per a descobrir dispositius Bluetooth, Briar necessita accedir a la vostra posició.\n\nBriar no guarda la vostra posició ni la comparteix amb ningú.</string>
|
||||
<string name="permission_camera_location_title">Permís d\'accés a la càmera i a la posició</string>
|
||||
<string name="permission_camera_location_request_body">Per escanejar el codi QR, Briar necessita accedir a la càmera.\n\nPer trobar dispositius Bluetooth, Briar necessita accedir a la vostra posició.\n\nBriar no guarda la vostra posició ni la comparteix amb ningú.</string>
|
||||
<string name="permission_camera_bluetooth_title">Càmera i dispositius propers</string>
|
||||
<string name="permission_camera_bluetooth_request_body">Per escanejar el codi QR, el Briar necessita accedir a la càmera.\n\nPer descobrir dispositius Bluetooth, el Briar necessita permís per trobar i connectar-se a dispositius propers.</string>
|
||||
<string name="permission_camera_denied_body">Heu denegat l\'accés a la càmera però per afegir contactes cal utilitzar la càmera.\n\nRecomanem que permeteu l\'accés a la càmera.</string>
|
||||
<string name="permission_location_denied_body">Heu denegat l\'accés a la vostra ubicació, però el Briar necessita aquest permís per descobrir dispositius Bluetooth.\n\nPenseu en concedir l\'accés.</string>
|
||||
<string name="permission_location_setting_title">Configuració d\'ubicació</string>
|
||||
<string name="permission_location_setting_body">La configuració d\'ubicació del dispositiu ha d\'estar activada per trobar altres dispositius mitjançant Bluetooth. Activeu la ubicació per continuar. Podeu tornar a desactivar-lo després.</string>
|
||||
<string name="permission_location_setting_hotspot_body">La configuració d\'ubicació del dispositiu s\'ha d\'activar per crear un punt d\'accés Wi-Fi. Activeu la ubicació per continuar. Podeu tornar a desactivar-lo després.</string>
|
||||
<string name="permission_location_setting_button">Activa la ubicació</string>
|
||||
<string name="permission_bluetooth_title">Permís de dispositius propers</string>
|
||||
<string name="permission_bluetooth_body">Per utilitzar la comunicació Bluetooth, el Briar necessita permís per trobar i connectar-se a dispositius propers.</string>
|
||||
<string name="permission_bluetooth_denied_body">Heu denegat l\'accés als dispositius propers, però el Briar necessita aquest permís per utilitzar Bluetooth.\n\nPenseu en concedir l\'accés.</string>
|
||||
<string name="qr_code">Codi QR</string>
|
||||
<string name="show_qr_code_fullscreen">Mostra el codi QR a pantalla completa</string>
|
||||
<!--App Locking-->
|
||||
@@ -615,17 +766,92 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<!--Connections Screen-->
|
||||
<string name="transports_help_text">Briar pot contactar els vostres contactes via Internet, WiFi o Bluetooth.\n\nTotes les connexions d\'Internet van via la xarxa Tor per privacitat.\n\nSi es pot arribar a un contacte per diversos mètodes, Briar els usa tots simultàniament.</string>
|
||||
<!--Share app offline-->
|
||||
<string name="hotspot_title">Comparteix aquesta aplicació fora de línia</string>
|
||||
<string name="hotspot_intro">Compartiu aquesta aplicació amb algú proper sense connexió a Internet mitjançant la Wi-Fi del telèfon.
|
||||
\n\nEl vostre telèfon iniciarà un punt d\'accés Wi-Fi. Les persones properes poden connectar-se al punt d\'accés i baixar l\'aplicació Briar des del telèfon.</string>
|
||||
<string name="hotspot_button_start_sharing">Inicia el punt d\'accés</string>
|
||||
<string name="hotspot_button_stop_sharing">Atura el punt d\'accés</string>
|
||||
<string name="hotspot_progress_text_start">S\'està configurant el punt d\'accés...</string>
|
||||
<string name="hotspot_notification_channel_title">Punt d\'accés Wi-Fi</string>
|
||||
<string name="hotspot_notification_title">Compartint el Briar fora de línia</string>
|
||||
<string name="hotspot_button_connected">Endavant</string>
|
||||
<string name="permission_hotspot_location_request_body">Per crear un punt d\'accés Wi-Fi, el Briar necessita permís per accedir a la vostra ubicació.\n\nel Briar no emmagatzema la vostra ubicació ni la comparteix amb ningú.</string>
|
||||
<string name="permission_hotspot_location_request_precise_body">Per crear un punt d\'accés Wi-Fi, el Briar necessita permís per accedir a la vostra ubicació precisa.\n\nel Briar no emmagatzema la vostra ubicació ni la comparteix amb ningú.</string>
|
||||
<string name="permission_hotspot_location_denied_body">Heu denegat l\'accés a la vostra ubicació, però el Briar necessita aquest permís per crear un punt d\'accés Wi-Fi.\n\nPenseu en concedir l\'accés.</string>
|
||||
<string name="permission_hotspot_location_denied_precise_body">Heu denegat l\'accés a la vostra ubicació precisa, però el Briar necessita aquest permís per crear un punt d\'accés Wi-Fi.\n\nPenseu en concedir l\'accés.</string>
|
||||
<string name="wifi_settings_title">Configuració Wi-Fi</string>
|
||||
<string name="wifi_settings_request_enable_body">Per crear un punt d\'accés Wi-Fi, el Briar ha d\'utilitzar Wi-Fi. Activeu-lo.</string>
|
||||
<string name="hotspot_tab_manual">Manual</string>
|
||||
<!--The placeholder to be inserted into the string 'hotspot_manual_wifi': People can connect by %s-->
|
||||
<string name="hotspot_scanning_a_qr_code">escanejant un codi QR</string>
|
||||
<!--Wi-Fi setup-->
|
||||
<!--The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code'-->
|
||||
<string name="hotspot_manual_wifi">El vostre telèfon proporciona un punt d\'accés Wi-Fi. Les persones que vulguin baixar el Briar es poden connectar al punt d\'accés afegint-lo a la configuració Wi-Fi del seu dispositiu mitjançant els detalls següents o mitjançant %s. Quan s\'hagin connectat al punt d\'accés, premeu «Següent».</string>
|
||||
<string name="hotspot_manual_wifi_ssid">Nom de la xarxa</string>
|
||||
<string name="hotspot_qr_wifi">El vostre telèfon proporciona un punt d\'accés Wi-Fi. Les persones que vulguin baixar el Briar poden connectar-se al punt d\'accés escanejant aquest codi QR. Quan s\'hagin connectat al punt d\'accés, premeu «Següent».</string>
|
||||
<string name="hotspot_no_peers_connected">No hi ha cap dispositiu connectat</string>
|
||||
<plurals name="hotspot_peers_connected">
|
||||
<item quantity="one">%s dispositiu connectat</item>
|
||||
<item quantity="other">%s dispositius connectats</item>
|
||||
</plurals>
|
||||
<!--Download link-->
|
||||
<!--The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code'-->
|
||||
<string name="hotspot_manual_site">El vostre telèfon proporciona un punt d\'accés Wi-Fi. Les persones connectades al punt d\'accés poden baixar el Briar escrivint l\'enllaç següent en un navegador web o %s.</string>
|
||||
<string name="hotspot_manual_site_address">Adreça (URL)</string>
|
||||
<string name="hotspot_qr_site">El vostre telèfon proporciona una punt d\'accés Wi-Fi. Les persones connectades al punt d\'accés poden baixar el Briar escanejant aquest codi QR.</string>
|
||||
<!--e.g. Download Briar 1.2.20-->
|
||||
<string name="website_download_title_1">Baixa el Briar %s</string>
|
||||
<string name="website_download_intro_1">Algú a prop ha compartit el Briar amb vós.</string>
|
||||
<string name="website_download_button">Baixa el Briar</string>
|
||||
<string name="website_download_outro">Un cop finalitzada la baixada, obriu el fitxer baixat i instal·leu-lo.</string>
|
||||
<string name="website_troubleshooting_title">Detecció d\'errors</string>
|
||||
<string name="website_troubleshooting_1">Si no podeu baixar l\'aplicació, proveu amb un altre navegador web.</string>
|
||||
<string name="website_troubleshooting_2_old">Per instal·lar l\'aplicació baixada, és possible que hàgiu de permetre la instal·lació d\'aplicacions des de «Fonts desconegudes» a la configuració del sistema. Després, potser haureu de tornar a baixar l\'aplicació. Us recomanem que desactiveu la configuració «Fonts desconegudes» després d\'instal·lar l\'aplicació.</string>
|
||||
<string name="website_troubleshooting_2_new">Per instal·lar l\'aplicació baixada, potser haureu de permetre que el navegador instal·li aplicacions desconegudes. Després d\'instal·lar l\'aplicació, us recomanem que elimineu el permís del navegador per instal·lar aplicacions desconegudes.</string>
|
||||
<string name="hotspot_help_wifi_title">Problemes amb la connexió Wi-Fi:</string>
|
||||
<string name="hotspot_help_wifi_1">Proveu a desactivar i reactivar la Wi-Fi als dos telèfons i torneu-ho a provar.</string>
|
||||
<string name="hotspot_help_wifi_2">Si el vostre telèfon es queixa que la Wi-Fi no té Internet, digueu-li que voleu mantenir-vos connectat de totes maneres.</string>
|
||||
<string name="hotspot_help_wifi_3">Reinicieu el telèfon que executa el punt d\'accés Wi-Fi, inicieu el Briar i torneu a provar de compartir.</string>
|
||||
<string name="hotspot_help_site_title">Problemes per visitar el lloc web local:</string>
|
||||
<string name="hotspot_help_site_1">Torneu a comprovar que heu introduït l\'adreça exactament com es mostra. Un petit error pot fer que falli.</string>
|
||||
<string name="hotspot_help_site_2">Assegureu-vos que el vostre telèfon encara estigui connectat a la Wi-Fi correcta (vegeu més amunt) quan intenteu accedir al lloc.</string>
|
||||
<string name="hotspot_help_site_3">Si teniu un tallafoc, comproveu que no bloquegi l\'accés.</string>
|
||||
<string name="hotspot_help_site_4">Si podeu visitar el lloc, però no baixar l\'aplicació Briar, proveu-ho amb un navegador web diferent.</string>
|
||||
<string name="hotspot_help_fallback_title">No funciona res?</string>
|
||||
<string name="hotspot_help_fallback_intro">Podeu provar de desar l\'aplicació com a fitxer .apk per compartir-la d\'una altra manera. Un cop el fitxer s\'ha transferit a l\'altre dispositiu, es pot utilitzar per instal·lar el Briar.
|
||||
\n\nConsell: per compartir mitjançant Bluetooth, és possible que hàgiu de canviar el nom del fitxer perquè acabi amb .zip.</string>
|
||||
<string name="hotspot_help_fallback_button">Desa l\'aplicació</string>
|
||||
<!--error handling-->
|
||||
<string name="hotspot_error_intro">S\'ha produït un error en intentar compartir l\'aplicació mitjançant Wi-Fi:</string>
|
||||
<string name="hotspot_error_no_wifi_direct">El dispositiu no és compatible amb Wi-Fi Direct</string>
|
||||
<string name="hotspot_error_start_callback_failed">El punt d\'accés no s\'ha pogut iniciar: error %s</string>
|
||||
<string name="hotspot_error_start_callback_failed_unknown">El punt d\'accés no s\'ha pogut iniciar amb un error desconegut, motiu %d</string>
|
||||
<string name="hotspot_error_start_callback_no_group_info">El punt d\'accés no s\'ha pogut iniciar: no hi ha informació del grup</string>
|
||||
<string name="hotspot_error_web_server_start">S\'ha produït un error en iniciar el servidor web</string>
|
||||
<string name="hotspot_error_web_server_serve">S\'ha produït un error en presentar el lloc web.\n\nSi el problema persisteix, envieu comentaris (amb dades anònimes) mitjançant l\'aplicació Briar.</string>
|
||||
<string name="hotspot_flag_test">Avís: aquesta aplicació s\'ha instal·lat amb l\'Android Studio i NO es pot instal·lar en un altre dispositiu.</string>
|
||||
<string name="hotspot_error_framework_busy">No es pot iniciar el punt d\'accés.\n\nSi teniu un altre punt d\'accés en execució o compartiu la vostra connexió a Internet mitjançant Wi-Fi, proveu d\'aturar-lo i torneu-ho a provar més tard.</string>
|
||||
<!--Transfer Data via Removable Drives-->
|
||||
<string name="removable_drive_menu_title">Connecta mitjançant una unitat extraïble</string>
|
||||
<string name="removable_drive_intro">Si no us podeu connectar al vostre contacte a través d\'Internet, Wi-Fi o Bluetooth, el Briar també pot transferir missatges en una unitat extraïble, com ara un llapis USB o una targeta SD.</string>
|
||||
<string name="removable_drive_explanation">Si no podeu connectar-vos al vostre contacte a través d\'Internet, Wi-Fi o Bluetooth, el Briar també pot transferir missatges en una unitat extraïble, com ara un llapis USB o una targeta SD.\n\nQuan feu servir el botó «Envia dades», qualsevol dada que estigui esperant per ser enviada al contacte s\'escriurà a la unitat extraïble. Això inclou missatges privats, fitxers adjunts, blogs, fòrums i grups privats.\n\nTot es xifrarà abans que s\'escrigui a la unitat extraïble.\n\nQuan el vostre contacte rebi la unitat extraïble, pot utilitzar el botó «Rebre dades» per importar els missatges al Briar.</string>
|
||||
<string name="removable_drive_title_send">Envia dades</string>
|
||||
<string name="removable_drive_title_receive">Rebre dades</string>
|
||||
<string name="removable_drive_send_intro">Toqueu el botó següent per crear un fitxer nou que contingui els missatges xifrats. Podeu triar on es desarà el fitxer.\n\nSi voleu desar el fitxer en una unitat extraïble, inseriu la unitat ara.</string>
|
||||
<string name="removable_drive_send_no_data">Actualment no hi ha cap missatge esperant per ser enviat a aquest contacte.</string>
|
||||
<string name="removable_drive_send_not_supported">Aquest contacte utilitza una versió antiga del Briar o un dispositiu antic que no admet aquesta funció.</string>
|
||||
<string name="removable_drive_send_button">Trieu el fitxer per exportar</string>
|
||||
<string name="removable_drive_ongoing">Espereu que es completi la tasca en curs</string>
|
||||
<string name="removable_drive_receive_intro">Toqueu el botó següent per triar el fitxer que t\'ha enviat el vostre contacte.\n\nSi el fitxer es troba en una unitat extraïble, inseriu-la ara.</string>
|
||||
<string name="removable_drive_receive_button">Trieu el fitxer per importar</string>
|
||||
<string name="removable_drive_success_send_title">Exportació correcta</string>
|
||||
<string name="removable_drive_success_send_text">Les dades s\'han exportat correctament. Ara teniu 28 dies per transportar el fitxer al vostre contacte.\n\nSi el fitxer es troba en una unitat extraïble, feu servir la notificació de la barra d\'estat per expulsar la unitat abans de desconnectar-la.</string>
|
||||
<string name="removable_drive_success_receive_title">Importació correcta</string>
|
||||
<string name="removable_drive_success_receive_text">S\'han rebut tots els missatges xifrats continguts en aquest fitxer.</string>
|
||||
<string name="removable_drive_error_send_title">S\'ha produït un error en exportar les dades</string>
|
||||
<string name="removable_drive_error_send_text">S\'ha produït un error escrivint dades al fitxer.\n\nSi utilitzeu una unitat extraïble, assegureu-vos que estigui inserida correctament i torneu-ho a provar.\n\nSi l\'error persisteix, envieu comentaris per informar l\'equip del Briar sobre el assumpte.</string>
|
||||
<string name="removable_drive_error_receive_title">S\'ha produït un error en importar les dades</string>
|
||||
<string name="removable_drive_error_receive_text">El fitxer seleccionat no contenia res que el Briar pogués reconèixer.\n\nComproveu que heu triat el fitxer correcte.\n\nSi el vostre contacte va crear el fitxer fa més de 28 dies, el Briar no el podrà reconèixer.</string>
|
||||
<!--Screenshots-->
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_alice">Alba</string>
|
||||
|
||||
@@ -60,8 +60,8 @@
|
||||
<string name="old_android_expiry_date_reached">Briar wird auf Android 4 nicht mehr unterstützt.\nBitte installiere Briar auf einem neueren Gerät.</string>
|
||||
<string name="old_android_delete_account">Du kannst auf die Schaltfläche unten tippen, um dein Konto von diesem Gerät zu löschen.</string>
|
||||
<string name="delete_account_button">Konto löschen</string>
|
||||
<string name="startup_open_database">Datenbank wird entschlüsselt...</string>
|
||||
<string name="startup_migrate_database">Datenbank wird aktualisiert...</string>
|
||||
<string name="startup_open_database">Datenbank wird entschlüsselt…</string>
|
||||
<string name="startup_migrate_database">Datenbank wird aktualisiert…</string>
|
||||
<string name="startup_compact_database">Datenbank wird komprimiert…</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">Navigationsleiste öffnen</string>
|
||||
@@ -237,6 +237,8 @@
|
||||
<string name="qr_code_invalid">Der QR-Code ist ungültig</string>
|
||||
<string name="qr_code_too_old_1">Der QR-Code, den du gescannt hast, kommt von einer älteren Version von Briar.\n\nBitte deinen Kontakt, zur neuesten Version upzudaten, und versuche es erneut.</string>
|
||||
<string name="qr_code_too_new_1">Der QR-Code, den du gescannt hast, kommt von einer neueren Version.\n\nBitte aktualisiere Briar auf die neueste Version und versuche es erneut.</string>
|
||||
<string name="mailbox_qr_code_for_contact">Der QR-Code, den du gescannt hast, stammt von einer Briar-Mailbox.\n\nWenn du dich mit einer Mailbox verbinden willst, dann wähle in den Einstellungen > Mailbox im Briar-Menü.</string>
|
||||
<string name="qr_code_format_unknown">Der QR-Code, den du gescannt hast, ist nicht dazu gedacht, einen Kontakt hinzuzufügen.\n\nBitte scanne den QR-Code, der auf dem Display deines Kontaktes angezeigt wird.</string>
|
||||
<string name="camera_error">Kamerafehler</string>
|
||||
<string name="connecting_to_device">Verbinde mit Gerät\u2026</string>
|
||||
<string name="authenticating_with_device">Authentifiziere Gerät\u2026</string>
|
||||
@@ -264,9 +266,9 @@
|
||||
<string name="pending_contact_requests_snackbar">Es gibt ausstehende Kontaktanfragen</string>
|
||||
<string name="pending_contact_requests">Ausstehende Kontaktanfragen</string>
|
||||
<string name="no_pending_contacts">Keine ausstehenden Kontakte</string>
|
||||
<string name="waiting_for_contact_to_come_online">Warte auf Online-Aktivität des Kontakts ...</string>
|
||||
<string name="waiting_for_contact_to_come_online">Warte auf Online-Aktivität des Kontakts…</string>
|
||||
<string name="connecting">Verbinde…</string>
|
||||
<string name="adding_contact">Kontakt hinzufügen...</string>
|
||||
<string name="adding_contact">Kontakt hinzufügen…</string>
|
||||
<string name="adding_contact_failed">Hinzufügen von Kontakt ist fehlgeschlagen</string>
|
||||
<string name="dialog_title_remove_pending_contact">Entfernung bestätigen</string>
|
||||
<string name="dialog_message_remove_pending_contact">Dieser Kontakt befindet sich noch beim Hinzufügen. Wenn er jetzt entfernt wird, wird das Hinzufügen abgebrochen.</string>
|
||||
@@ -431,6 +433,10 @@
|
||||
<string name="forum_declined_toast">Einladung abgelehnt</string>
|
||||
<string name="shared_by_format">Geteilt durch %s</string>
|
||||
<string name="forum_invitation_already_sharing">Bereits geteilt.</string>
|
||||
<string name="forum_invitation_already_invited">Die Einladung wurde bereits versendet.</string>
|
||||
<string name="forum_invitation_invite_received">Die Einladung wurde bereits empfangen.</string>
|
||||
<string name="forum_invitation_not_supported">Dies wird von diesem Kontakt nicht unterstützt.</string>
|
||||
<string name="forum_invitation_error">Fehler. Dies ist ein Bug und ist nicht dein Fehler.</string>
|
||||
<string name="forum_invitation_response_accepted_sent">Du hast die Forumeinladung von %s angenommen.</string>
|
||||
<string name="forum_invitation_response_declined_sent">Du hast die Forumeinladung von %s abgelehnt.</string>
|
||||
<string name="forum_invitation_response_declined_auto">Die Forumeinladung von %s wurde automatisch abgelehnt.</string>
|
||||
@@ -481,8 +487,9 @@
|
||||
<string name="blogs_rss_feeds_import">RSS-Feed importieren</string>
|
||||
<string name="blogs_rss_feeds_import_button">Importieren</string>
|
||||
<string name="blogs_rss_feeds_import_hint">URL des RSS-Feeds eingeben</string>
|
||||
<string name="blogs_rss_feeds_import_progress">RSS-Feed wird importiert…</string>
|
||||
<string name="blogs_rss_feeds_import_error">Es tut uns Leid! Es gab einen Fehler beim Importieren deines Feeds.</string>
|
||||
<string name="blogs_rss_feeds_import_exists">Dieser Feed ist bereits importiert.</string>
|
||||
<string name="blogs_rss_feeds_import_title">Feed aus Datei importieren</string>
|
||||
<string name="blogs_rss_feeds">RSS-Feeds</string>
|
||||
<string name="blogs_rss_feeds_manage_imported">Importiert:</string>
|
||||
<string name="blogs_rss_feeds_manage_author">Autor:</string>
|
||||
@@ -594,7 +601,13 @@
|
||||
<string name="mailbox_setup_download_link">Download-Link teilen</string>
|
||||
<string name="mailbox_setup_button_scan">Mailbox QR-Code scannen</string>
|
||||
<string name="permission_camera_qr_denied_body">Du hast den Zugriff auf die Kamera verweigert, aber das Scannen eines QR-Codes erfordert die Verwendung der Kamera.\n\nBitte gewähre den Zugriff.</string>
|
||||
<string name="mailbox_setup_connecting">Verbinde mit der Mailbox…</string>
|
||||
<!--This string is shown when connecting to a Mailbox for the first time. The placeholder will be replaced with a duration, e.g. "2 minutes".-->
|
||||
<string name="mailbox_setup_connecting_info">Dies könnte %1s erfordern.</string>
|
||||
<string name="mailbox_qr_code_too_old">Der QR-Code, den du gescannt hast, stammt von einer älteren Briar-Mailbox Version.\n\nBitte aktualisiere die Briar-Mailbox auf die neueste Version und probiere es noch einmal.</string>
|
||||
<string name="mailbox_qr_code_too_new">Der QR-Code, den du gescannt hast, stammt von einer neueren Briar-Mailbox Version.\n\nBitte aktualisiere Briar auf die neueste Version und probiere es noch einmal.</string>
|
||||
<string name="contact_qr_code_for_mailbox">Der QR-Code, den du gescannt hast, ist dafür gedacht, einen Kontakt hinzuzufügen.\n\nWenn du einen Kontakt hinzufügen möchtest, gehe bitte zur Kontaktliste und klicke auf das + Symbol.</string>
|
||||
<string name="mailbox_setup_qr_code_wrong_description">Der QR-Code, den du gescannt hast, stammt nicht von einer Briar-Mailbox.\n\nBitte öffne die Briar-Mailbox auf deinem Mailbox-Gerät und scanne den davon angezeigten QR-Code.</string>
|
||||
<string name="mailbox_setup_already_paired_title">Mailbox bereits verknüpft</string>
|
||||
<string name="mailbox_setup_already_paired_description">Verknüpfung der Mailbox auf deinem anderen Gerät aufheben und erneut versuchen.</string>
|
||||
<string name="mailbox_setup_io_error_title">Keine Verknüpfung möglich</string>
|
||||
@@ -717,7 +730,7 @@
|
||||
<string name="dev_report_saved">Der Bericht wurde gespeichert. Er wird verschickt, wenn du dich das nächste Mal bei Briar anmeldest.</string>
|
||||
<string name="dev_report_error">Fehler: Senden des Reports fehlgeschlagen</string>
|
||||
<!--Sign Out-->
|
||||
<string name="progress_title_logout">Von Briar abmelden...</string>
|
||||
<string name="progress_title_logout">Von Briar abmelden…</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Bildschirmüberlagerung erkannt</string>
|
||||
<string name="screen_filter_body">Eine andere App überlagert Briar. Um deine Sicherheit zu gewährleisten, reagiert Briar in diesem Fall nicht auf deine Eingaben.\n\nDie folgenden Apps könnten überlagern:\n\n%1$s</string>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user