mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
63 Commits
control-po
...
contact-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c290a320e | ||
|
|
421c9c44d6 | ||
|
|
29d3ee2439 | ||
|
|
06d4f85768 | ||
|
|
9685462242 | ||
|
|
84f2c29c76 | ||
|
|
9c8125d77a | ||
|
|
1a1a010ee7 | ||
|
|
56fb20f257 | ||
|
|
f82294527f | ||
|
|
456f25b701 | ||
|
|
0587fdc54c | ||
|
|
ece083026e | ||
|
|
0e5bb3e9de | ||
|
|
dcebd5a81c | ||
|
|
e9a3685bfd | ||
|
|
3aadcc17dd | ||
|
|
296ce080e2 | ||
|
|
724e6643bd | ||
|
|
fafd0c7ff9 | ||
|
|
e91a7c64d8 | ||
|
|
f08e3a58e6 | ||
|
|
94de1834b8 | ||
|
|
6b24eeb84c | ||
|
|
f72ff9f812 | ||
|
|
0f5f440f1c | ||
|
|
7acbe56197 | ||
|
|
fccf735a89 | ||
|
|
d5ac2c9ead | ||
|
|
d4b929fc6c | ||
|
|
b568405f59 | ||
|
|
ff2f710495 | ||
|
|
d00094edab | ||
|
|
9ca854473f | ||
|
|
8603fd3257 | ||
|
|
648fc6e65c | ||
|
|
0c65e97fcf | ||
|
|
16d2154c73 | ||
|
|
b8e390db21 | ||
|
|
b2702062bc | ||
|
|
f11b32f188 | ||
|
|
d603607a90 | ||
|
|
6c0dffff56 | ||
|
|
9f3394aa1d | ||
|
|
74710664e3 | ||
|
|
0d0197fd2d | ||
|
|
c3b5b04b71 | ||
|
|
8b3164e107 | ||
|
|
79ff5aa148 | ||
|
|
652ce4a53d | ||
|
|
df0d6594b6 | ||
|
|
f73ecc6066 | ||
|
|
0f614e8460 | ||
|
|
f4bdd201a3 | ||
|
|
5130c83556 | ||
|
|
423ecc003b | ||
|
|
d40cfd30a2 | ||
|
|
3b4a92f66c | ||
|
|
f9dfbe3fa5 | ||
|
|
68c40f0c46 | ||
|
|
d4f8abfac1 | ||
|
|
d07c144316 | ||
|
|
dcd5189910 |
@@ -32,9 +32,6 @@ import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
@ParametersNotNullByDefault
|
||||
class AndroidTorPlugin extends TorPlugin {
|
||||
|
||||
// This tag may prevent Huawei's power manager from killing us
|
||||
private static final String WAKE_LOCK_TAG = "LocationManagerService";
|
||||
|
||||
private final Context appContext;
|
||||
private final RenewableWakeLock wakeLock;
|
||||
|
||||
@@ -55,7 +52,7 @@ class AndroidTorPlugin extends TorPlugin {
|
||||
appContext.getSystemService(POWER_SERVICE);
|
||||
if (pm == null) throw new AssertionError();
|
||||
wakeLock = new RenewableWakeLock(pm, scheduler, PARTIAL_WAKE_LOCK,
|
||||
WAKE_LOCK_TAG, 1, MINUTES);
|
||||
getWakeLockTag(), 1, MINUTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,4 +84,17 @@ class AndroidTorPlugin extends TorPlugin {
|
||||
super.stop();
|
||||
wakeLock.release();
|
||||
}
|
||||
|
||||
private String getWakeLockTag() {
|
||||
PackageManager pm = appContext.getPackageManager();
|
||||
for (PackageInfo info : pm.getInstalledPackages(0)) {
|
||||
String name = info.packageName.toLowerCase();
|
||||
if (name.startsWith("com.huawei.powergenie")) {
|
||||
return "LocationManagerService";
|
||||
} else if (name.startsWith("com.evenwell.powermonitor")) {
|
||||
return "AudioIn";
|
||||
}
|
||||
}
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
@@ -32,6 +35,13 @@ public class AndroidSystemModule {
|
||||
return androidExecutor;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@EventExecutor
|
||||
Executor provideEventExecutor(AndroidExecutor androidExecutor) {
|
||||
return androidExecutor::runOnUiThread;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ResourceProvider provideResourceProvider(AndroidResourceProvider provider) {
|
||||
|
||||
@@ -18,11 +18,10 @@ public interface ContactGroupFactory {
|
||||
* Creates a group for the given client to share with the given contact.
|
||||
*/
|
||||
Group createContactGroup(ClientId clientId, int majorVersion,
|
||||
Contact contact);
|
||||
Contact contact, AuthorId local);
|
||||
|
||||
/**
|
||||
* Creates a group for the given client to share between the given authors
|
||||
* identified by their AuthorIds.
|
||||
* Creates a group for the given client to share between the given authors.
|
||||
*/
|
||||
Group createContactGroup(ClientId clientId, int majorVersion,
|
||||
AuthorId authorId1, AuthorId authorId2);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package org.briarproject.bramble.api.contact;
|
||||
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
||||
|
||||
@Immutable
|
||||
@@ -16,24 +16,28 @@ public class Contact {
|
||||
|
||||
private final ContactId id;
|
||||
private final Author author;
|
||||
private final AuthorId localAuthorId;
|
||||
@Nullable
|
||||
private final String alias;
|
||||
private final boolean verified, active;
|
||||
@Nullable
|
||||
private final byte[] handshakePublicKey;
|
||||
private final boolean verified;
|
||||
|
||||
public Contact(ContactId id, Author author, AuthorId localAuthorId,
|
||||
@Nullable String alias, boolean verified, boolean active) {
|
||||
public Contact(ContactId id, Author author, @Nullable String alias,
|
||||
@Nullable byte[] handshakePublicKey, boolean verified) {
|
||||
if (alias != null) {
|
||||
int aliasLength = toUtf8(alias).length;
|
||||
if (aliasLength == 0 || aliasLength > MAX_AUTHOR_NAME_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (handshakePublicKey != null && (handshakePublicKey.length == 0 ||
|
||||
handshakePublicKey.length > MAX_PUBLIC_KEY_LENGTH)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
this.id = id;
|
||||
this.author = author;
|
||||
this.localAuthorId = localAuthorId;
|
||||
this.alias = alias;
|
||||
this.handshakePublicKey = handshakePublicKey;
|
||||
this.verified = verified;
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public ContactId getId() {
|
||||
@@ -44,21 +48,18 @@ public class Contact {
|
||||
return author;
|
||||
}
|
||||
|
||||
public AuthorId getLocalAuthorId() {
|
||||
return localAuthorId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public boolean isVerified() {
|
||||
return verified;
|
||||
@Nullable
|
||||
public byte[] getHandshakePublicKey() {
|
||||
return handshakePublicKey;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
public boolean isVerified() {
|
||||
return verified;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,30 +18,30 @@ public interface ContactExchangeTask {
|
||||
byte PROTOCOL_VERSION = 1;
|
||||
|
||||
/**
|
||||
* Label for deriving Alice's header key from the master secret.
|
||||
* Label for deriving Alice's header key from the master key.
|
||||
*/
|
||||
String ALICE_KEY_LABEL =
|
||||
"org.briarproject.bramble.contact/ALICE_HEADER_KEY";
|
||||
|
||||
/**
|
||||
* Label for deriving Bob's header key from the master secret.
|
||||
* Label for deriving Bob's header key from the master key.
|
||||
*/
|
||||
String BOB_KEY_LABEL = "org.briarproject.bramble.contact/BOB_HEADER_KEY";
|
||||
|
||||
/**
|
||||
* Label for deriving Alice's key binding nonce from the master secret.
|
||||
* Label for deriving Alice's key binding nonce from the master key.
|
||||
*/
|
||||
String ALICE_NONCE_LABEL = "org.briarproject.bramble.contact/ALICE_NONCE";
|
||||
|
||||
/**
|
||||
* Label for deriving Bob's key binding nonce from the master secret.
|
||||
* Label for deriving Bob's key binding nonce from the master key.
|
||||
*/
|
||||
String BOB_NONCE_LABEL = "org.briarproject.bramble.contact/BOB_NONCE";
|
||||
|
||||
/**
|
||||
* Exchanges contact information with a remote peer.
|
||||
*/
|
||||
void startExchange(LocalAuthor localAuthor, SecretKey masterSecret,
|
||||
void startExchange(LocalAuthor localAuthor, SecretKey masterKey,
|
||||
DuplexTransportConnection conn, TransportId transportId,
|
||||
boolean alice);
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ import java.util.Collection;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.bramble.api.contact.PendingContact.PendingContactState.FAILED;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface ContactManager {
|
||||
|
||||
@@ -26,34 +24,31 @@ public interface ContactManager {
|
||||
void registerContactHook(ContactHook hook);
|
||||
|
||||
/**
|
||||
* Stores a contact associated with the given local and remote pseudonyms,
|
||||
* derives and stores transport keys for each transport, and returns an ID
|
||||
* for the contact.
|
||||
* Stores a contact with the given pseudonym, derives and stores transport
|
||||
* keys for each transport, and returns an ID for the contact.
|
||||
*
|
||||
* @param alice true if the local party is Alice
|
||||
*/
|
||||
ContactId addContact(Transaction txn, Author remote, AuthorId local,
|
||||
SecretKey master, long timestamp, boolean alice, boolean verified,
|
||||
boolean active) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a contact associated with the given local and remote pseudonyms
|
||||
* and returns an ID for the contact.
|
||||
*/
|
||||
ContactId addContact(Transaction txn, Author remote, AuthorId local,
|
||||
boolean verified, boolean active) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a contact associated with the given local and remote pseudonyms,
|
||||
* derives and stores transport keys for each transport, and returns an ID
|
||||
* for the contact.
|
||||
*
|
||||
* @param alice true if the local party is Alice
|
||||
*/
|
||||
ContactId addContact(Author remote, AuthorId local, SecretKey master,
|
||||
ContactId addContact(Transaction txn, Author a, SecretKey rootKey,
|
||||
long timestamp, boolean alice, boolean verified, boolean active)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a contact with the given pseudonym and returns an ID for the
|
||||
* contact.
|
||||
*/
|
||||
ContactId addContact(Transaction txn, Author a, boolean verified)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a contact with the given pseudonym, derives and stores transport
|
||||
* keys for each transport, and returns an ID for the contact.
|
||||
*
|
||||
* @param alice true if the local party is Alice
|
||||
*/
|
||||
ContactId addContact(Author a, SecretKey rootKey, long timestamp,
|
||||
boolean alice, boolean verified, boolean active) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the static link that needs to be sent to the contact to be added.
|
||||
*/
|
||||
@@ -79,7 +74,8 @@ public interface ContactManager {
|
||||
Collection<PendingContact> getPendingContacts();
|
||||
|
||||
/**
|
||||
* Removes a {@link PendingContact} that is in state {@link FAILED}.
|
||||
* Removes a {@link PendingContact} that is in state
|
||||
* {@link PendingContactState FAILED}.
|
||||
*/
|
||||
void removePendingContact(PendingContact pendingContact);
|
||||
|
||||
@@ -89,27 +85,19 @@ public interface ContactManager {
|
||||
Contact getContact(ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the contact with the given remoteAuthorId
|
||||
* that was added by the LocalAuthor with the given localAuthorId
|
||||
*
|
||||
* @throws org.briarproject.bramble.api.db.NoSuchContactException
|
||||
* Returns the contact with the given ID.
|
||||
*/
|
||||
Contact getContact(AuthorId remoteAuthorId, AuthorId localAuthorId)
|
||||
throws DbException;
|
||||
Contact getContact(AuthorId a) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the contact with the given remoteAuthorId
|
||||
* that was added by the LocalAuthor with the given localAuthorId
|
||||
*
|
||||
* @throws org.briarproject.bramble.api.db.NoSuchContactException
|
||||
* Returns the contact with the given ID.
|
||||
*/
|
||||
Contact getContact(Transaction txn, AuthorId remoteAuthorId,
|
||||
AuthorId localAuthorId) throws DbException;
|
||||
Contact getContact(Transaction txn, AuthorId a) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all active contacts.
|
||||
*/
|
||||
Collection<Contact> getActiveContacts() throws DbException;
|
||||
Collection<Contact> getContacts() throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a contact and all associated state.
|
||||
@@ -121,12 +109,6 @@ public interface ContactManager {
|
||||
*/
|
||||
void removeContact(Transaction txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Marks a contact as active or inactive.
|
||||
*/
|
||||
void setContactActive(Transaction txn, ContactId c, boolean active)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Sets an alias name for the contact or unsets it if alias is null.
|
||||
*/
|
||||
@@ -140,16 +122,14 @@ public interface ContactManager {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Return true if a contact with this name and public key already exists
|
||||
* Returns true if a contact with this pseudonym already exists.
|
||||
*/
|
||||
boolean contactExists(Transaction txn, AuthorId remoteAuthorId,
|
||||
AuthorId localAuthorId) throws DbException;
|
||||
boolean contactExists(Transaction txn, AuthorId a) throws DbException;
|
||||
|
||||
/**
|
||||
* Return true if a contact with this name and public key already exists
|
||||
* Returns true if a contact with this pseudonym already exists.
|
||||
*/
|
||||
boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId)
|
||||
throws DbException;
|
||||
boolean contactExists(AuthorId a) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the {@link AuthorInfo} for the given author.
|
||||
|
||||
@@ -8,26 +8,29 @@ import javax.annotation.concurrent.Immutable;
|
||||
@NotNullByDefault
|
||||
public class PendingContact {
|
||||
|
||||
public enum PendingContactState {
|
||||
WAITING_FOR_CONNECTION,
|
||||
CONNECTED,
|
||||
ADDING_CONTACT,
|
||||
FAILED
|
||||
}
|
||||
|
||||
private final PendingContactId id;
|
||||
private final byte[] publicKey;
|
||||
private final String alias;
|
||||
private final PendingContactState state;
|
||||
private final long timestamp;
|
||||
|
||||
public PendingContact(PendingContactId id, String alias,
|
||||
PendingContactState state, long timestamp) {
|
||||
public PendingContact(PendingContactId id, byte[] publicKey,
|
||||
String alias, PendingContactState state, long timestamp) {
|
||||
this.id = id;
|
||||
this.publicKey = publicKey;
|
||||
this.alias = alias;
|
||||
this.state = state;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public PendingContactId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public byte[] getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
@@ -50,5 +53,4 @@ public class PendingContact {
|
||||
return o instanceof PendingContact &&
|
||||
id.equals(((PendingContact) o).id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
package org.briarproject.bramble.api.contact;
|
||||
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/**
|
||||
* Type-safe wrapper for a byte array that uniquely identifies a
|
||||
* {@link PendingContact}.
|
||||
*/
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
public class PendingContactId extends UniqueId {
|
||||
|
||||
public PendingContactId(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof PendingContactId && super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.briarproject.bramble.api.contact;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public enum PendingContactState {
|
||||
|
||||
WAITING_FOR_CONNECTION(0),
|
||||
CONNECTED(1),
|
||||
ADDING_CONTACT(2),
|
||||
FAILED(3);
|
||||
|
||||
private final int value;
|
||||
|
||||
PendingContactState(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static PendingContactState fromValue(int value) {
|
||||
for (PendingContactState s : values()) if (s.value == value) return s;
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
@@ -14,18 +14,12 @@ import javax.annotation.concurrent.Immutable;
|
||||
public class ContactAddedEvent extends Event {
|
||||
|
||||
private final ContactId contactId;
|
||||
private final boolean active;
|
||||
|
||||
public ContactAddedEvent(ContactId contactId, boolean active) {
|
||||
public ContactAddedEvent(ContactId contactId) {
|
||||
this.contactId = contactId;
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package org.briarproject.bramble.api.contact.event;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when a contact is marked active or inactive.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class ContactStatusChangedEvent extends Event {
|
||||
|
||||
private final ContactId contactId;
|
||||
private final boolean active;
|
||||
|
||||
public ContactStatusChangedEvent(ContactId contactId, boolean active) {
|
||||
this.contactId = contactId;
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.briarproject.bramble.api.contact.event;
|
||||
|
||||
import org.briarproject.bramble.api.contact.PendingContact.PendingContactState;
|
||||
import org.briarproject.bramble.api.contact.PendingContactId;
|
||||
import org.briarproject.bramble.api.contact.PendingContactState;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.briarproject.bramble.api.crypto;
|
||||
|
||||
/**
|
||||
* Crypto operations for the key agreement protocol - see
|
||||
* https://code.briarproject.org/akwizgran/briar-spec/blob/master/protocols/BQP.md
|
||||
* https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BQP.md
|
||||
*/
|
||||
public interface KeyAgreementCrypto {
|
||||
|
||||
|
||||
@@ -1,29 +1,45 @@
|
||||
package org.briarproject.bramble.api.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.transport.HandshakeKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
|
||||
/**
|
||||
* Crypto operations for the transport security protocol - see
|
||||
* https://code.briarproject.org/akwizgran/briar-spec/blob/master/protocols/BTP.md
|
||||
* https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BTP.md
|
||||
*/
|
||||
public interface TransportCrypto {
|
||||
|
||||
/**
|
||||
* Derives initial transport keys for the given transport in the given
|
||||
* rotation period from the given master secret.
|
||||
* time period from the given root key.
|
||||
*
|
||||
* @param alice whether the keys are for use by Alice or Bob.
|
||||
* @param active whether the keys are usable for outgoing streams.
|
||||
*/
|
||||
TransportKeys deriveTransportKeys(TransportId t, SecretKey master,
|
||||
long rotationPeriod, boolean alice, boolean active);
|
||||
TransportKeys deriveTransportKeys(TransportId t, SecretKey rootKey,
|
||||
long timePeriod, boolean alice, boolean active);
|
||||
|
||||
/**
|
||||
* Rotates the given transport keys to the given rotation period. If the
|
||||
* keys are for the given period or any later period they are not rotated.
|
||||
* Rotates the given transport keys to the given time period. If the keys
|
||||
* are for the given period or any later period they are not rotated.
|
||||
*/
|
||||
TransportKeys rotateTransportKeys(TransportKeys k, long rotationPeriod);
|
||||
TransportKeys rotateTransportKeys(TransportKeys k, long timePeriod);
|
||||
|
||||
/**
|
||||
* Derives handshake keys for the given transport in the given time period
|
||||
* from the given root key.
|
||||
*
|
||||
* @param alice whether the keys are for use by Alice or Bob.
|
||||
*/
|
||||
HandshakeKeys deriveHandshakeKeys(TransportId t, SecretKey rootKey,
|
||||
long timePeriod, boolean alice);
|
||||
|
||||
/**
|
||||
* Updates the given handshake keys to the given time period. If the keys
|
||||
* are for the given period or any later period they are not updated.
|
||||
*/
|
||||
HandshakeKeys updateHandshakeKeys(HandshakeKeys k, long timePeriod);
|
||||
|
||||
/**
|
||||
* Encodes the pseudo-random tag that is used to recognise a stream.
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
|
||||
/**
|
||||
* An action that's taken when a {@link Transaction} is committed.
|
||||
*/
|
||||
public interface CommitAction {
|
||||
|
||||
void accept(Visitor visitor);
|
||||
|
||||
interface Visitor {
|
||||
|
||||
@EventExecutor
|
||||
void visit(EventAction a);
|
||||
|
||||
@EventExecutor
|
||||
void visit(TaskAction a);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package org.briarproject.bramble.api.db;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
import org.briarproject.bramble.api.contact.PendingContactId;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
@@ -20,8 +22,11 @@ import org.briarproject.bramble.api.sync.MessageStatus;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
import org.briarproject.bramble.api.sync.Request;
|
||||
import org.briarproject.bramble.api.sync.validation.MessageState;
|
||||
import org.briarproject.bramble.api.transport.KeySet;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.HandshakeKeySet;
|
||||
import org.briarproject.bramble.api.transport.HandshakeKeySetId;
|
||||
import org.briarproject.bramble.api.transport.HandshakeKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeySet;
|
||||
import org.briarproject.bramble.api.transport.TransportKeySetId;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -97,17 +102,31 @@ public interface DatabaseComponent {
|
||||
NullableDbCallable<R, E> task) throws DbException, E;
|
||||
|
||||
/**
|
||||
* Stores a contact associated with the given local and remote pseudonyms,
|
||||
* and returns an ID for the contact.
|
||||
* Stores a contact with the given pseudonym and returns an ID for the
|
||||
* contact.
|
||||
*/
|
||||
ContactId addContact(Transaction txn, Author remote, AuthorId local,
|
||||
boolean verified, boolean active) throws DbException;
|
||||
ContactId addContact(Transaction txn, Author a, boolean verified)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a group.
|
||||
*/
|
||||
void addGroup(Transaction txn, Group g) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given handshake keys for the given contact and returns a
|
||||
* key set ID.
|
||||
*/
|
||||
HandshakeKeySetId addHandshakeKeys(Transaction txn, ContactId c,
|
||||
HandshakeKeys k) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given handshake keys for the given pending contact and
|
||||
* returns a key set ID.
|
||||
*/
|
||||
HandshakeKeySetId addHandshakeKeys(Transaction txn, PendingContactId p,
|
||||
HandshakeKeys k) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a local pseudonym.
|
||||
*/
|
||||
@@ -119,6 +138,12 @@ public interface DatabaseComponent {
|
||||
void addLocalMessage(Transaction txn, Message m, Metadata meta,
|
||||
boolean shared) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a pending contact.
|
||||
*/
|
||||
void addPendingContact(Transaction txn, PendingContact p)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a transport.
|
||||
*/
|
||||
@@ -129,27 +154,39 @@ public interface DatabaseComponent {
|
||||
* Stores the given transport keys for the given contact and returns a
|
||||
* key set ID.
|
||||
*/
|
||||
KeySetId addTransportKeys(Transaction txn, ContactId c,
|
||||
TransportKeySetId addTransportKeys(Transaction txn, ContactId c,
|
||||
TransportKeys k) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given contact for the given
|
||||
* local pseudonym.
|
||||
* Returns true if the database contains the given contact.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
boolean containsContact(Transaction txn, AuthorId remote, AuthorId local)
|
||||
throws DbException;
|
||||
boolean containsContact(Transaction txn, AuthorId a) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given group.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
boolean containsGroup(Transaction txn, GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given local author.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
boolean containsLocalAuthor(Transaction txn, AuthorId local)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given pending contact.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
boolean containsPendingContact(Transaction txn, PendingContactId p)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Deletes the message with the given ID. Unlike
|
||||
* {@link #removeMessage(Transaction, MessageId)}, the message ID,
|
||||
@@ -215,6 +252,13 @@ public interface DatabaseComponent {
|
||||
*/
|
||||
Contact getContact(Transaction txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the contact with the given author ID.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Contact getContact(Transaction txn, AuthorId a) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all contacts.
|
||||
* <p/>
|
||||
@@ -222,22 +266,6 @@ public interface DatabaseComponent {
|
||||
*/
|
||||
Collection<Contact> getContacts(Transaction txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns a possibly empty collection of contacts with the given author ID.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<Contact> getContactsByAuthorId(Transaction txn, AuthorId remote)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all contacts associated with the given local pseudonym.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<ContactId> getContacts(Transaction txn, AuthorId a)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the group with the given ID.
|
||||
* <p/>
|
||||
@@ -269,6 +297,14 @@ public interface DatabaseComponent {
|
||||
Visibility getGroupVisibility(Transaction txn, ContactId c, GroupId g)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all handshake keys for the given transport.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<HandshakeKeySet> getHandshakeKeys(Transaction txn, TransportId t)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the local pseudonym with the given ID.
|
||||
* <p/>
|
||||
@@ -417,6 +453,14 @@ public interface DatabaseComponent {
|
||||
*/
|
||||
long getNextSendTime(Transaction txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all pending contacts.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<PendingContact> getPendingContacts(Transaction txn)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all settings in the given namespace.
|
||||
* <p/>
|
||||
@@ -429,14 +473,20 @@ public interface DatabaseComponent {
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<KeySet> getTransportKeys(Transaction txn, TransportId t)
|
||||
Collection<TransportKeySet> getTransportKeys(Transaction txn, TransportId t)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Increments the outgoing stream counter for the given handshake keys.
|
||||
*/
|
||||
void incrementStreamCounter(Transaction txn, TransportId t,
|
||||
HandshakeKeySetId k) throws DbException;
|
||||
|
||||
/**
|
||||
* Increments the outgoing stream counter for the given transport keys.
|
||||
*/
|
||||
void incrementStreamCounter(Transaction txn, TransportId t, KeySetId k)
|
||||
throws DbException;
|
||||
void incrementStreamCounter(Transaction txn, TransportId t,
|
||||
TransportKeySetId k) throws DbException;
|
||||
|
||||
/**
|
||||
* Merges the given metadata with the existing metadata for the given
|
||||
@@ -491,6 +541,12 @@ public interface DatabaseComponent {
|
||||
*/
|
||||
void removeGroup(Transaction txn, Group g) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes the given handshake keys from the database.
|
||||
*/
|
||||
void removeHandshakeKeys(Transaction txn, TransportId t,
|
||||
HandshakeKeySetId k) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a local pseudonym (and all associated state) from the database.
|
||||
*/
|
||||
@@ -501,6 +557,12 @@ public interface DatabaseComponent {
|
||||
*/
|
||||
void removeMessage(Transaction txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a pending contact (and all associated state) from the database.
|
||||
*/
|
||||
void removePendingContact(Transaction txn, PendingContactId p)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a transport (and all associated state) from the database.
|
||||
*/
|
||||
@@ -509,20 +571,14 @@ public interface DatabaseComponent {
|
||||
/**
|
||||
* Removes the given transport keys from the database.
|
||||
*/
|
||||
void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
|
||||
throws DbException;
|
||||
void removeTransportKeys(Transaction txn, TransportId t,
|
||||
TransportKeySetId k) throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given contact as verified.
|
||||
*/
|
||||
void setContactVerified(Transaction txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given contact as active or inactive.
|
||||
*/
|
||||
void setContactActive(Transaction txn, ContactId c, boolean active)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Sets an alias name for the contact or unsets it if alias is null.
|
||||
*/
|
||||
@@ -553,21 +609,36 @@ public interface DatabaseComponent {
|
||||
Collection<MessageId> dependencies) throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the reordering window for the given key set and transport in the
|
||||
* given rotation period.
|
||||
* Sets the reordering window for the given transport key set in the given
|
||||
* time period.
|
||||
*/
|
||||
void setReorderingWindow(Transaction txn, KeySetId k, TransportId t,
|
||||
long rotationPeriod, long base, byte[] bitmap) throws DbException;
|
||||
void setReorderingWindow(Transaction txn, TransportKeySetId k,
|
||||
TransportId t, long timePeriod, long base, byte[] bitmap)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the reordering window for the given handshake key set in the given
|
||||
* time period.
|
||||
*/
|
||||
void setReorderingWindow(Transaction txn, HandshakeKeySetId k,
|
||||
TransportId t, long timePeriod, long base, byte[] bitmap)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given transport keys as usable for outgoing streams.
|
||||
*/
|
||||
void setTransportKeysActive(Transaction txn, TransportId t, KeySetId k)
|
||||
void setTransportKeysActive(Transaction txn, TransportId t,
|
||||
TransportKeySetId k) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given handshake keys, deleting any keys they have replaced.
|
||||
*/
|
||||
void updateHandshakeKeys(Transaction txn, Collection<HandshakeKeySet> keys)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given transport keys, deleting any keys they have replaced.
|
||||
*/
|
||||
void updateTransportKeys(Transaction txn, Collection<KeySet> keys)
|
||||
void updateTransportKeys(Transaction txn, Collection<TransportKeySet> keys)
|
||||
throws DbException;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,4 @@ public interface DatabaseConfig {
|
||||
File getDatabaseDirectory();
|
||||
|
||||
File getDatabaseKeyDirectory();
|
||||
|
||||
long getMaxSize();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
|
||||
/**
|
||||
* A {@link CommitAction} that broadcasts an event.
|
||||
*/
|
||||
public class EventAction implements CommitAction {
|
||||
|
||||
private final Event event;
|
||||
|
||||
EventAction(Event event) {
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
public Event getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Visitor visitor) {
|
||||
visitor.visit(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
/**
|
||||
* Thrown when a database operation is attempted for a pending contact that is
|
||||
* not in the database. This exception may occur due to concurrent updates and
|
||||
* does not indicate a database error.
|
||||
*/
|
||||
public class NoSuchPendingContactException extends DbException {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
/**
|
||||
* Thrown when a duplicate pending contact is added to the database. This
|
||||
* exception may occur due to concurrent updates and does not indicate a
|
||||
* database error.
|
||||
*/
|
||||
public class PendingContactExistsException extends DbException {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
|
||||
/**
|
||||
* A {@link CommitAction} that submits a task to the {@link EventExecutor}.
|
||||
*/
|
||||
public class TaskAction implements CommitAction {
|
||||
|
||||
private final Runnable task;
|
||||
|
||||
TaskAction(Runnable task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
public Runnable getTask() {
|
||||
return task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Visitor visitor) {
|
||||
visitor.visit(this);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* A wrapper around a database transaction. Transactions are not thread-safe.
|
||||
*/
|
||||
@@ -17,7 +19,7 @@ public class Transaction {
|
||||
private final Object txn;
|
||||
private final boolean readOnly;
|
||||
|
||||
private List<Event> events = null;
|
||||
private List<CommitAction> actions = null;
|
||||
private boolean committed = false;
|
||||
|
||||
public Transaction(Object txn, boolean readOnly) {
|
||||
@@ -42,19 +44,27 @@ public class Transaction {
|
||||
|
||||
/**
|
||||
* Attaches an event to be broadcast when the transaction has been
|
||||
* committed.
|
||||
* committed. The event will be broadcast on the {@link EventExecutor}.
|
||||
*/
|
||||
public void attach(Event e) {
|
||||
if (events == null) events = new ArrayList<>();
|
||||
events.add(e);
|
||||
if (actions == null) actions = new ArrayList<>();
|
||||
actions.add(new EventAction(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns any events attached to the transaction.
|
||||
* Attaches a task to be executed when the transaction has been
|
||||
* committed. The task will be run on the {@link EventExecutor}.
|
||||
*/
|
||||
public List<Event> getEvents() {
|
||||
if (events == null) return Collections.emptyList();
|
||||
return events;
|
||||
public void attach(Runnable r) {
|
||||
if (actions == null) actions = new ArrayList<>();
|
||||
actions.add(new TaskAction(r));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns any actions attached to the transaction.
|
||||
*/
|
||||
public List<CommitAction> getActions() {
|
||||
return actions == null ? emptyList() : actions;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,8 @@ public interface EventBus {
|
||||
void removeListener(EventListener l);
|
||||
|
||||
/**
|
||||
* Notifies all listeners of an event.
|
||||
* Asynchronously notifies all listeners of an event. Listeners are
|
||||
* notified on the {@link EventExecutor}.
|
||||
*/
|
||||
void broadcast(Event e);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.briarproject.bramble.api.event;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* Annotation for injecting the executor for broadcasting events and running
|
||||
* tasks that need to run in a defined order with respect to events. Also used
|
||||
* for annotating methods that should run on the event executor.
|
||||
* <p>
|
||||
* The contract of this executor is that tasks are run in the order they're
|
||||
* submitted, tasks are not run concurrently, and submitting a task will never
|
||||
* block. Tasks must not block. Tasks submitted during shutdown are discarded.
|
||||
*/
|
||||
@Qualifier
|
||||
@Target({FIELD, METHOD, PARAMETER})
|
||||
@Retention(RUNTIME)
|
||||
public @interface EventExecutor {
|
||||
}
|
||||
@@ -12,5 +12,6 @@ public interface EventListener {
|
||||
* Called when an event is broadcast. Implementations of this method must
|
||||
* not block.
|
||||
*/
|
||||
@EventExecutor
|
||||
void eventOccurred(Event e);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ package org.briarproject.bramble.api.identity;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
|
||||
/**
|
||||
* A pseudonym for the local user.
|
||||
*/
|
||||
@@ -12,6 +15,8 @@ import javax.annotation.concurrent.Immutable;
|
||||
public class LocalAuthor extends Author {
|
||||
|
||||
private final byte[] privateKey;
|
||||
@Nullable
|
||||
private final byte[] handshakePublicKey, handshakePrivateKey;
|
||||
private final long created;
|
||||
|
||||
public LocalAuthor(AuthorId id, int formatVersion, String name,
|
||||
@@ -19,6 +24,22 @@ public class LocalAuthor extends Author {
|
||||
super(id, formatVersion, name, publicKey);
|
||||
this.privateKey = privateKey;
|
||||
this.created = created;
|
||||
handshakePublicKey = null;
|
||||
handshakePrivateKey = null;
|
||||
}
|
||||
|
||||
public LocalAuthor(AuthorId id, int formatVersion, String name,
|
||||
byte[] publicKey, byte[] privateKey, byte[] handshakePublicKey,
|
||||
byte[] handshakePrivateKey, long created) {
|
||||
super(id, formatVersion, name, publicKey);
|
||||
if (handshakePublicKey.length == 0 ||
|
||||
handshakePublicKey.length > MAX_PUBLIC_KEY_LENGTH) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
this.privateKey = privateKey;
|
||||
this.handshakePublicKey = handshakePublicKey;
|
||||
this.handshakePrivateKey = handshakePrivateKey;
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,6 +49,22 @@ public class LocalAuthor extends Author {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public key used for handshaking, or null if no key exists.
|
||||
*/
|
||||
@Nullable
|
||||
public byte[] getHandshakePublicKey() {
|
||||
return handshakePublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the private key used for handshaking, or null if no key exists.
|
||||
*/
|
||||
@Nullable
|
||||
public byte[] getHandshakePrivateKey() {
|
||||
return handshakePrivateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time the pseudonym was created, in milliseconds since the
|
||||
* Unix epoch.
|
||||
|
||||
@@ -40,8 +40,8 @@ public interface KeyAgreementConstants {
|
||||
"org.briarproject.bramble.keyagreement/SHARED_SECRET";
|
||||
|
||||
/**
|
||||
* Label for deriving the master secret.
|
||||
* Label for deriving the master key.
|
||||
*/
|
||||
String MASTER_SECRET_LABEL =
|
||||
String MASTER_KEY_LABEL =
|
||||
"org.briarproject.bramble.keyagreement/MASTER_SECRET";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.briarproject.bramble.api.transport;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* Abstract superclass for {@link TransportKeys} and {@link HandshakeKeys}.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public abstract class AbstractTransportKeys {
|
||||
|
||||
private final TransportId transportId;
|
||||
private final IncomingKeys inPrev, inCurr, inNext;
|
||||
private final OutgoingKeys outCurr;
|
||||
|
||||
AbstractTransportKeys(TransportId transportId, IncomingKeys inPrev,
|
||||
IncomingKeys inCurr, IncomingKeys inNext, OutgoingKeys outCurr) {
|
||||
if (inPrev.getTimePeriod() != outCurr.getTimePeriod() - 1)
|
||||
throw new IllegalArgumentException();
|
||||
if (inCurr.getTimePeriod() != outCurr.getTimePeriod())
|
||||
throw new IllegalArgumentException();
|
||||
if (inNext.getTimePeriod() != outCurr.getTimePeriod() + 1)
|
||||
throw new IllegalArgumentException();
|
||||
this.transportId = transportId;
|
||||
this.inPrev = inPrev;
|
||||
this.inCurr = inCurr;
|
||||
this.inNext = inNext;
|
||||
this.outCurr = outCurr;
|
||||
}
|
||||
|
||||
public TransportId getTransportId() {
|
||||
return transportId;
|
||||
}
|
||||
|
||||
public IncomingKeys getPreviousIncomingKeys() {
|
||||
return inPrev;
|
||||
}
|
||||
|
||||
public IncomingKeys getCurrentIncomingKeys() {
|
||||
return inCurr;
|
||||
}
|
||||
|
||||
public IncomingKeys getNextIncomingKeys() {
|
||||
return inNext;
|
||||
}
|
||||
|
||||
public OutgoingKeys getCurrentOutgoingKeys() {
|
||||
return outCurr;
|
||||
}
|
||||
|
||||
public long getTimePeriod() {
|
||||
return outCurr.getTimePeriod();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.briarproject.bramble.api.transport;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.PendingContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* A set of keys for handshaking with a given contact or pending contact over a
|
||||
* given transport. Unlike a {@link TransportKeySet} these keys do not provide
|
||||
* forward secrecy.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class HandshakeKeySet {
|
||||
|
||||
private final HandshakeKeySetId keySetId;
|
||||
@Nullable
|
||||
private final ContactId contactId;
|
||||
@Nullable
|
||||
private final PendingContactId pendingContactId;
|
||||
private final HandshakeKeys keys;
|
||||
|
||||
public HandshakeKeySet(HandshakeKeySetId keySetId, ContactId contactId,
|
||||
HandshakeKeys keys) {
|
||||
this.keySetId = keySetId;
|
||||
this.contactId = contactId;
|
||||
this.keys = keys;
|
||||
pendingContactId = null;
|
||||
}
|
||||
|
||||
public HandshakeKeySet(HandshakeKeySetId keySetId,
|
||||
PendingContactId pendingContactId, HandshakeKeys keys) {
|
||||
this.keySetId = keySetId;
|
||||
this.pendingContactId = pendingContactId;
|
||||
this.keys = keys;
|
||||
contactId = null;
|
||||
}
|
||||
|
||||
public HandshakeKeySetId getKeySetId() {
|
||||
return keySetId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PendingContactId getPendingContactId() {
|
||||
return pendingContactId;
|
||||
}
|
||||
|
||||
public HandshakeKeys getKeys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return keySetId.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof HandshakeKeySet &&
|
||||
keySetId.equals(((HandshakeKeySet) o).keySetId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.briarproject.bramble.api.transport;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* Type-safe wrapper for an integer that uniquely identifies a
|
||||
* {@link HandshakeKeySet set of handshake keys} within the scope of the local
|
||||
* device.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class HandshakeKeySetId {
|
||||
|
||||
private final int id;
|
||||
|
||||
public HandshakeKeySetId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getInt() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof HandshakeKeySetId &&
|
||||
id == ((HandshakeKeySetId) o).id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.briarproject.bramble.api.transport;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* Keys for handshaking with a given contact or pending contact over a given
|
||||
* transport. Unlike {@link TransportKeys} these keys do not provide forward
|
||||
* secrecy.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class HandshakeKeys extends AbstractTransportKeys {
|
||||
|
||||
private final SecretKey rootKey;
|
||||
private final boolean alice;
|
||||
|
||||
public HandshakeKeys(TransportId transportId, IncomingKeys inPrev,
|
||||
IncomingKeys inCurr, IncomingKeys inNext, OutgoingKeys outCurr,
|
||||
SecretKey rootKey, boolean alice) {
|
||||
super(transportId, inPrev, inCurr, inNext, outCurr);
|
||||
this.rootKey = rootKey;
|
||||
this.alice = alice;
|
||||
}
|
||||
|
||||
public SecretKey getRootKey() {
|
||||
return rootKey;
|
||||
}
|
||||
|
||||
public boolean isAlice() {
|
||||
return alice;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,35 @@
|
||||
package org.briarproject.bramble.api.transport;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
|
||||
|
||||
/**
|
||||
* Contains transport keys for receiving streams from a given contact over a
|
||||
* given transport in a given rotation period.
|
||||
* given transport in a given time period.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class IncomingKeys {
|
||||
|
||||
private final SecretKey tagKey, headerKey;
|
||||
private final long rotationPeriod, windowBase;
|
||||
private final long timePeriod, windowBase;
|
||||
private final byte[] windowBitmap;
|
||||
|
||||
public IncomingKeys(SecretKey tagKey, SecretKey headerKey,
|
||||
long rotationPeriod) {
|
||||
this(tagKey, headerKey, rotationPeriod, 0,
|
||||
long timePeriod) {
|
||||
this(tagKey, headerKey, timePeriod, 0,
|
||||
new byte[REORDERING_WINDOW_SIZE / 8]);
|
||||
}
|
||||
|
||||
public IncomingKeys(SecretKey tagKey, SecretKey headerKey,
|
||||
long rotationPeriod, long windowBase, byte[] windowBitmap) {
|
||||
long timePeriod, long windowBase, byte[] windowBitmap) {
|
||||
this.tagKey = tagKey;
|
||||
this.headerKey = headerKey;
|
||||
this.rotationPeriod = rotationPeriod;
|
||||
this.timePeriod = timePeriod;
|
||||
this.windowBase = windowBase;
|
||||
this.windowBitmap = windowBitmap;
|
||||
}
|
||||
@@ -37,8 +42,8 @@ public class IncomingKeys {
|
||||
return headerKey;
|
||||
}
|
||||
|
||||
public long getRotationPeriod() {
|
||||
return rotationPeriod;
|
||||
public long getTimePeriod() {
|
||||
return timePeriod;
|
||||
}
|
||||
|
||||
public long getWindowBase() {
|
||||
|
||||
@@ -27,14 +27,14 @@ public interface KeyManager {
|
||||
* @param alice true if the local party is Alice
|
||||
* @param active whether the derived keys can be used for outgoing streams
|
||||
*/
|
||||
Map<TransportId, KeySetId> addContact(Transaction txn, ContactId c,
|
||||
SecretKey master, long timestamp, boolean alice, boolean active)
|
||||
Map<TransportId, TransportKeySetId> addContact(Transaction txn, ContactId c,
|
||||
SecretKey rootKey, long timestamp, boolean alice, boolean active)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given transport keys as usable for outgoing streams.
|
||||
*/
|
||||
void activateKeys(Transaction txn, Map<TransportId, KeySetId> keys)
|
||||
void activateKeys(Transaction txn, Map<TransportId, TransportKeySetId> keys)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package org.briarproject.bramble.api.transport;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* A set of transport keys for communicating with a contact.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class KeySet {
|
||||
|
||||
private final KeySetId keySetId;
|
||||
private final ContactId contactId;
|
||||
private final TransportKeys transportKeys;
|
||||
|
||||
public KeySet(KeySetId keySetId, ContactId contactId,
|
||||
TransportKeys transportKeys) {
|
||||
this.keySetId = keySetId;
|
||||
this.contactId = contactId;
|
||||
this.transportKeys = transportKeys;
|
||||
}
|
||||
|
||||
public KeySetId getKeySetId() {
|
||||
return keySetId;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public TransportKeys getTransportKeys() {
|
||||
return transportKeys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return keySetId.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof KeySet && keySetId.equals(((KeySet) o).keySetId);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,32 @@
|
||||
package org.briarproject.bramble.api.transport;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* Contains transport keys for sending streams to a given contact over a given
|
||||
* transport in a given rotation period.
|
||||
* transport in a given time period.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class OutgoingKeys {
|
||||
|
||||
private final SecretKey tagKey, headerKey;
|
||||
private final long rotationPeriod, streamCounter;
|
||||
private final long timePeriod, streamCounter;
|
||||
private final boolean active;
|
||||
|
||||
public OutgoingKeys(SecretKey tagKey, SecretKey headerKey,
|
||||
long rotationPeriod, boolean active) {
|
||||
this(tagKey, headerKey, rotationPeriod, 0, active);
|
||||
long timePeriod, boolean active) {
|
||||
this(tagKey, headerKey, timePeriod, 0, active);
|
||||
}
|
||||
|
||||
public OutgoingKeys(SecretKey tagKey, SecretKey headerKey,
|
||||
long rotationPeriod, long streamCounter, boolean active) {
|
||||
long timePeriod, long streamCounter, boolean active) {
|
||||
this.tagKey = tagKey;
|
||||
this.headerKey = headerKey;
|
||||
this.rotationPeriod = rotationPeriod;
|
||||
this.timePeriod = timePeriod;
|
||||
this.streamCounter = streamCounter;
|
||||
this.active = active;
|
||||
}
|
||||
@@ -34,8 +39,8 @@ public class OutgoingKeys {
|
||||
return headerKey;
|
||||
}
|
||||
|
||||
public long getRotationPeriod() {
|
||||
return rotationPeriod;
|
||||
public long getTimePeriod() {
|
||||
return timePeriod;
|
||||
}
|
||||
|
||||
public long getStreamCounter() {
|
||||
|
||||
@@ -2,8 +2,13 @@ package org.briarproject.bramble.api.transport;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class StreamContext {
|
||||
|
||||
private final ContactId contactId;
|
||||
|
||||
@@ -82,30 +82,58 @@ public interface TransportConstants {
|
||||
int REORDERING_WINDOW_SIZE = 32;
|
||||
|
||||
/**
|
||||
* Label for deriving Alice's initial tag key from the master secret.
|
||||
* Label for deriving Alice's initial tag key from the root key in
|
||||
* rotation mode.
|
||||
*/
|
||||
String ALICE_TAG_LABEL = "org.briarproject.bramble.transport/ALICE_TAG_KEY";
|
||||
|
||||
/**
|
||||
* Label for deriving Bob's initial tag key from the master secret.
|
||||
* Label for deriving Bob's initial tag key from the root key in rotation
|
||||
* mode.
|
||||
*/
|
||||
String BOB_TAG_LABEL = "org.briarproject.bramble.transport/BOB_TAG_KEY";
|
||||
|
||||
/**
|
||||
* Label for deriving Alice's initial header key from the master secret.
|
||||
* Label for deriving Alice's initial header key from the root key in
|
||||
* rotation mode.
|
||||
*/
|
||||
String ALICE_HEADER_LABEL =
|
||||
"org.briarproject.bramble.transport/ALICE_HEADER_KEY";
|
||||
|
||||
/**
|
||||
* Label for deriving Bob's initial header key from the master secret.
|
||||
* Label for deriving Bob's initial header key from the root key in
|
||||
* rotation mode.
|
||||
*/
|
||||
String BOB_HEADER_LABEL =
|
||||
"org.briarproject.bramble.transport/BOB_HEADER_KEY";
|
||||
|
||||
/**
|
||||
* Label for deriving the next period's key in key rotation.
|
||||
* Label for deriving the next period's key in rotation mode.
|
||||
*/
|
||||
String ROTATE_LABEL = "org.briarproject.bramble.transport/ROTATE";
|
||||
|
||||
/**
|
||||
* Label for deriving Alice's tag key from the root key in handshake mode.
|
||||
*/
|
||||
String ALICE_HANDSHAKE_TAG_LABEL =
|
||||
"org.briarproject.bramble.transport/ALICE_HANDSHAKE_TAG_KEY";
|
||||
|
||||
/**
|
||||
* Label for deriving Bob's tag key from the root key in handshake mode.
|
||||
*/
|
||||
String BOB_HANDSHAKE_TAG_LABEL =
|
||||
"org.briarproject.bramble.transport/BOB_HANDSHAKE_TAG_KEY";
|
||||
|
||||
/**
|
||||
* Label for deriving Alice's header key from the root key in handshake
|
||||
* mode.
|
||||
*/
|
||||
String ALICE_HANDSHAKE_HEADER_LABEL =
|
||||
"org.briarproject.bramble.transport/ALICE_HANDSHAKE_HEADER_KEY";
|
||||
|
||||
/**
|
||||
* Label for deriving Bob's header key from the root key in handshake mode.
|
||||
*/
|
||||
String BOB_HANDSHAKE_HEADER_LABEL =
|
||||
"org.briarproject.bramble.transport/BOB_HANDSHAKE_HEADER_KEY";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.briarproject.bramble.api.transport;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* A set of keys for communicating with a given contact over a given transport.
|
||||
* Unlike a {@link HandshakeKeySet} these keys provide forward secrecy.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class TransportKeySet {
|
||||
|
||||
private final TransportKeySetId keySetId;
|
||||
private final ContactId contactId;
|
||||
private final TransportKeys keys;
|
||||
|
||||
public TransportKeySet(TransportKeySetId keySetId, ContactId contactId,
|
||||
TransportKeys keys) {
|
||||
this.keySetId = keySetId;
|
||||
this.contactId = contactId;
|
||||
this.keys = keys;
|
||||
}
|
||||
|
||||
public TransportKeySetId getKeySetId() {
|
||||
return keySetId;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public TransportKeys getKeys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return keySetId.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof TransportKeySet &&
|
||||
keySetId.equals(((TransportKeySet) o).keySetId);
|
||||
}
|
||||
}
|
||||
@@ -5,18 +5,19 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* Type-safe wrapper for an integer that uniquely identifies a set of transport
|
||||
* keys within the scope of the local device.
|
||||
* Type-safe wrapper for an integer that uniquely identifies a
|
||||
* {@link TransportKeySet set of transport keys} within the scope of the local
|
||||
* device.
|
||||
* <p/>
|
||||
* Key sets created on a given device must have increasing identifiers.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class KeySetId {
|
||||
public class TransportKeySetId {
|
||||
|
||||
private final int id;
|
||||
|
||||
public KeySetId(int id) {
|
||||
public TransportKeySetId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@@ -31,6 +32,7 @@ public class KeySetId {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof KeySetId && id == ((KeySetId) o).id;
|
||||
return o instanceof TransportKeySetId &&
|
||||
id == ((TransportKeySetId) o).id;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,20 @@
|
||||
package org.briarproject.bramble.api.transport;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
|
||||
/**
|
||||
* Keys for communicating with a given contact over a given transport.
|
||||
*/
|
||||
public class TransportKeys {
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
private final TransportId transportId;
|
||||
private final IncomingKeys inPrev, inCurr, inNext;
|
||||
private final OutgoingKeys outCurr;
|
||||
/**
|
||||
* Keys for communicating with a given contact over a given transport. Unlike
|
||||
* {@link HandshakeKeys} these keys provide forward secrecy.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class TransportKeys extends AbstractTransportKeys {
|
||||
|
||||
public TransportKeys(TransportId transportId, IncomingKeys inPrev,
|
||||
IncomingKeys inCurr, IncomingKeys inNext, OutgoingKeys outCurr) {
|
||||
if (inPrev.getRotationPeriod() != inCurr.getRotationPeriod() - 1)
|
||||
throw new IllegalArgumentException();
|
||||
if (inNext.getRotationPeriod() != inCurr.getRotationPeriod() + 1)
|
||||
throw new IllegalArgumentException();
|
||||
if (outCurr.getRotationPeriod() != inCurr.getRotationPeriod())
|
||||
throw new IllegalArgumentException();
|
||||
this.transportId = transportId;
|
||||
this.inPrev = inPrev;
|
||||
this.inCurr = inCurr;
|
||||
this.inNext = inNext;
|
||||
this.outCurr = outCurr;
|
||||
}
|
||||
|
||||
public TransportId getTransportId() {
|
||||
return transportId;
|
||||
}
|
||||
|
||||
public IncomingKeys getPreviousIncomingKeys() {
|
||||
return inPrev;
|
||||
}
|
||||
|
||||
public IncomingKeys getCurrentIncomingKeys() {
|
||||
return inCurr;
|
||||
}
|
||||
|
||||
public IncomingKeys getNextIncomingKeys() {
|
||||
return inNext;
|
||||
}
|
||||
|
||||
public OutgoingKeys getCurrentOutgoingKeys() {
|
||||
return outCurr;
|
||||
}
|
||||
|
||||
public long getRotationPeriod() {
|
||||
return outCurr.getRotationPeriod();
|
||||
super(transportId, inPrev, inCurr, inNext, outCurr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
import org.briarproject.bramble.api.contact.PendingContactId;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
@@ -25,6 +29,7 @@ import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
|
||||
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
@@ -41,6 +46,7 @@ public class TestUtils {
|
||||
new AtomicInteger((int) (Math.random() * 1000 * 1000));
|
||||
private static final Random random = new Random();
|
||||
private static final long timestamp = System.currentTimeMillis();
|
||||
private static final AtomicInteger nextContactId = new AtomicInteger(1);
|
||||
|
||||
public static File getTestDirectory() {
|
||||
int name = nextTestDir.getAndIncrement();
|
||||
@@ -140,6 +146,35 @@ public class TestUtils {
|
||||
return new Message(id, groupId, timestamp, body);
|
||||
}
|
||||
|
||||
public static PendingContact getPendingContact() {
|
||||
return getPendingContact(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
|
||||
}
|
||||
|
||||
public static PendingContact getPendingContact(int nameLength) {
|
||||
PendingContactId id = new PendingContactId(getRandomId());
|
||||
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
String alias = getRandomString(nameLength);
|
||||
return new PendingContact(id, publicKey, alias, WAITING_FOR_CONNECTION,
|
||||
timestamp);
|
||||
}
|
||||
|
||||
public static ContactId getContactId() {
|
||||
return new ContactId(nextContactId.getAndIncrement());
|
||||
}
|
||||
|
||||
public static Contact getContact() {
|
||||
return getContact(getAuthor(), random.nextBoolean());
|
||||
}
|
||||
|
||||
public static Contact getContact(Author a, boolean verified) {
|
||||
return getContact(getContactId(), a, verified);
|
||||
}
|
||||
|
||||
public static Contact getContact(ContactId c, Author a, boolean verified) {
|
||||
return new Contact(c, a, getRandomString(MAX_AUTHOR_NAME_LENGTH),
|
||||
getRandomBytes(MAX_PUBLIC_KEY_LENGTH), verified);
|
||||
}
|
||||
|
||||
public static double getMedian(Collection<? extends Number> samples) {
|
||||
int size = samples.size();
|
||||
if (size == 0) throw new IllegalArgumentException();
|
||||
|
||||
@@ -15,6 +15,7 @@ dependencies {
|
||||
implementation 'org.bitlet:weupnp:0.1.4'
|
||||
implementation 'net.i2p.crypto:eddsa:0.2.0'
|
||||
implementation 'org.whispersystems:curve25519-java:0.5.0'
|
||||
implementation 'org.briarproject:jtorctl:0.3'
|
||||
|
||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.19'
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
*.class
|
||||
@@ -1,114 +0,0 @@
|
||||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Static class to do bytewise structure manipulation in Java.
|
||||
*/
|
||||
/* XXXX There must be a better way to do most of this.
|
||||
* XXXX The string logic here uses default encoding, which is stupid.
|
||||
*/
|
||||
final class Bytes {
|
||||
|
||||
/** Write the two-byte value in 's' into the byte array 'ba', starting at
|
||||
* the index 'pos'. */
|
||||
public static void setU16(byte[] ba, int pos, short s) {
|
||||
ba[pos] = (byte)((s >> 8) & 0xff);
|
||||
ba[pos+1] = (byte)((s ) & 0xff);
|
||||
}
|
||||
|
||||
/** Write the four-byte value in 'i' into the byte array 'ba', starting at
|
||||
* the index 'pos'. */
|
||||
public static void setU32(byte[] ba, int pos, int i) {
|
||||
ba[pos] = (byte)((i >> 24) & 0xff);
|
||||
ba[pos+1] = (byte)((i >> 16) & 0xff);
|
||||
ba[pos+2] = (byte)((i >> 8) & 0xff);
|
||||
ba[pos+3] = (byte)((i ) & 0xff);
|
||||
}
|
||||
|
||||
/** Return the four-byte value starting at index 'pos' within 'ba' */
|
||||
public static int getU32(byte[] ba, int pos) {
|
||||
return
|
||||
((ba[pos ]&0xff)<<24) |
|
||||
((ba[pos+1]&0xff)<<16) |
|
||||
((ba[pos+2]&0xff)<< 8) |
|
||||
((ba[pos+3]&0xff));
|
||||
}
|
||||
|
||||
public static String getU32S(byte[] ba, int pos) {
|
||||
return String.valueOf( (getU32(ba,pos))&0xffffffffL );
|
||||
}
|
||||
|
||||
/** Return the two-byte value starting at index 'pos' within 'ba' */
|
||||
public static int getU16(byte[] ba, int pos) {
|
||||
return
|
||||
((ba[pos ]&0xff)<<8) |
|
||||
((ba[pos+1]&0xff));
|
||||
}
|
||||
|
||||
/** Return the string starting at position 'pos' of ba and extending
|
||||
* until a zero byte or the end of the string. */
|
||||
public static String getNulTerminatedStr(byte[] ba, int pos) {
|
||||
int len, maxlen = ba.length-pos;
|
||||
for (len=0; len<maxlen; ++len) {
|
||||
if (ba[pos+len] == 0)
|
||||
break;
|
||||
}
|
||||
return new String(ba, pos, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read bytes from 'ba' starting at 'pos', dividing them into strings
|
||||
* along the character in 'split' and writing them into 'lst'
|
||||
*/
|
||||
public static void splitStr(List<String> lst, byte[] ba, int pos, byte split) {
|
||||
while (pos < ba.length && ba[pos] != 0) {
|
||||
int len;
|
||||
for (len=0; pos+len < ba.length; ++len) {
|
||||
if (ba[pos+len] == 0 || ba[pos+len] == split)
|
||||
break;
|
||||
}
|
||||
if (len>0)
|
||||
lst.add(new String(ba, pos, len));
|
||||
pos += len;
|
||||
if (ba[pos] == split)
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read bytes from 'ba' starting at 'pos', dividing them into strings
|
||||
* along the character in 'split' and writing them into 'lst'
|
||||
*/
|
||||
public static List<String> splitStr(List<String> lst, String str) {
|
||||
// split string on spaces, include trailing/leading
|
||||
String[] tokenArray = str.split(" ", -1);
|
||||
if (lst == null) {
|
||||
lst = Arrays.asList( tokenArray );
|
||||
} else {
|
||||
lst.addAll( Arrays.asList( tokenArray ) );
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
|
||||
private static final char[] NYBBLES = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
|
||||
};
|
||||
|
||||
public static final String hex(byte[] ba) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int i = 0; i < ba.length; ++i) {
|
||||
int b = (ba[i]) & 0xff;
|
||||
buf.append(NYBBLES[b >> 4]);
|
||||
buf.append(NYBBLES[b&0x0f]);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private Bytes() {};
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
/** A single key-value pair from Tor's configuration. */
|
||||
public class ConfigEntry {
|
||||
public ConfigEntry(String k, String v) {
|
||||
key = k;
|
||||
value = v;
|
||||
is_default = false;
|
||||
}
|
||||
public ConfigEntry(String k) {
|
||||
key = k;
|
||||
value = "";
|
||||
is_default = true;
|
||||
}
|
||||
public final String key;
|
||||
public final String value;
|
||||
public final boolean is_default;
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
/**
|
||||
* Abstract interface whose methods are invoked when Tor sends us an event.
|
||||
*
|
||||
* @see TorControlConnection#setEventHandler
|
||||
* @see TorControlConnection#setEvents
|
||||
*/
|
||||
public interface EventHandler {
|
||||
/**
|
||||
* Invoked when a circuit's status has changed.
|
||||
* Possible values for <b>status</b> are:
|
||||
* <ul>
|
||||
* <li>"LAUNCHED" : circuit ID assigned to new circuit</li>
|
||||
* <li>"BUILT" : all hops finished, can now accept streams</li>
|
||||
* <li>"EXTENDED" : one more hop has been completed</li>
|
||||
* <li>"FAILED" : circuit closed (was not built)</li>
|
||||
* <li>"CLOSED" : circuit closed (was built)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <b>circID</b> is the alphanumeric identifier of the affected circuit,
|
||||
* and <b>path</b> is a comma-separated list of alphanumeric ServerIDs.
|
||||
*/
|
||||
public void circuitStatus(String status, String circID, String path);
|
||||
/**
|
||||
* Invoked when a stream's status has changed.
|
||||
* Possible values for <b>status</b> are:
|
||||
* <ul>
|
||||
* <li>"NEW" : New request to connect</li>
|
||||
* <li>"NEWRESOLVE" : New request to resolve an address</li>
|
||||
* <li>"SENTCONNECT" : Sent a connect cell along a circuit</li>
|
||||
* <li>"SENTRESOLVE" : Sent a resolve cell along a circuit</li>
|
||||
* <li>"SUCCEEDED" : Received a reply; stream established</li>
|
||||
* <li>"FAILED" : Stream failed and not retriable.</li>
|
||||
* <li>"CLOSED" : Stream closed</li>
|
||||
* <li>"DETACHED" : Detached from circuit; still retriable.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <b>streamID</b> is the alphanumeric identifier of the affected stream,
|
||||
* and its <b>target</b> is specified as address:port.
|
||||
*/
|
||||
public void streamStatus(String status, String streamID, String target);
|
||||
/**
|
||||
* Invoked when the status of a connection to an OR has changed.
|
||||
* Possible values for <b>status</b> are ["LAUNCHED" | "CONNECTED" | "FAILED" | "CLOSED"].
|
||||
* <b>orName</b> is the alphanumeric identifier of the OR affected.
|
||||
*/
|
||||
public void orConnStatus(String status, String orName);
|
||||
/**
|
||||
* Invoked once per second. <b>read</b> and <b>written</b> are
|
||||
* the number of bytes read and written, respectively, in
|
||||
* the last second.
|
||||
*/
|
||||
public void bandwidthUsed(long read, long written);
|
||||
/**
|
||||
* Invoked whenever Tor learns about new ORs. The <b>orList</b> object
|
||||
* contains the alphanumeric ServerIDs associated with the new ORs.
|
||||
*/
|
||||
public void newDescriptors(java.util.List<String> orList);
|
||||
/**
|
||||
* Invoked when Tor logs a message.
|
||||
* <b>severity</b> is one of ["DEBUG" | "INFO" | "NOTICE" | "WARN" | "ERR"],
|
||||
* and <b>msg</b> is the message string.
|
||||
*/
|
||||
public void message(String severity, String msg);
|
||||
/**
|
||||
* Invoked when an unspecified message is received.
|
||||
* <type> is the message type, and <msg> is the message string.
|
||||
*/
|
||||
public void unrecognized(String type, String msg);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
/**
|
||||
* Implementation of EventHandler that ignores all events. Useful
|
||||
* when you only want to override one method.
|
||||
*/
|
||||
public class NullEventHandler implements EventHandler {
|
||||
public void circuitStatus(String status, String circID, String path) {}
|
||||
public void streamStatus(String status, String streamID, String target) {}
|
||||
public void orConnStatus(String status, String orName) {}
|
||||
public void bandwidthUsed(long read, long written) {}
|
||||
public void newDescriptors(java.util.List<String> orList) {}
|
||||
public void message(String severity, String msg) {}
|
||||
public void unrecognized(String type, String msg) {}
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* A hashed digest of a secret password (used to set control connection
|
||||
* security.)
|
||||
*
|
||||
* For the actual hashing algorithm, see RFC2440's secret-to-key conversion.
|
||||
*/
|
||||
public class PasswordDigest {
|
||||
|
||||
private final byte[] secret;
|
||||
private final String hashedKey;
|
||||
|
||||
/** Return a new password digest with a random secret and salt. */
|
||||
public static PasswordDigest generateDigest() {
|
||||
byte[] secret = new byte[20];
|
||||
SecureRandom rng = new SecureRandom();
|
||||
rng.nextBytes(secret);
|
||||
return new PasswordDigest(secret);
|
||||
}
|
||||
|
||||
/** Construct a new password digest with a given secret and random salt */
|
||||
public PasswordDigest(byte[] secret) {
|
||||
this(secret, null);
|
||||
}
|
||||
|
||||
/** Construct a new password digest with a given secret and random salt.
|
||||
* Note that the 9th byte of the specifier determines the number of hash
|
||||
* iterations as in RFC2440.
|
||||
*/
|
||||
public PasswordDigest(byte[] secret, byte[] specifier) {
|
||||
this.secret = secret.clone();
|
||||
if (specifier == null) {
|
||||
specifier = new byte[9];
|
||||
SecureRandom rng = new SecureRandom();
|
||||
rng.nextBytes(specifier);
|
||||
specifier[8] = 96;
|
||||
}
|
||||
hashedKey = "16:"+encodeBytes(secretToKey(secret, specifier));
|
||||
}
|
||||
|
||||
/** Return the secret used to generate this password hash.
|
||||
*/
|
||||
public byte[] getSecret() {
|
||||
return secret.clone();
|
||||
}
|
||||
|
||||
/** Return the hashed password in the format used by Tor. */
|
||||
public String getHashedPassword() {
|
||||
return hashedKey;
|
||||
}
|
||||
|
||||
/** Parameter used by RFC2440's s2k algorithm. */
|
||||
private static final int EXPBIAS = 6;
|
||||
|
||||
/** Implement rfc2440 s2k */
|
||||
public static byte[] secretToKey(byte[] secret, byte[] specifier) {
|
||||
MessageDigest d;
|
||||
try {
|
||||
d = MessageDigest.getInstance("SHA-1");
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
throw new RuntimeException("Can't run without sha-1.");
|
||||
}
|
||||
int c = (specifier[8])&0xff;
|
||||
int count = (16 + (c&15)) << ((c>>4) + EXPBIAS);
|
||||
|
||||
byte[] tmp = new byte[8+secret.length];
|
||||
System.arraycopy(specifier, 0, tmp, 0, 8);
|
||||
System.arraycopy(secret, 0, tmp, 8, secret.length);
|
||||
while (count > 0) {
|
||||
if (count >= tmp.length) {
|
||||
d.update(tmp);
|
||||
count -= tmp.length;
|
||||
} else {
|
||||
d.update(tmp, 0, count);
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
byte[] key = new byte[20+9];
|
||||
System.arraycopy(d.digest(), 0, key, 9, 20);
|
||||
System.arraycopy(specifier, 0, key, 0, 9);
|
||||
return key;
|
||||
}
|
||||
|
||||
/** Return a hexadecimal encoding of a byte array. */
|
||||
// XXX There must be a better way to do this in Java.
|
||||
private static final String encodeBytes(byte[] ba) {
|
||||
return Bytes.hex(ba);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
We broke the version detection stuff in Tor 0.1.2.16 / 0.2.0.4-alpha.
|
||||
Somebody should rip out the v0 control protocol stuff from here, and
|
||||
it should start working again. -RD
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
/** Interface defining constants used by the Tor controller protocol.
|
||||
*/
|
||||
// XXXX Take documentation for these from control-spec.txt
|
||||
public interface TorControlCommands {
|
||||
|
||||
public static final short CMD_ERROR = 0x0000;
|
||||
public static final short CMD_DONE = 0x0001;
|
||||
public static final short CMD_SETCONF = 0x0002;
|
||||
public static final short CMD_GETCONF = 0x0003;
|
||||
public static final short CMD_CONFVALUE = 0x0004;
|
||||
public static final short CMD_SETEVENTS = 0x0005;
|
||||
public static final short CMD_EVENT = 0x0006;
|
||||
public static final short CMD_AUTH = 0x0007;
|
||||
public static final short CMD_SAVECONF = 0x0008;
|
||||
public static final short CMD_SIGNAL = 0x0009;
|
||||
public static final short CMD_MAPADDRESS = 0x000A;
|
||||
public static final short CMD_GETINFO = 0x000B;
|
||||
public static final short CMD_INFOVALUE = 0x000C;
|
||||
public static final short CMD_EXTENDCIRCUIT = 0x000D;
|
||||
public static final short CMD_ATTACHSTREAM = 0x000E;
|
||||
public static final short CMD_POSTDESCRIPTOR = 0x000F;
|
||||
public static final short CMD_FRAGMENTHEADER = 0x0010;
|
||||
public static final short CMD_FRAGMENT = 0x0011;
|
||||
public static final short CMD_REDIRECTSTREAM = 0x0012;
|
||||
public static final short CMD_CLOSESTREAM = 0x0013;
|
||||
public static final short CMD_CLOSECIRCUIT = 0x0014;
|
||||
|
||||
public static final String[] CMD_NAMES = {
|
||||
"ERROR",
|
||||
"DONE",
|
||||
"SETCONF",
|
||||
"GETCONF",
|
||||
"CONFVALUE",
|
||||
"SETEVENTS",
|
||||
"EVENT",
|
||||
"AUTH",
|
||||
"SAVECONF",
|
||||
"SIGNAL",
|
||||
"MAPADDRESS",
|
||||
"GETINFO",
|
||||
"INFOVALUE",
|
||||
"EXTENDCIRCUIT",
|
||||
"ATTACHSTREAM",
|
||||
"POSTDESCRIPTOR",
|
||||
"FRAGMENTHEADER",
|
||||
"FRAGMENT",
|
||||
"REDIRECTSTREAM",
|
||||
"CLOSESTREAM",
|
||||
"CLOSECIRCUIT",
|
||||
};
|
||||
|
||||
public static final short EVENT_CIRCSTATUS = 0x0001;
|
||||
public static final short EVENT_STREAMSTATUS = 0x0002;
|
||||
public static final short EVENT_ORCONNSTATUS = 0x0003;
|
||||
public static final short EVENT_BANDWIDTH = 0x0004;
|
||||
public static final short EVENT_NEWDESCRIPTOR = 0x0006;
|
||||
public static final short EVENT_MSG_DEBUG = 0x0007;
|
||||
public static final short EVENT_MSG_INFO = 0x0008;
|
||||
public static final short EVENT_MSG_NOTICE = 0x0009;
|
||||
public static final short EVENT_MSG_WARN = 0x000A;
|
||||
public static final short EVENT_MSG_ERROR = 0x000B;
|
||||
|
||||
public static final String[] EVENT_NAMES = {
|
||||
"(0)",
|
||||
"CIRC",
|
||||
"STREAM",
|
||||
"ORCONN",
|
||||
"BW",
|
||||
"OLDLOG",
|
||||
"NEWDESC",
|
||||
"DEBUG",
|
||||
"INFO",
|
||||
"NOTICE",
|
||||
"WARN",
|
||||
"ERR",
|
||||
};
|
||||
|
||||
public static final byte CIRC_STATUS_LAUNCHED = 0x01;
|
||||
public static final byte CIRC_STATUS_BUILT = 0x02;
|
||||
public static final byte CIRC_STATUS_EXTENDED = 0x03;
|
||||
public static final byte CIRC_STATUS_FAILED = 0x04;
|
||||
public static final byte CIRC_STATUS_CLOSED = 0x05;
|
||||
|
||||
public static final String[] CIRC_STATUS_NAMES = {
|
||||
"LAUNCHED",
|
||||
"BUILT",
|
||||
"EXTENDED",
|
||||
"FAILED",
|
||||
"CLOSED",
|
||||
};
|
||||
|
||||
public static final byte STREAM_STATUS_SENT_CONNECT = 0x00;
|
||||
public static final byte STREAM_STATUS_SENT_RESOLVE = 0x01;
|
||||
public static final byte STREAM_STATUS_SUCCEEDED = 0x02;
|
||||
public static final byte STREAM_STATUS_FAILED = 0x03;
|
||||
public static final byte STREAM_STATUS_CLOSED = 0x04;
|
||||
public static final byte STREAM_STATUS_NEW_CONNECT = 0x05;
|
||||
public static final byte STREAM_STATUS_NEW_RESOLVE = 0x06;
|
||||
public static final byte STREAM_STATUS_DETACHED = 0x07;
|
||||
|
||||
public static final String[] STREAM_STATUS_NAMES = {
|
||||
"SENT_CONNECT",
|
||||
"SENT_RESOLVE",
|
||||
"SUCCEEDED",
|
||||
"FAILED",
|
||||
"CLOSED",
|
||||
"NEW_CONNECT",
|
||||
"NEW_RESOLVE",
|
||||
"DETACHED"
|
||||
};
|
||||
|
||||
public static final byte OR_CONN_STATUS_LAUNCHED = 0x00;
|
||||
public static final byte OR_CONN_STATUS_CONNECTED = 0x01;
|
||||
public static final byte OR_CONN_STATUS_FAILED = 0x02;
|
||||
public static final byte OR_CONN_STATUS_CLOSED = 0x03;
|
||||
|
||||
public static final String[] OR_CONN_STATUS_NAMES = {
|
||||
"LAUNCHED","CONNECTED","FAILED","CLOSED"
|
||||
};
|
||||
|
||||
public static final byte SIGNAL_HUP = 0x01;
|
||||
public static final byte SIGNAL_INT = 0x02;
|
||||
public static final byte SIGNAL_USR1 = 0x0A;
|
||||
public static final byte SIGNAL_USR2 = 0x0C;
|
||||
public static final byte SIGNAL_TERM = 0x0F;
|
||||
|
||||
public static final String ERROR_MSGS[] = {
|
||||
"Unspecified error",
|
||||
"Internal error",
|
||||
"Unrecognized message type",
|
||||
"Syntax error",
|
||||
"Unrecognized configuration key",
|
||||
"Invalid configuration value",
|
||||
"Unrecognized byte code",
|
||||
"Unauthorized",
|
||||
"Failed authentication attempt",
|
||||
"Resource exhausted",
|
||||
"No such stream",
|
||||
"No such circuit",
|
||||
"No such OR",
|
||||
};
|
||||
|
||||
public static final String HS_ADDRESS = "onionAddress";
|
||||
public static final String HS_PRIVKEY = "onionPrivKey";
|
||||
|
||||
}
|
||||
|
||||
@@ -1,998 +0,0 @@
|
||||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
|
||||
/**
|
||||
* A connection to a running Tor process as specified in control-spec.txt.
|
||||
*/
|
||||
public class TorControlConnection implements TorControlCommands {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(TorControlConnection.class.getName());
|
||||
|
||||
private final LinkedList<Waiter> waiters;
|
||||
private final BufferedReader input;
|
||||
private final Writer output;
|
||||
|
||||
private ControlParseThread thread; // Locking: this
|
||||
|
||||
private volatile EventHandler handler;
|
||||
private volatile PrintWriter debugOutput;
|
||||
private volatile IOException parseThreadException;
|
||||
|
||||
static class Waiter {
|
||||
|
||||
List<ReplyLine> response; // Locking: this
|
||||
boolean interrupted;
|
||||
|
||||
List<ReplyLine> getResponse() throws InterruptedException {
|
||||
LOG.info("Entering synchronized (waiter " + hashCode() + ")");
|
||||
synchronized (this) {
|
||||
LOG.info("Entered synchronized (waiter " + hashCode() + ")");
|
||||
while (response == null) {
|
||||
LOG.info("Waiter " + hashCode() + " waiting for response");
|
||||
wait();
|
||||
if (interrupted) {
|
||||
LOG.info("Waiter " + hashCode() + " interrupted");
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
LOG.info("Waiter " + hashCode() + " got response " + response);
|
||||
LOG.info("Leaving synchronized (waiter " + hashCode() + ")");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
void setResponse(List<ReplyLine> response) {
|
||||
LOG.info("Entering synchronized (waiter " + hashCode() + ")");
|
||||
synchronized (this) {
|
||||
LOG.info("Entered synchronized (waiter " + hashCode() + ")");
|
||||
LOG.info("Setting response for waiter " + hashCode() + ": "
|
||||
+ response);
|
||||
this.response = response;
|
||||
notifyAll();
|
||||
LOG.info("Leaving synchronized (waiter " + hashCode() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
void interrupt() {
|
||||
LOG.info("Entering synchronized (waiter " + hashCode() + ")");
|
||||
synchronized (this) {
|
||||
LOG.info("Entered synchronized (waiter " + hashCode() + ")");
|
||||
LOG.info("Interrupting waiter " + hashCode());
|
||||
interrupted = true;
|
||||
notifyAll();
|
||||
LOG.info("Leaving synchronized (waiter " + hashCode() + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class ReplyLine {
|
||||
|
||||
final String status;
|
||||
final String msg;
|
||||
final String rest;
|
||||
|
||||
ReplyLine(String status, String msg, String rest) {
|
||||
this.status = status;
|
||||
this.msg = msg;
|
||||
this.rest = rest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return status + " " + msg + " " + rest;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new TorControlConnection to communicate with Tor over
|
||||
* a given socket. After calling this constructor, it is typical to
|
||||
* call launchThread and authenticate.
|
||||
*/
|
||||
public TorControlConnection(Socket connection) throws IOException {
|
||||
this(connection.getInputStream(), connection.getOutputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new TorControlConnection to communicate with Tor over
|
||||
* an arbitrary pair of data streams.
|
||||
*/
|
||||
public TorControlConnection(InputStream i, OutputStream o) {
|
||||
this(new InputStreamReader(i), new OutputStreamWriter(o));
|
||||
}
|
||||
|
||||
public TorControlConnection(Reader i, Writer o) {
|
||||
this.output = o;
|
||||
if (i instanceof BufferedReader)
|
||||
this.input = (BufferedReader) i;
|
||||
else
|
||||
this.input = new BufferedReader(i);
|
||||
this.waiters = new LinkedList<>();
|
||||
}
|
||||
|
||||
protected final void writeEscaped(String s) throws IOException {
|
||||
StringTokenizer st = new StringTokenizer(s, "\n");
|
||||
while (st.hasMoreTokens()) {
|
||||
String line = st.nextToken();
|
||||
if (line.startsWith("."))
|
||||
line = "." + line;
|
||||
if (line.endsWith("\r"))
|
||||
line += "\n";
|
||||
else
|
||||
line += "\r\n";
|
||||
if (debugOutput != null)
|
||||
debugOutput.print(">> " + line);
|
||||
output.write(line);
|
||||
}
|
||||
output.write(".\r\n");
|
||||
if (debugOutput != null)
|
||||
debugOutput.print(">> .\n");
|
||||
}
|
||||
|
||||
protected static String quote(String s) {
|
||||
StringBuffer sb = new StringBuffer("\"");
|
||||
for (int i = 0; i < s.length(); ++i) {
|
||||
char c = s.charAt(i);
|
||||
switch (c) {
|
||||
case '\r':
|
||||
case '\n':
|
||||
case '\\':
|
||||
case '\"':
|
||||
sb.append('\\');
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
sb.append('\"');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected final ArrayList<ReplyLine> readReply() throws IOException {
|
||||
ArrayList<ReplyLine> reply = new ArrayList<>();
|
||||
char c;
|
||||
do {
|
||||
String line = input.readLine();
|
||||
if (line == null) {
|
||||
// if line is null, the end of the stream has been reached, i.e.
|
||||
// the connection to Tor has been closed!
|
||||
if (reply.isEmpty()) {
|
||||
// nothing received so far, can exit cleanly
|
||||
return reply;
|
||||
}
|
||||
// received half of a reply before the connection broke down
|
||||
throw new TorControlSyntaxError("Connection to Tor " +
|
||||
" broke down while receiving reply!");
|
||||
}
|
||||
if (debugOutput != null)
|
||||
debugOutput.println("<< " + line);
|
||||
if (line.length() < 4)
|
||||
throw new TorControlSyntaxError(
|
||||
"Line (\"" + line + "\") too short");
|
||||
String status = line.substring(0, 3);
|
||||
c = line.charAt(3);
|
||||
String msg = line.substring(4);
|
||||
String rest = null;
|
||||
if (c == '+') {
|
||||
StringBuffer data = new StringBuffer();
|
||||
while (true) {
|
||||
line = input.readLine();
|
||||
if (debugOutput != null)
|
||||
debugOutput.print("<< " + line);
|
||||
if (line.equals("."))
|
||||
break;
|
||||
else if (line.startsWith("."))
|
||||
line = line.substring(1);
|
||||
data.append(line).append('\n');
|
||||
}
|
||||
rest = data.toString();
|
||||
}
|
||||
reply.add(new ReplyLine(status, msg, rest));
|
||||
} while (c != ' ');
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
protected List<ReplyLine> sendAndWaitForResponse(String s,
|
||||
String rest) throws IOException {
|
||||
LOG.info("Entering synchronized (connection)");
|
||||
synchronized (this) {
|
||||
LOG.info("Entered synchronized (connection)");
|
||||
LOG.info("Sending '" + s + "', '" + rest +
|
||||
"' and waiting for response");
|
||||
if (parseThreadException != null) {
|
||||
LOG.info("Throwing previously caught exception "
|
||||
+ parseThreadException);
|
||||
throw parseThreadException;
|
||||
}
|
||||
checkThread();
|
||||
Waiter w = new Waiter();
|
||||
LOG.info("Created waiter " + w.hashCode());
|
||||
if (debugOutput != null)
|
||||
debugOutput.print(">> " + s);
|
||||
LOG.info("Entering synchronized (waiters)");
|
||||
synchronized (waiters) {
|
||||
LOG.info("Entered synchronized (waiters)");
|
||||
output.write(s);
|
||||
LOG.info("Wrote '" + s + "'");
|
||||
if (rest != null) {
|
||||
writeEscaped(rest);
|
||||
LOG.info("Wrote escaped '" + rest + "'");
|
||||
}
|
||||
output.flush();
|
||||
LOG.info("Flushed output");
|
||||
waiters.addLast(w);
|
||||
LOG.info("Added waiter, " + waiters.size() + " waiting");
|
||||
LOG.info("Leaving synchronized (waiters)");
|
||||
}
|
||||
List<ReplyLine> lst;
|
||||
try {
|
||||
LOG.info("Getting response from waiter " + w.hashCode());
|
||||
lst = w.getResponse();
|
||||
LOG.info("Got response from waiter " + w.hashCode() + ": " +
|
||||
lst);
|
||||
} catch (InterruptedException ex) {
|
||||
throw new IOException("Interrupted");
|
||||
}
|
||||
for (Iterator<ReplyLine> i = lst.iterator(); i.hasNext(); ) {
|
||||
ReplyLine c = i.next();
|
||||
if (!c.status.startsWith("2"))
|
||||
throw new TorControlError("Error reply: " + c.msg);
|
||||
}
|
||||
LOG.info("Leaving synchronized (connection)");
|
||||
return lst;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: decode a CMD_EVENT command and dispatch it to our
|
||||
* EventHandler (if any).
|
||||
*/
|
||||
protected void handleEvent(ArrayList<ReplyLine> events) {
|
||||
if (handler == null)
|
||||
return;
|
||||
|
||||
for (Iterator<ReplyLine> i = events.iterator(); i.hasNext(); ) {
|
||||
ReplyLine line = i.next();
|
||||
int idx = line.msg.indexOf(' ');
|
||||
String tp = line.msg.substring(0, idx).toUpperCase();
|
||||
String rest = line.msg.substring(idx + 1);
|
||||
if (tp.equals("CIRC")) {
|
||||
List<String> lst = Bytes.splitStr(null, rest);
|
||||
handler.circuitStatus(lst.get(1),
|
||||
lst.get(0),
|
||||
lst.get(1).equals("LAUNCHED")
|
||||
|| lst.size() < 3 ? ""
|
||||
: lst.get(2));
|
||||
} else if (tp.equals("STREAM")) {
|
||||
List<String> lst = Bytes.splitStr(null, rest);
|
||||
handler.streamStatus(lst.get(1),
|
||||
lst.get(0),
|
||||
lst.get(3));
|
||||
// XXXX circID.
|
||||
} else if (tp.equals("ORCONN")) {
|
||||
List<String> lst = Bytes.splitStr(null, rest);
|
||||
handler.orConnStatus(lst.get(1), lst.get(0));
|
||||
} else if (tp.equals("BW")) {
|
||||
List<String> lst = Bytes.splitStr(null, rest);
|
||||
handler.bandwidthUsed(Integer.parseInt(lst.get(0)),
|
||||
Integer.parseInt(lst.get(1)));
|
||||
} else if (tp.equals("NEWDESC")) {
|
||||
List<String> lst = Bytes.splitStr(null, rest);
|
||||
handler.newDescriptors(lst);
|
||||
} else if (tp.equals("DEBUG") ||
|
||||
tp.equals("INFO") ||
|
||||
tp.equals("NOTICE") ||
|
||||
tp.equals("WARN") ||
|
||||
tp.equals("ERR")) {
|
||||
handler.message(tp, rest);
|
||||
} else {
|
||||
handler.unrecognized(tp, rest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets <b>w</b> as the PrintWriter for debugging output,
|
||||
* which writes out all messages passed between Tor and the controller.
|
||||
* Outgoing messages are preceded by "\>\>" and incoming messages are preceded
|
||||
* by "\<\<"
|
||||
*/
|
||||
public void setDebugging(PrintWriter w) {
|
||||
debugOutput = w;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets <b>s</b> as the PrintStream for debugging output,
|
||||
* which writes out all messages passed between Tor and the controller.
|
||||
* Outgoing messages are preceded by "\>\>" and incoming messages are preceded
|
||||
* by "\<\<"
|
||||
*/
|
||||
public void setDebugging(PrintStream s) {
|
||||
debugOutput = new PrintWriter(s, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the EventHandler object that will be notified of any
|
||||
* events Tor delivers to this connection. To make Tor send us
|
||||
* events, call setEvents().
|
||||
*/
|
||||
public void setEventHandler(EventHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a thread to react to Tor's responses in the background.
|
||||
* This is necessary to handle asynchronous events and synchronous
|
||||
* responses that arrive independantly over the same socket.
|
||||
*/
|
||||
public Thread launchThread(boolean daemon) {
|
||||
LOG.info("Entering synchronized (connection)");
|
||||
synchronized (this) {
|
||||
LOG.info("Entered synchronized (connection)");
|
||||
ControlParseThread th = new ControlParseThread();
|
||||
LOG.info("Launching parse thread " + th.hashCode());
|
||||
if (daemon)
|
||||
th.setDaemon(true);
|
||||
th.start();
|
||||
this.thread = th;
|
||||
LOG.info("Leaving synchronized (connection)");
|
||||
return th;
|
||||
}
|
||||
}
|
||||
|
||||
protected class ControlParseThread extends Thread {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
react();
|
||||
} catch (IOException ex) {
|
||||
LOG.info("Parse thread " + hashCode()
|
||||
+ " caught exception " + ex);
|
||||
parseThreadException = ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkThread() {
|
||||
LOG.info("Entering synchronized (connection)");
|
||||
synchronized (this) {
|
||||
LOG.info("Entered synchronized (connection)");
|
||||
if (thread == null)
|
||||
launchThread(true);
|
||||
LOG.info("Leaving synchronized (connection)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* helper: implement the main background loop.
|
||||
*/
|
||||
protected void react() throws IOException {
|
||||
while (true) {
|
||||
ArrayList<ReplyLine> lst = readReply();
|
||||
LOG.info("Read reply: " + lst);
|
||||
if (lst.isEmpty()) {
|
||||
// interrupted queued waiters, there won't be any response.
|
||||
LOG.info("Entering synchronized (waiters)");
|
||||
synchronized (waiters) {
|
||||
LOG.info("Entered synchronized (waiters)");
|
||||
if (!waiters.isEmpty()) {
|
||||
for (Waiter w : waiters) {
|
||||
LOG.info("Interrupting waiter " + w.hashCode());
|
||||
w.interrupt();
|
||||
}
|
||||
} else {
|
||||
LOG.info("No waiters");
|
||||
}
|
||||
LOG.info("Leaving synchronized (waiters)");
|
||||
}
|
||||
throw new IOException("Tor is no longer running");
|
||||
}
|
||||
if ((lst.get(0)).status.startsWith("6")) {
|
||||
LOG.info("Reply is an event");
|
||||
handleEvent(lst);
|
||||
} else {
|
||||
LOG.info("Entering synchronized (waiters)");
|
||||
synchronized (waiters) {
|
||||
LOG.info("Entered synchronized (waiters)");
|
||||
if (!waiters.isEmpty()) {
|
||||
Waiter w;
|
||||
w = waiters.removeFirst();
|
||||
LOG.info("Setting response for waiter " + w.hashCode());
|
||||
w.setResponse(lst);
|
||||
} else {
|
||||
LOG.info("No waiters");
|
||||
}
|
||||
LOG.info("Leaving synchronized (waiters)");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the value of the configuration option 'key' to 'val'.
|
||||
*/
|
||||
public void setConf(String key, String value) throws IOException {
|
||||
List<String> lst = new ArrayList<>();
|
||||
lst.add(key + " " + value);
|
||||
setConf(lst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the values of the configuration options stored in kvMap.
|
||||
*/
|
||||
public void setConf(Map<String, String> kvMap) throws IOException {
|
||||
List<String> lst = new ArrayList<>();
|
||||
for (Iterator<Map.Entry<String, String>> it =
|
||||
kvMap.entrySet().iterator(); it.hasNext(); ) {
|
||||
Map.Entry<String, String> ent = it.next();
|
||||
lst.add(ent.getKey() + " " + ent.getValue() + "\n");
|
||||
}
|
||||
setConf(lst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the values of the configuration options stored in
|
||||
* <b>kvList</b>. Each list element in <b>kvList</b> is expected to be
|
||||
* String of the format "key value".
|
||||
* <p>
|
||||
* Tor behaves as though it had just read each of the key-value pairs
|
||||
* from its configuration file. Keywords with no corresponding values have
|
||||
* their configuration values reset to their defaults. setConf is
|
||||
* all-or-nothing: if there is an error in any of the configuration settings,
|
||||
* Tor sets none of them.
|
||||
* <p>
|
||||
* When a configuration option takes multiple values, or when multiple
|
||||
* configuration keys form a context-sensitive group (see getConf below), then
|
||||
* setting any of the options in a setConf command is taken to reset all of
|
||||
* the others. For example, if two ORBindAddress values are configured, and a
|
||||
* command arrives containing a single ORBindAddress value, the new
|
||||
* command's value replaces the two old values.
|
||||
* <p>
|
||||
* To remove all settings for a given option entirely (and go back to its
|
||||
* default value), include a String in <b>kvList</b> containing the key and no value.
|
||||
*/
|
||||
public void setConf(Collection<String> kvList) throws IOException {
|
||||
if (kvList.size() == 0)
|
||||
return;
|
||||
StringBuffer b = new StringBuffer("SETCONF");
|
||||
for (Iterator<String> it = kvList.iterator(); it.hasNext(); ) {
|
||||
String kv = it.next();
|
||||
int i = kv.indexOf(' ');
|
||||
if (i == -1)
|
||||
b.append(" ").append(kv);
|
||||
b.append(" ").append(kv.substring(0, i)).append("=")
|
||||
.append(quote(kv.substring(i + 1)));
|
||||
}
|
||||
b.append("\r\n");
|
||||
sendAndWaitForResponse(b.toString(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to reset the values listed in the collection 'keys' to their
|
||||
* default values.
|
||||
**/
|
||||
public void resetConf(Collection<String> keys) throws IOException {
|
||||
if (keys.size() == 0)
|
||||
return;
|
||||
StringBuffer b = new StringBuffer("RESETCONF");
|
||||
for (Iterator<String> it = keys.iterator(); it.hasNext(); ) {
|
||||
String key = it.next();
|
||||
b.append(" ").append(key);
|
||||
}
|
||||
b.append("\r\n");
|
||||
sendAndWaitForResponse(b.toString(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value of the configuration option 'key'
|
||||
*/
|
||||
public List<ConfigEntry> getConf(String key) throws IOException {
|
||||
List<String> lst = new ArrayList<>();
|
||||
lst.add(key);
|
||||
return getConf(lst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the values of the configuration variables listed in <b>keys</b>.
|
||||
* Results are returned as a list of ConfigEntry objects.
|
||||
* <p>
|
||||
* If an option appears multiple times in the configuration, all of its
|
||||
* key-value pairs are returned in order.
|
||||
* <p>
|
||||
* Some options are context-sensitive, and depend on other options with
|
||||
* different keywords. These cannot be fetched directly. Currently there
|
||||
* is only one such option: clients should use the "HiddenServiceOptions"
|
||||
* virtual keyword to get all HiddenServiceDir, HiddenServicePort,
|
||||
* HiddenServiceNodes, and HiddenServiceExcludeNodes option settings.
|
||||
*/
|
||||
public List<ConfigEntry> getConf(Collection<String> keys)
|
||||
throws IOException {
|
||||
StringBuffer sb = new StringBuffer("GETCONF");
|
||||
for (Iterator<String> it = keys.iterator(); it.hasNext(); ) {
|
||||
String key = it.next();
|
||||
sb.append(" ").append(key);
|
||||
}
|
||||
sb.append("\r\n");
|
||||
List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null);
|
||||
List<ConfigEntry> result = new ArrayList<>();
|
||||
for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) {
|
||||
String kv = (it.next()).msg;
|
||||
int idx = kv.indexOf('=');
|
||||
if (idx >= 0)
|
||||
result.add(new ConfigEntry(kv.substring(0, idx),
|
||||
kv.substring(idx + 1)));
|
||||
else
|
||||
result.add(new ConfigEntry(kv));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request that the server inform the client about interesting events.
|
||||
* Each element of <b>events</b> is one of the following Strings:
|
||||
* ["CIRC" | "STREAM" | "ORCONN" | "BW" | "DEBUG" |
|
||||
* "INFO" | "NOTICE" | "WARN" | "ERR" | "NEWDESC" | "ADDRMAP"] .
|
||||
* <p>
|
||||
* Any events not listed in the <b>events</b> are turned off; thus, calling
|
||||
* setEvents with an empty <b>events</b> argument turns off all event reporting.
|
||||
*/
|
||||
public void setEvents(List<String> events) throws IOException {
|
||||
StringBuffer sb = new StringBuffer("SETEVENTS");
|
||||
for (Iterator<String> it = events.iterator(); it.hasNext(); ) {
|
||||
sb.append(" ").append(it.next());
|
||||
}
|
||||
sb.append("\r\n");
|
||||
sendAndWaitForResponse(sb.toString(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates the controller to the Tor server.
|
||||
* <p>
|
||||
* By default, the current Tor implementation trusts all local users, and
|
||||
* the controller can authenticate itself by calling authenticate(new byte[0]).
|
||||
* <p>
|
||||
* If the 'CookieAuthentication' option is true, Tor writes a "magic cookie"
|
||||
* file named "control_auth_cookie" into its data directory. To authenticate,
|
||||
* the controller must send the contents of this file in <b>auth</b>.
|
||||
* <p>
|
||||
* If the 'HashedControlPassword' option is set, <b>auth</b> must contain the salted
|
||||
* hash of a secret password. The salted hash is computed according to the
|
||||
* S2K algorithm in RFC 2440 (OpenPGP), and prefixed with the s2k specifier.
|
||||
* This is then encoded in hexadecimal, prefixed by the indicator sequence
|
||||
* "16:".
|
||||
* <p>
|
||||
* You can generate the salt of a password by calling
|
||||
* 'tor --hash-password <password>'
|
||||
* or by using the provided PasswordDigest class.
|
||||
* To authenticate under this scheme, the controller sends Tor the original
|
||||
* secret that was used to generate the password.
|
||||
*/
|
||||
public void authenticate(byte[] auth) throws IOException {
|
||||
String cmd = "AUTHENTICATE " + Bytes.hex(auth) + "\r\n";
|
||||
sendAndWaitForResponse(cmd, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the server to write out its configuration options into its torrc.
|
||||
*/
|
||||
public void saveConf() throws IOException {
|
||||
sendAndWaitForResponse("SAVECONF\r\n", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a signal from the controller to the Tor server.
|
||||
* <b>signal</b> is one of the following Strings:
|
||||
* <ul>
|
||||
* <li>"RELOAD" or "HUP" : Reload config items, refetch directory</li>
|
||||
* <li>"SHUTDOWN" or "INT" : Controlled shutdown: if server is an OP, exit immediately.
|
||||
* If it's an OR, close listeners and exit after 30 seconds</li>
|
||||
* <li>"DUMP" or "USR1" : Dump stats: log information about open connections and circuits</li>
|
||||
* <li>"DEBUG" or "USR2" : Debug: switch all open logs to loglevel debug</li>
|
||||
* <li>"HALT" or "TERM" : Immediate shutdown: clean up and exit now</li>
|
||||
* </ul>
|
||||
*/
|
||||
public void signal(String signal) throws IOException {
|
||||
String cmd = "SIGNAL " + signal + "\r\n";
|
||||
sendAndWaitForResponse(cmd, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a signal to the Tor process to shut it down or halt it.
|
||||
* Does not wait for a response.
|
||||
*/
|
||||
public void shutdownTor(String signal) throws IOException {
|
||||
String s = "SIGNAL " + signal + "\r\n";
|
||||
Waiter w = new Waiter();
|
||||
if (debugOutput != null)
|
||||
debugOutput.print(">> " + s);
|
||||
LOG.info("Entering synchronized (waiters)");
|
||||
synchronized (waiters) {
|
||||
LOG.info("Entered synchronized (waiters)");
|
||||
output.write(s);
|
||||
output.flush();
|
||||
LOG.info("Leaving synchronized (waiters)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the Tor server that future SOCKS requests for connections to a set of original
|
||||
* addresses should be replaced with connections to the specified replacement
|
||||
* addresses. Each element of <b>kvLines</b> is a String of the form
|
||||
* "old-address new-address". This function returns the new address mapping.
|
||||
* <p>
|
||||
* The client may decline to provide a body for the original address, and
|
||||
* instead send a special null address ("0.0.0.0" for IPv4, "::0" for IPv6, or
|
||||
* "." for hostname), signifying that the server should choose the original
|
||||
* address itself, and return that address in the reply. The server
|
||||
* should ensure that it returns an element of address space that is unlikely
|
||||
* to be in actual use. If there is already an address mapped to the
|
||||
* destination address, the server may reuse that mapping.
|
||||
* <p>
|
||||
* If the original address is already mapped to a different address, the old
|
||||
* mapping is removed. If the original address and the destination address
|
||||
* are the same, the server removes any mapping in place for the original
|
||||
* address.
|
||||
* <p>
|
||||
* Mappings set by the controller last until the Tor process exits:
|
||||
* they never expire. If the controller wants the mapping to last only
|
||||
* a certain time, then it must explicitly un-map the address when that
|
||||
* time has elapsed.
|
||||
*/
|
||||
public Map<String, String> mapAddresses(Collection<String> kvLines)
|
||||
throws IOException {
|
||||
StringBuffer sb = new StringBuffer("MAPADDRESS");
|
||||
for (Iterator<String> it = kvLines.iterator(); it.hasNext(); ) {
|
||||
String kv = it.next();
|
||||
int i = kv.indexOf(' ');
|
||||
sb.append(" ").append(kv.substring(0, i)).append("=")
|
||||
.append(quote(kv.substring(i + 1)));
|
||||
}
|
||||
sb.append("\r\n");
|
||||
List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null);
|
||||
Map<String, String> result = new HashMap<>();
|
||||
for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) {
|
||||
String kv = (it.next()).msg;
|
||||
int idx = kv.indexOf('=');
|
||||
result.put(kv.substring(0, idx),
|
||||
kv.substring(idx + 1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, String> mapAddresses(Map<String, String> addresses)
|
||||
throws IOException {
|
||||
List<String> kvList = new ArrayList<>();
|
||||
for (Iterator<Map.Entry<String, String>> it =
|
||||
addresses.entrySet().iterator(); it.hasNext(); ) {
|
||||
Map.Entry<String, String> e = it.next();
|
||||
kvList.add(e.getKey() + " " + e.getValue());
|
||||
}
|
||||
return mapAddresses(kvList);
|
||||
}
|
||||
|
||||
public String mapAddress(String fromAddr, String toAddr)
|
||||
throws IOException {
|
||||
List<String> lst = new ArrayList<>();
|
||||
lst.add(fromAddr + " " + toAddr + "\n");
|
||||
Map<String, String> m = mapAddresses(lst);
|
||||
return m.get(fromAddr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the Tor server for keyed values that are not stored in the torrc
|
||||
* configuration file. Returns a map of keys to values.
|
||||
* <p>
|
||||
* Recognized keys include:
|
||||
* <ul>
|
||||
* <li>"version" : The version of the server's software, including the name
|
||||
* of the software. (example: "Tor 0.0.9.4")</li>
|
||||
* <li>"desc/id/<OR identity>" or "desc/name/<OR nickname>" : the latest server
|
||||
* descriptor for a given OR, NUL-terminated. If no such OR is known, the
|
||||
* corresponding value is an empty string.</li>
|
||||
* <li>"network-status" : a space-separated list of all known OR identities.
|
||||
* This is in the same format as the router-status line in directories;
|
||||
* see tor-spec.txt for details.</li>
|
||||
* <li>"addr-mappings/all"</li>
|
||||
* <li>"addr-mappings/config"</li>
|
||||
* <li>"addr-mappings/cache"</li>
|
||||
* <li>"addr-mappings/control" : a space-separated list of address mappings, each
|
||||
* in the form of "from-address=to-address". The 'config' key
|
||||
* returns those address mappings set in the configuration; the 'cache'
|
||||
* key returns the mappings in the client-side DNS cache; the 'control'
|
||||
* key returns the mappings set via the control interface; the 'all'
|
||||
* target returns the mappings set through any mechanism.</li>
|
||||
* <li>"circuit-status" : A series of lines as for a circuit status event. Each line is of the form:
|
||||
* "CircuitID CircStatus Path"</li>
|
||||
* <li>"stream-status" : A series of lines as for a stream status event. Each is of the form:
|
||||
* "StreamID StreamStatus CircID Target"</li>
|
||||
* <li>"orconn-status" : A series of lines as for an OR connection status event. Each is of the
|
||||
* form: "ServerID ORStatus"</li>
|
||||
* </ul>
|
||||
*/
|
||||
public Map<String, String> getInfo(Collection<String> keys)
|
||||
throws IOException {
|
||||
StringBuffer sb = new StringBuffer("GETINFO");
|
||||
for (Iterator<String> it = keys.iterator(); it.hasNext(); ) {
|
||||
sb.append(" ").append(it.next());
|
||||
}
|
||||
sb.append("\r\n");
|
||||
List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null);
|
||||
Map<String, String> m = new HashMap<>();
|
||||
for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) {
|
||||
ReplyLine line = it.next();
|
||||
int idx = line.msg.indexOf('=');
|
||||
if (idx < 0)
|
||||
break;
|
||||
String k = line.msg.substring(0, idx);
|
||||
String v;
|
||||
if (line.rest != null) {
|
||||
v = line.rest;
|
||||
} else {
|
||||
v = line.msg.substring(idx + 1);
|
||||
}
|
||||
m.put(k, v);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the value of the information field 'key'
|
||||
*/
|
||||
public String getInfo(String key) throws IOException {
|
||||
List<String> lst = new ArrayList<>();
|
||||
lst.add(key);
|
||||
Map<String, String> m = getInfo(lst);
|
||||
return m.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* An extendCircuit request takes one of two forms: either the <b>circID</b> is zero, in
|
||||
* which case it is a request for the server to build a new circuit according
|
||||
* to the specified path, or the <b>circID</b> is nonzero, in which case it is a
|
||||
* request for the server to extend an existing circuit with that ID according
|
||||
* to the specified <b>path</b>.
|
||||
* <p>
|
||||
* If successful, returns the Circuit ID of the (maybe newly created) circuit.
|
||||
*/
|
||||
public String extendCircuit(String circID, String path) throws IOException {
|
||||
List<ReplyLine> lst = sendAndWaitForResponse(
|
||||
"EXTENDCIRCUIT " + circID + " " + path + "\r\n", null);
|
||||
return (lst.get(0)).msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs the Tor server that the stream specified by <b>streamID</b> should be
|
||||
* associated with the circuit specified by <b>circID</b>.
|
||||
* <p>
|
||||
* Each stream may be associated with
|
||||
* at most one circuit, and multiple streams may share the same circuit.
|
||||
* Streams can only be attached to completed circuits (that is, circuits that
|
||||
* have sent a circuit status "BUILT" event or are listed as built in a
|
||||
* getInfo circuit-status request).
|
||||
* <p>
|
||||
* If <b>circID</b> is 0, responsibility for attaching the given stream is
|
||||
* returned to Tor.
|
||||
* <p>
|
||||
* By default, Tor automatically attaches streams to
|
||||
* circuits itself, unless the configuration variable
|
||||
* "__LeaveStreamsUnattached" is set to "1". Attempting to attach streams
|
||||
* via TC when "__LeaveStreamsUnattached" is false may cause a race between
|
||||
* Tor and the controller, as both attempt to attach streams to circuits.
|
||||
*/
|
||||
public void attachStream(String streamID, String circID)
|
||||
throws IOException {
|
||||
sendAndWaitForResponse(
|
||||
"ATTACHSTREAM " + streamID + " " + circID + "\r\n", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor about the server descriptor in <b>desc</b>.
|
||||
* <p>
|
||||
* The descriptor, when parsed, must contain a number of well-specified
|
||||
* fields, including fields for its nickname and identity.
|
||||
*/
|
||||
// More documentation here on format of desc?
|
||||
// No need for return value? control-spec.txt says reply is merely "250 OK" on success...
|
||||
public String postDescriptor(String desc) throws IOException {
|
||||
List<ReplyLine> lst =
|
||||
sendAndWaitForResponse("+POSTDESCRIPTOR\r\n", desc);
|
||||
return (lst.get(0)).msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor to change the exit address of the stream identified by <b>streamID</b>
|
||||
* to <b>address</b>. No remapping is performed on the new provided address.
|
||||
* <p>
|
||||
* To be sure that the modified address will be used, this event must be sent
|
||||
* after a new stream event is received, and before attaching this stream to
|
||||
* a circuit.
|
||||
*/
|
||||
public void redirectStream(String streamID, String address)
|
||||
throws IOException {
|
||||
sendAndWaitForResponse(
|
||||
"REDIRECTSTREAM " + streamID + " " + address + "\r\n",
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor to close the stream identified by <b>streamID</b>.
|
||||
* <b>reason</b> should be one of the Tor RELAY_END reasons given in tor-spec.txt, as a decimal:
|
||||
* <ul>
|
||||
* <li>1 -- REASON_MISC (catch-all for unlisted reasons)</li>
|
||||
* <li>2 -- REASON_RESOLVEFAILED (couldn't look up hostname)</li>
|
||||
* <li>3 -- REASON_CONNECTREFUSED (remote host refused connection)</li>
|
||||
* <li>4 -- REASON_EXITPOLICY (OR refuses to connect to host or port)</li>
|
||||
* <li>5 -- REASON_DESTROY (Circuit is being destroyed)</li>
|
||||
* <li>6 -- REASON_DONE (Anonymized TCP connection was closed)</li>
|
||||
* <li>7 -- REASON_TIMEOUT (Connection timed out, or OR timed out while connecting)</li>
|
||||
* <li>8 -- (unallocated)</li>
|
||||
* <li>9 -- REASON_HIBERNATING (OR is temporarily hibernating)</li>
|
||||
* <li>10 -- REASON_INTERNAL (Internal error at the OR)</li>
|
||||
* <li>11 -- REASON_RESOURCELIMIT (OR has no resources to fulfill request)</li>
|
||||
* <li>12 -- REASON_CONNRESET (Connection was unexpectedly reset)</li>
|
||||
* <li>13 -- REASON_TORPROTOCOL (Sent when closing connection because of Tor protocol violations)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Tor may hold the stream open for a while to flush any data that is pending.
|
||||
*/
|
||||
public void closeStream(String streamID, byte reason)
|
||||
throws IOException {
|
||||
sendAndWaitForResponse(
|
||||
"CLOSESTREAM " + streamID + " " + reason + "\r\n", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor to close the circuit identified by <b>circID</b>.
|
||||
* If <b>ifUnused</b> is true, do not close the circuit unless it is unused.
|
||||
*/
|
||||
public void closeCircuit(String circID, boolean ifUnused)
|
||||
throws IOException {
|
||||
sendAndWaitForResponse("CLOSECIRCUIT " + circID +
|
||||
(ifUnused ? " IFUNUSED" : "") + "\r\n", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor to exit when this control connection is closed. This command
|
||||
* was added in Tor 0.2.2.28-beta.
|
||||
*/
|
||||
public void takeOwnership() throws IOException {
|
||||
sendAndWaitForResponse("TAKEOWNERSHIP\r\n", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor to generate and set up a new onion service using the best
|
||||
* supported algorithm.
|
||||
* <p/>
|
||||
* ADD_ONION was added in Tor 0.2.7.1-alpha.
|
||||
*/
|
||||
public Map<String, String> addOnion(Map<Integer, String> portLines)
|
||||
throws IOException {
|
||||
return addOnion("NEW:BEST", portLines, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor to generate and set up a new onion service using the best
|
||||
* supported algorithm.
|
||||
* <p/>
|
||||
* ADD_ONION was added in Tor 0.2.7.1-alpha.
|
||||
*/
|
||||
public Map<String, String> addOnion(Map<Integer, String> portLines,
|
||||
boolean ephemeral, boolean detach)
|
||||
throws IOException {
|
||||
return addOnion("NEW:BEST", portLines, ephemeral, detach);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor to set up an onion service using the provided private key.
|
||||
* <p/>
|
||||
* ADD_ONION was added in Tor 0.2.7.1-alpha.
|
||||
*/
|
||||
public Map<String, String> addOnion(String privKey,
|
||||
Map<Integer, String> portLines)
|
||||
throws IOException {
|
||||
return addOnion(privKey, portLines, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor to set up an onion service using the provided private key.
|
||||
* <p/>
|
||||
* ADD_ONION was added in Tor 0.2.7.1-alpha.
|
||||
*/
|
||||
public Map<String, String> addOnion(String privKey,
|
||||
Map<Integer, String> portLines,
|
||||
boolean ephemeral, boolean detach)
|
||||
throws IOException {
|
||||
List<String> flags = new ArrayList<>();
|
||||
if (ephemeral)
|
||||
flags.add("DiscardPK");
|
||||
if (detach)
|
||||
flags.add("Detach");
|
||||
return addOnion(privKey, portLines, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor to set up an onion service.
|
||||
* <p/>
|
||||
* ADD_ONION was added in Tor 0.2.7.1-alpha.
|
||||
*/
|
||||
public Map<String, String> addOnion(String privKey,
|
||||
Map<Integer, String> portLines,
|
||||
List<String> flags)
|
||||
throws IOException {
|
||||
if (privKey.indexOf(':') < 0)
|
||||
throw new IllegalArgumentException("Invalid privKey");
|
||||
if (portLines == null || portLines.size() < 1)
|
||||
throw new IllegalArgumentException(
|
||||
"Must provide at least one port line");
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("ADD_ONION ").append(privKey);
|
||||
if (flags != null && flags.size() > 0) {
|
||||
b.append(" Flags=");
|
||||
String separator = "";
|
||||
for (String flag : flags) {
|
||||
b.append(separator).append(flag);
|
||||
separator = ",";
|
||||
}
|
||||
}
|
||||
for (Map.Entry<Integer, String> portLine : portLines.entrySet()) {
|
||||
int virtPort = portLine.getKey();
|
||||
String target = portLine.getValue();
|
||||
b.append(" Port=").append(virtPort);
|
||||
if (target != null && target.length() > 0)
|
||||
b.append(",").append(target);
|
||||
}
|
||||
b.append("\r\n");
|
||||
List<ReplyLine> lst = sendAndWaitForResponse(b.toString(), null);
|
||||
Map<String, String> ret = new HashMap<>();
|
||||
ret.put(HS_ADDRESS, (lst.get(0)).msg.split("=", 2)[1]);
|
||||
if (lst.size() > 2)
|
||||
ret.put(HS_PRIVKEY, (lst.get(1)).msg.split("=", 2)[1]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor to take down an onion service previously set up with
|
||||
* addOnion(). The hostname excludes the .onion extension.
|
||||
* <p/>
|
||||
* DEL_ONION was added in Tor 0.2.7.1-alpha.
|
||||
*/
|
||||
public void delOnion(String hostname) throws IOException {
|
||||
sendAndWaitForResponse("DEL_ONION " + hostname + "\r\n", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor to forget any cached client state relating to the hidden
|
||||
* service with the given hostname (excluding the .onion extension).
|
||||
*/
|
||||
public void forgetHiddenService(String hostname) throws IOException {
|
||||
sendAndWaitForResponse("HSFORGET " + hostname + "\r\n", null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An exception raised when Tor tells us about an error.
|
||||
*/
|
||||
public class TorControlError extends IOException {
|
||||
|
||||
static final long serialVersionUID = 3;
|
||||
|
||||
private final int errorType;
|
||||
|
||||
public TorControlError(int type, String s) {
|
||||
super(s);
|
||||
errorType = type;
|
||||
}
|
||||
|
||||
public TorControlError(String s) {
|
||||
this(-1, s);
|
||||
}
|
||||
|
||||
public int getErrorType() {
|
||||
return errorType;
|
||||
}
|
||||
|
||||
public String getErrorMsg() {
|
||||
try {
|
||||
if (errorType == -1)
|
||||
return null;
|
||||
return TorControlCommands.ERROR_MSGS[errorType];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
return "Unrecongized error #"+errorType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An exception raised when Tor behaves in an unexpected way.
|
||||
*/
|
||||
public class TorControlSyntaxError extends IOException {
|
||||
|
||||
static final long serialVersionUID = 3;
|
||||
|
||||
public TorControlSyntaxError(String s) { super(s); }
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
*.class
|
||||
@@ -1,44 +0,0 @@
|
||||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control.examples;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Iterator;
|
||||
import net.freehaven.tor.control.EventHandler;
|
||||
|
||||
public class DebuggingEventHandler implements EventHandler {
|
||||
|
||||
private final PrintWriter out;
|
||||
|
||||
public DebuggingEventHandler(PrintWriter p) {
|
||||
out = p;
|
||||
}
|
||||
|
||||
public void circuitStatus(String status, String circID, String path) {
|
||||
out.println("Circuit "+circID+" is now "+status+" (path="+path+")");
|
||||
}
|
||||
public void streamStatus(String status, String streamID, String target) {
|
||||
out.println("Stream "+streamID+" is now "+status+" (target="+target+")");
|
||||
}
|
||||
public void orConnStatus(String status, String orName) {
|
||||
out.println("OR connection to "+orName+" is now "+status);
|
||||
}
|
||||
public void bandwidthUsed(long read, long written) {
|
||||
out.println("Bandwidth usage: "+read+" bytes read; "+
|
||||
written+" bytes written.");
|
||||
}
|
||||
public void newDescriptors(java.util.List<String> orList) {
|
||||
out.println("New descriptors for routers:");
|
||||
for (Iterator<String> i = orList.iterator(); i.hasNext(); )
|
||||
out.println(" "+i.next());
|
||||
}
|
||||
public void message(String type, String msg) {
|
||||
out.println("["+type+"] "+msg.trim());
|
||||
}
|
||||
|
||||
public void unrecognized(String type, String msg) {
|
||||
out.println("unrecognized event ["+type+"] "+msg.trim());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control.examples;
|
||||
|
||||
import net.freehaven.tor.control.*;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class Main implements TorControlCommands {
|
||||
|
||||
public static void main(String args[]) {
|
||||
if (args.length < 1) {
|
||||
System.err.println("No command given.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (args[0].equals("set-config")) {
|
||||
setConfig(args);
|
||||
} else if (args[0].equals("get-config")) {
|
||||
getConfig(args);
|
||||
} else if (args[0].equals("get-info")) {
|
||||
getInfo(args);
|
||||
} else if (args[0].equals("listen")) {
|
||||
listenForEvents(args);
|
||||
} else if (args[0].equals("signal")) {
|
||||
signal(args);
|
||||
} else if (args[0].equals("auth")) {
|
||||
authDemo(args);
|
||||
} else {
|
||||
System.err.println("Unrecognized command: "+args[0]);
|
||||
}
|
||||
} catch (EOFException ex) {
|
||||
System.out.println("Control socket closed by Tor.");
|
||||
} catch (TorControlError ex) {
|
||||
System.err.println("Error from Tor process: "+
|
||||
ex+" ["+ex.getErrorMsg()+"]");
|
||||
} catch (IOException ex) {
|
||||
System.err.println("IO exception when talking to Tor process: "+
|
||||
ex);
|
||||
ex.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
private static TorControlConnection getConnection(String[] args,
|
||||
boolean daemon) throws IOException {
|
||||
Socket s = new Socket("127.0.0.1", 9100);
|
||||
TorControlConnection conn = new TorControlConnection(s);
|
||||
conn.launchThread(daemon);
|
||||
conn.authenticate(new byte[0]);
|
||||
return conn;
|
||||
}
|
||||
|
||||
private static TorControlConnection getConnection(String[] args)
|
||||
throws IOException {
|
||||
return getConnection(args, true);
|
||||
}
|
||||
|
||||
public static void setConfig(String[] args) throws IOException {
|
||||
// Usage: "set-config [-save] key value key value key value"
|
||||
TorControlConnection conn = getConnection(args);
|
||||
ArrayList<String> lst = new ArrayList<String>();
|
||||
int i = 1;
|
||||
boolean save = false;
|
||||
if (args[i].equals("-save")) {
|
||||
save = true;
|
||||
++i;
|
||||
}
|
||||
for (; i < args.length; i +=2) {
|
||||
lst.add(args[i]+" "+args[i+1]);
|
||||
}
|
||||
conn.setConf(lst);
|
||||
if (save) {
|
||||
conn.saveConf();
|
||||
}
|
||||
}
|
||||
|
||||
public static void getConfig(String[] args) throws IOException {
|
||||
// Usage: get-config key key key
|
||||
TorControlConnection conn = getConnection(args);
|
||||
List<ConfigEntry> lst = conn.getConf(Arrays.asList(args).subList(1,args.length));
|
||||
for (Iterator<ConfigEntry> i = lst.iterator(); i.hasNext(); ) {
|
||||
ConfigEntry e = i.next();
|
||||
System.out.println("KEY: "+e.key);
|
||||
System.out.println("VAL: "+e.value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void getInfo(String[] args) throws IOException {
|
||||
TorControlConnection conn = getConnection(args);
|
||||
Map<String,String> m = conn.getInfo(Arrays.asList(args).subList(1,args.length));
|
||||
for (Iterator<Map.Entry<String, String>> i = m.entrySet().iterator(); i.hasNext(); ) {
|
||||
Map.Entry<String,String> e = i.next();
|
||||
System.out.println("KEY: "+e.getKey());
|
||||
System.out.println("VAL: "+e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public static void listenForEvents(String[] args) throws IOException {
|
||||
// Usage: listen [circ|stream|orconn|bw|newdesc|info|notice|warn|error]*
|
||||
TorControlConnection conn = getConnection(args, false);
|
||||
ArrayList<String> lst = new ArrayList<String>();
|
||||
for (int i = 1; i < args.length; ++i) {
|
||||
lst.add(args[i]);
|
||||
}
|
||||
conn.setEventHandler(
|
||||
new DebuggingEventHandler(new PrintWriter(System.out, true)));
|
||||
conn.setEvents(lst);
|
||||
}
|
||||
|
||||
public static void signal(String[] args) throws IOException {
|
||||
// Usage signal [reload|shutdown|dump|debug|halt]
|
||||
TorControlConnection conn = getConnection(args, false);
|
||||
// distinguish shutdown signal from other signals
|
||||
if ("SHUTDOWN".equalsIgnoreCase(args[1])
|
||||
|| "HALT".equalsIgnoreCase(args[1])) {
|
||||
conn.shutdownTor(args[1].toUpperCase());
|
||||
} else {
|
||||
conn.signal(args[1].toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
public static void authDemo(String[] args) throws IOException {
|
||||
|
||||
PasswordDigest pwd = PasswordDigest.generateDigest();
|
||||
Socket s = new Socket("127.0.0.1", 9100);
|
||||
TorControlConnection conn = new TorControlConnection(s);
|
||||
conn.launchThread(true);
|
||||
conn.authenticate(new byte[0]);
|
||||
|
||||
conn.setConf("HashedControlPassword", pwd.getHashedPassword());
|
||||
|
||||
s = new Socket("127.0.0.1", 9100);
|
||||
conn = new TorControlConnection(s);
|
||||
conn.launchThread(true);
|
||||
conn.authenticate(pwd.getSecret());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,8 +39,7 @@ class ContactGroupFactoryImpl implements ContactGroupFactory {
|
||||
|
||||
@Override
|
||||
public Group createContactGroup(ClientId clientId, int majorVersion,
|
||||
Contact contact) {
|
||||
AuthorId local = contact.getLocalAuthorId();
|
||||
Contact contact, AuthorId local) {
|
||||
AuthorId remote = contact.getAuthor().getId();
|
||||
byte[] descriptor = createGroupDescriptor(local, remote);
|
||||
return groupFactory.createGroup(clientId, majorVersion, descriptor);
|
||||
|
||||
@@ -77,7 +77,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
private volatile LocalAuthor localAuthor;
|
||||
private volatile DuplexTransportConnection conn;
|
||||
private volatile TransportId transportId;
|
||||
private volatile SecretKey masterSecret;
|
||||
private volatile SecretKey masterKey;
|
||||
private volatile boolean alice;
|
||||
|
||||
@Inject
|
||||
@@ -104,13 +104,13 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startExchange(LocalAuthor localAuthor, SecretKey masterSecret,
|
||||
public void startExchange(LocalAuthor localAuthor, SecretKey masterKey,
|
||||
DuplexTransportConnection conn, TransportId transportId,
|
||||
boolean alice) {
|
||||
this.localAuthor = localAuthor;
|
||||
this.conn = conn;
|
||||
this.transportId = transportId;
|
||||
this.masterSecret = masterSecret;
|
||||
this.masterKey = masterKey;
|
||||
this.alice = alice;
|
||||
start();
|
||||
}
|
||||
@@ -142,9 +142,9 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
}
|
||||
|
||||
// Derive the header keys for the transport streams
|
||||
SecretKey aliceHeaderKey = crypto.deriveKey(ALICE_KEY_LABEL,
|
||||
masterSecret, new byte[] {PROTOCOL_VERSION});
|
||||
SecretKey bobHeaderKey = crypto.deriveKey(BOB_KEY_LABEL, masterSecret,
|
||||
SecretKey aliceHeaderKey = crypto.deriveKey(ALICE_KEY_LABEL, masterKey,
|
||||
new byte[] {PROTOCOL_VERSION});
|
||||
SecretKey bobHeaderKey = crypto.deriveKey(BOB_KEY_LABEL, masterKey,
|
||||
new byte[] {PROTOCOL_VERSION});
|
||||
|
||||
// Create the readers
|
||||
@@ -163,9 +163,9 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
.createRecordWriter(streamWriter.getOutputStream());
|
||||
|
||||
// Derive the nonces to be signed
|
||||
byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterSecret,
|
||||
byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterKey,
|
||||
new byte[] {PROTOCOL_VERSION});
|
||||
byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterSecret,
|
||||
byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterKey,
|
||||
new byte[] {PROTOCOL_VERSION});
|
||||
byte[] localNonce = alice ? aliceNonce : bobNonce;
|
||||
byte[] remoteNonce = alice ? bobNonce : aliceNonce;
|
||||
@@ -293,8 +293,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
throws DbException {
|
||||
return db.transactionWithResult(false, txn -> {
|
||||
ContactId contactId = contactManager.addContact(txn, remoteAuthor,
|
||||
localAuthor.getId(), masterSecret, timestamp, alice,
|
||||
true, true);
|
||||
masterKey, timestamp, alice, true, true);
|
||||
transportPropertyManager.addRemoteProperties(txn, contactId,
|
||||
remoteProperties);
|
||||
return contactId;
|
||||
|
||||
@@ -8,7 +8,6 @@ import org.briarproject.bramble.api.contact.PendingContactId;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
@@ -18,7 +17,6 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.transport.KeyManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
@@ -30,8 +28,9 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static org.briarproject.bramble.api.contact.PendingContact.PendingContactState.WAITING_FOR_CONNECTION;
|
||||
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED;
|
||||
@@ -68,31 +67,31 @@ class ContactManagerImpl implements ContactManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactId addContact(Transaction txn, Author remote, AuthorId local,
|
||||
SecretKey master, long timestamp, boolean alice, boolean verified,
|
||||
boolean active) throws DbException {
|
||||
ContactId c = db.addContact(txn, remote, local, verified, active);
|
||||
keyManager.addContact(txn, c, master, timestamp, alice, active);
|
||||
Contact contact = db.getContact(txn, c);
|
||||
for (ContactHook hook : hooks) hook.addingContact(txn, contact);
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactId addContact(Transaction txn, Author remote, AuthorId local,
|
||||
boolean verified, boolean active) throws DbException {
|
||||
ContactId c = db.addContact(txn, remote, local, verified, active);
|
||||
Contact contact = db.getContact(txn, c);
|
||||
for (ContactHook hook : hooks) hook.addingContact(txn, contact);
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactId addContact(Author remote, AuthorId local, SecretKey master,
|
||||
public ContactId addContact(Transaction txn, Author a, SecretKey rootKey,
|
||||
long timestamp, boolean alice, boolean verified, boolean active)
|
||||
throws DbException {
|
||||
ContactId c = db.addContact(txn, a, verified);
|
||||
keyManager.addContact(txn, c, rootKey, timestamp, alice, active);
|
||||
Contact contact = db.getContact(txn, c);
|
||||
for (ContactHook hook : hooks) hook.addingContact(txn, contact);
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactId addContact(Transaction txn, Author a, boolean verified)
|
||||
throws DbException {
|
||||
ContactId c = db.addContact(txn, a, verified);
|
||||
Contact contact = db.getContact(txn, c);
|
||||
for (ContactHook hook : hooks) hook.addingContact(txn, contact);
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactId addContact(Author a, SecretKey rootKey, long timestamp,
|
||||
boolean alice, boolean verified, boolean active)
|
||||
throws DbException {
|
||||
return db.transactionWithResult(false, txn ->
|
||||
addContact(txn, remote, local, master, timestamp, alice,
|
||||
addContact(txn, a, rootKey, timestamp, alice,
|
||||
verified, active));
|
||||
}
|
||||
|
||||
@@ -123,8 +122,8 @@ class ContactManagerImpl implements ContactManager {
|
||||
public PendingContact addRemoteContactRequest(String link, String alias) {
|
||||
// TODO replace with real implementation
|
||||
PendingContactId id = new PendingContactId(link.getBytes());
|
||||
return new PendingContact(id, alias, WAITING_FOR_CONNECTION,
|
||||
System.currentTimeMillis());
|
||||
return new PendingContact(id, new byte[MAX_PUBLIC_KEY_LENGTH], alias,
|
||||
WAITING_FOR_CONNECTION, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,32 +143,18 @@ class ContactManagerImpl implements ContactManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contact getContact(AuthorId remoteAuthorId, AuthorId localAuthorId)
|
||||
throws DbException {
|
||||
return db.transactionWithResult(true, txn ->
|
||||
getContact(txn, remoteAuthorId, localAuthorId));
|
||||
public Contact getContact(AuthorId a) throws DbException {
|
||||
return db.transactionWithResult(true, txn -> getContact(txn, a));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contact getContact(Transaction txn, AuthorId remoteAuthorId,
|
||||
AuthorId localAuthorId) throws DbException {
|
||||
Collection<Contact> contacts =
|
||||
db.getContactsByAuthorId(txn, remoteAuthorId);
|
||||
for (Contact c : contacts) {
|
||||
if (c.getLocalAuthorId().equals(localAuthorId)) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
throw new NoSuchContactException();
|
||||
public Contact getContact(Transaction txn, AuthorId a) throws DbException {
|
||||
return db.getContact(txn, a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Contact> getActiveContacts() throws DbException {
|
||||
Collection<Contact> contacts =
|
||||
db.transactionWithResult(true, db::getContacts);
|
||||
List<Contact> active = new ArrayList<>(contacts.size());
|
||||
for (Contact c : contacts) if (c.isActive()) active.add(c);
|
||||
return active;
|
||||
public Collection<Contact> getContacts() throws DbException {
|
||||
return db.transactionWithResult(true, db::getContacts);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -177,12 +162,6 @@ class ContactManagerImpl implements ContactManager {
|
||||
db.transaction(false, txn -> removeContact(txn, c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContactActive(Transaction txn, ContactId c, boolean active)
|
||||
throws DbException {
|
||||
db.setContactActive(txn, c, active);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContactAlias(Transaction txn, ContactId c,
|
||||
@Nullable String alias) throws DbException {
|
||||
@@ -201,16 +180,14 @@ class ContactManagerImpl implements ContactManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contactExists(Transaction txn, AuthorId remoteAuthorId,
|
||||
AuthorId localAuthorId) throws DbException {
|
||||
return db.containsContact(txn, remoteAuthorId, localAuthorId);
|
||||
public boolean contactExists(Transaction txn, AuthorId a)
|
||||
throws DbException {
|
||||
return db.containsContact(txn, a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contactExists(AuthorId remoteAuthorId,
|
||||
AuthorId localAuthorId) throws DbException {
|
||||
return db.transactionWithResult(true, txn ->
|
||||
contactExists(txn, remoteAuthorId, localAuthorId));
|
||||
public boolean contactExists(AuthorId a) throws DbException {
|
||||
return db.transactionWithResult(true, txn -> contactExists(txn, a));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -232,12 +209,12 @@ class ContactManagerImpl implements ContactManager {
|
||||
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
|
||||
if (localAuthor.getId().equals(authorId))
|
||||
return new AuthorInfo(OURSELVES);
|
||||
Collection<Contact> contacts = db.getContactsByAuthorId(txn, authorId);
|
||||
if (contacts.isEmpty()) return new AuthorInfo(UNKNOWN);
|
||||
if (contacts.size() > 1) throw new AssertionError();
|
||||
Contact c = contacts.iterator().next();
|
||||
if (c.isVerified()) return new AuthorInfo(VERIFIED, c.getAlias());
|
||||
else return new AuthorInfo(UNVERIFIED, c.getAlias());
|
||||
if (db.containsContact(txn, authorId)) {
|
||||
Contact c = db.getContact(txn, authorId);
|
||||
if (c.isVerified()) return new AuthorInfo(VERIFIED, c.getAlias());
|
||||
else return new AuthorInfo(UNVERIFIED, c.getAlias());
|
||||
}
|
||||
return new AuthorInfo(UNKNOWN);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,18 +4,22 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.transport.HandshakeKeys;
|
||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.spongycastle.crypto.Digest;
|
||||
import org.spongycastle.crypto.digests.Blake2bDigest;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.lang.System.arraycopy;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.ALICE_HANDSHAKE_HEADER_LABEL;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.ALICE_HANDSHAKE_TAG_LABEL;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.ALICE_HEADER_LABEL;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.ALICE_TAG_LABEL;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.BOB_HANDSHAKE_HEADER_LABEL;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.BOB_HANDSHAKE_TAG_LABEL;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.BOB_HEADER_LABEL;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.BOB_TAG_LABEL;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.ROTATE_LABEL;
|
||||
@@ -24,6 +28,9 @@ import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
|
||||
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
|
||||
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||
import static org.briarproject.bramble.util.ByteUtils.writeUint16;
|
||||
import static org.briarproject.bramble.util.ByteUtils.writeUint64;
|
||||
import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
||||
|
||||
class TransportCryptoImpl implements TransportCrypto {
|
||||
|
||||
@@ -36,45 +43,44 @@ class TransportCryptoImpl implements TransportCrypto {
|
||||
|
||||
@Override
|
||||
public TransportKeys deriveTransportKeys(TransportId t,
|
||||
SecretKey master, long rotationPeriod, boolean alice,
|
||||
SecretKey rootKey, long timePeriod, boolean weAreAlice,
|
||||
boolean active) {
|
||||
// Keys for the previous period are derived from the master secret
|
||||
SecretKey inTagPrev = deriveTagKey(master, t, !alice);
|
||||
SecretKey inHeaderPrev = deriveHeaderKey(master, t, !alice);
|
||||
SecretKey outTagPrev = deriveTagKey(master, t, alice);
|
||||
SecretKey outHeaderPrev = deriveHeaderKey(master, t, alice);
|
||||
// Keys for the previous period are derived from the root key
|
||||
SecretKey inTagPrev = deriveTagKey(rootKey, t, !weAreAlice);
|
||||
SecretKey inHeaderPrev = deriveHeaderKey(rootKey, t, !weAreAlice);
|
||||
SecretKey outTagPrev = deriveTagKey(rootKey, t, weAreAlice);
|
||||
SecretKey outHeaderPrev = deriveHeaderKey(rootKey, t, weAreAlice);
|
||||
// Derive the keys for the current and next periods
|
||||
SecretKey inTagCurr = rotateKey(inTagPrev, rotationPeriod);
|
||||
SecretKey inHeaderCurr = rotateKey(inHeaderPrev, rotationPeriod);
|
||||
SecretKey inTagNext = rotateKey(inTagCurr, rotationPeriod + 1);
|
||||
SecretKey inHeaderNext = rotateKey(inHeaderCurr, rotationPeriod + 1);
|
||||
SecretKey outTagCurr = rotateKey(outTagPrev, rotationPeriod);
|
||||
SecretKey outHeaderCurr = rotateKey(outHeaderPrev, rotationPeriod);
|
||||
SecretKey inTagCurr = rotateKey(inTagPrev, timePeriod);
|
||||
SecretKey inHeaderCurr = rotateKey(inHeaderPrev, timePeriod);
|
||||
SecretKey inTagNext = rotateKey(inTagCurr, timePeriod + 1);
|
||||
SecretKey inHeaderNext = rotateKey(inHeaderCurr, timePeriod + 1);
|
||||
SecretKey outTagCurr = rotateKey(outTagPrev, timePeriod);
|
||||
SecretKey outHeaderCurr = rotateKey(outHeaderPrev, timePeriod);
|
||||
// Initialise the reordering windows and stream counters
|
||||
IncomingKeys inPrev = new IncomingKeys(inTagPrev, inHeaderPrev,
|
||||
rotationPeriod - 1);
|
||||
timePeriod - 1);
|
||||
IncomingKeys inCurr = new IncomingKeys(inTagCurr, inHeaderCurr,
|
||||
rotationPeriod);
|
||||
timePeriod);
|
||||
IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext,
|
||||
rotationPeriod + 1);
|
||||
timePeriod + 1);
|
||||
OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr,
|
||||
rotationPeriod, active);
|
||||
timePeriod, active);
|
||||
// Collect and return the keys
|
||||
return new TransportKeys(t, inPrev, inCurr, inNext, outCurr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportKeys rotateTransportKeys(TransportKeys k,
|
||||
long rotationPeriod) {
|
||||
if (k.getRotationPeriod() >= rotationPeriod) return k;
|
||||
public TransportKeys rotateTransportKeys(TransportKeys k, long timePeriod) {
|
||||
if (k.getTimePeriod() >= timePeriod) return k;
|
||||
IncomingKeys inPrev = k.getPreviousIncomingKeys();
|
||||
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
||||
IncomingKeys inNext = k.getNextIncomingKeys();
|
||||
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||
long startPeriod = outCurr.getRotationPeriod();
|
||||
long startPeriod = outCurr.getTimePeriod();
|
||||
boolean active = outCurr.isActive();
|
||||
// Rotate the keys
|
||||
for (long p = startPeriod + 1; p <= rotationPeriod; p++) {
|
||||
for (long p = startPeriod + 1; p <= timePeriod; p++) {
|
||||
inPrev = inCurr;
|
||||
inCurr = inNext;
|
||||
SecretKey inNextTag = rotateKey(inNext.getTagKey(), p + 1);
|
||||
@@ -89,24 +95,117 @@ class TransportCryptoImpl implements TransportCrypto {
|
||||
outCurr);
|
||||
}
|
||||
|
||||
private SecretKey rotateKey(SecretKey k, long rotationPeriod) {
|
||||
private SecretKey rotateKey(SecretKey k, long timePeriod) {
|
||||
byte[] period = new byte[INT_64_BYTES];
|
||||
ByteUtils.writeUint64(rotationPeriod, period, 0);
|
||||
writeUint64(timePeriod, period, 0);
|
||||
return crypto.deriveKey(ROTATE_LABEL, k, period);
|
||||
}
|
||||
|
||||
private SecretKey deriveTagKey(SecretKey master, TransportId t,
|
||||
boolean alice) {
|
||||
String label = alice ? ALICE_TAG_LABEL : BOB_TAG_LABEL;
|
||||
byte[] id = StringUtils.toUtf8(t.getString());
|
||||
return crypto.deriveKey(label, master, id);
|
||||
private SecretKey deriveTagKey(SecretKey rootKey, TransportId t,
|
||||
boolean keyBelongsToAlice) {
|
||||
String label = keyBelongsToAlice ? ALICE_TAG_LABEL : BOB_TAG_LABEL;
|
||||
byte[] id = toUtf8(t.getString());
|
||||
return crypto.deriveKey(label, rootKey, id);
|
||||
}
|
||||
|
||||
private SecretKey deriveHeaderKey(SecretKey master, TransportId t,
|
||||
boolean alice) {
|
||||
String label = alice ? ALICE_HEADER_LABEL : BOB_HEADER_LABEL;
|
||||
byte[] id = StringUtils.toUtf8(t.getString());
|
||||
return crypto.deriveKey(label, master, id);
|
||||
private SecretKey deriveHeaderKey(SecretKey rootKey, TransportId t,
|
||||
boolean keyBelongsToAlice) {
|
||||
String label = keyBelongsToAlice ? ALICE_HEADER_LABEL :
|
||||
BOB_HEADER_LABEL;
|
||||
byte[] id = toUtf8(t.getString());
|
||||
return crypto.deriveKey(label, rootKey, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandshakeKeys deriveHandshakeKeys(TransportId t, SecretKey rootKey,
|
||||
long timePeriod, boolean weAreAlice) {
|
||||
if (timePeriod < 1) throw new IllegalArgumentException();
|
||||
IncomingKeys inPrev = deriveIncomingHandshakeKeys(t, rootKey,
|
||||
weAreAlice, timePeriod - 1);
|
||||
IncomingKeys inCurr = deriveIncomingHandshakeKeys(t, rootKey,
|
||||
weAreAlice, timePeriod);
|
||||
IncomingKeys inNext = deriveIncomingHandshakeKeys(t, rootKey,
|
||||
weAreAlice, timePeriod + 1);
|
||||
OutgoingKeys outCurr = deriveOutgoingHandshakeKeys(t, rootKey,
|
||||
weAreAlice, timePeriod);
|
||||
return new HandshakeKeys(t, inPrev, inCurr, inNext, outCurr, rootKey,
|
||||
weAreAlice);
|
||||
}
|
||||
|
||||
private IncomingKeys deriveIncomingHandshakeKeys(TransportId t,
|
||||
SecretKey rootKey, boolean weAreAlice, long timePeriod) {
|
||||
SecretKey tag = deriveHandshakeTagKey(t, rootKey, !weAreAlice,
|
||||
timePeriod);
|
||||
SecretKey header = deriveHandshakeHeaderKey(t, rootKey, !weAreAlice,
|
||||
timePeriod);
|
||||
return new IncomingKeys(tag, header, timePeriod);
|
||||
}
|
||||
|
||||
private OutgoingKeys deriveOutgoingHandshakeKeys(TransportId t,
|
||||
SecretKey rootKey, boolean weAreAlice, long timePeriod) {
|
||||
SecretKey tag = deriveHandshakeTagKey(t, rootKey, weAreAlice,
|
||||
timePeriod);
|
||||
SecretKey header = deriveHandshakeHeaderKey(t, rootKey, weAreAlice,
|
||||
timePeriod);
|
||||
return new OutgoingKeys(tag, header, timePeriod, true);
|
||||
}
|
||||
|
||||
private SecretKey deriveHandshakeTagKey(TransportId t, SecretKey rootKey,
|
||||
boolean keyBelongsToAlice, long timePeriod) {
|
||||
String label = keyBelongsToAlice ? ALICE_HANDSHAKE_TAG_LABEL :
|
||||
BOB_HANDSHAKE_TAG_LABEL;
|
||||
byte[] id = toUtf8(t.getString());
|
||||
byte[] period = new byte[INT_64_BYTES];
|
||||
writeUint64(timePeriod, period, 0);
|
||||
return crypto.deriveKey(label, rootKey, id, period);
|
||||
}
|
||||
|
||||
private SecretKey deriveHandshakeHeaderKey(TransportId t, SecretKey rootKey,
|
||||
boolean keyBelongsToAlice, long timePeriod) {
|
||||
String label = keyBelongsToAlice ? ALICE_HANDSHAKE_HEADER_LABEL :
|
||||
BOB_HANDSHAKE_HEADER_LABEL;
|
||||
byte[] id = toUtf8(t.getString());
|
||||
byte[] period = new byte[INT_64_BYTES];
|
||||
writeUint64(timePeriod, period, 0);
|
||||
return crypto.deriveKey(label, rootKey, id, period);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandshakeKeys updateHandshakeKeys(HandshakeKeys k, long timePeriod) {
|
||||
long elapsed = timePeriod - k.getTimePeriod();
|
||||
TransportId t = k.getTransportId();
|
||||
SecretKey rootKey = k.getRootKey();
|
||||
boolean weAreAlice = k.isAlice();
|
||||
if (elapsed <= 0) {
|
||||
// The keys are for the given period or later - don't update them
|
||||
return k;
|
||||
} else if (elapsed == 1) {
|
||||
// The keys are one period old - shift by one period, keeping the
|
||||
// reordering windows for keys we retain
|
||||
IncomingKeys inPrev = k.getCurrentIncomingKeys();
|
||||
IncomingKeys inCurr = k.getNextIncomingKeys();
|
||||
IncomingKeys inNext = deriveIncomingHandshakeKeys(t, rootKey,
|
||||
weAreAlice, timePeriod + 1);
|
||||
OutgoingKeys outCurr = deriveOutgoingHandshakeKeys(t, rootKey,
|
||||
weAreAlice, timePeriod);
|
||||
return new HandshakeKeys(t, inPrev, inCurr, inNext, outCurr,
|
||||
rootKey, weAreAlice);
|
||||
} else if (elapsed == 2) {
|
||||
// The keys are two periods old - shift by two periods, keeping
|
||||
// the reordering windows for keys we retain
|
||||
IncomingKeys inPrev = k.getNextIncomingKeys();
|
||||
IncomingKeys inCurr = deriveIncomingHandshakeKeys(t, rootKey,
|
||||
weAreAlice, timePeriod);
|
||||
IncomingKeys inNext = deriveIncomingHandshakeKeys(t, rootKey,
|
||||
weAreAlice, timePeriod + 1);
|
||||
OutgoingKeys outCurr = deriveOutgoingHandshakeKeys(t, rootKey,
|
||||
weAreAlice, timePeriod);
|
||||
return new HandshakeKeys(t, inPrev, inCurr, inNext, outCurr,
|
||||
rootKey, weAreAlice);
|
||||
} else {
|
||||
// The keys are more than two periods old - derive fresh keys
|
||||
return deriveHandshakeKeys(t, rootKey, timePeriod, weAreAlice);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,14 +224,14 @@ class TransportCryptoImpl implements TransportCrypto {
|
||||
// The input is the protocol version as a 16-bit integer, followed by
|
||||
// the stream number as a 64-bit integer
|
||||
byte[] protocolVersionBytes = new byte[INT_16_BYTES];
|
||||
ByteUtils.writeUint16(protocolVersion, protocolVersionBytes, 0);
|
||||
writeUint16(protocolVersion, protocolVersionBytes, 0);
|
||||
prf.update(protocolVersionBytes, 0, protocolVersionBytes.length);
|
||||
byte[] streamNumberBytes = new byte[INT_64_BYTES];
|
||||
ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
|
||||
writeUint64(streamNumber, streamNumberBytes, 0);
|
||||
prf.update(streamNumberBytes, 0, streamNumberBytes.length);
|
||||
byte[] mac = new byte[macLength];
|
||||
prf.doFinal(mac, 0);
|
||||
// The output is the first TAG_LENGTH bytes of the MAC
|
||||
System.arraycopy(mac, 0, tag, 0, TAG_LENGTH);
|
||||
arraycopy(mac, 0, tag, 0, TAG_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,13 @@ package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
import org.briarproject.bramble.api.contact.PendingContactId;
|
||||
import org.briarproject.bramble.api.contact.PendingContactState;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.MessageDeletedException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
@@ -23,8 +27,11 @@ import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.MessageStatus;
|
||||
import org.briarproject.bramble.api.sync.validation.MessageState;
|
||||
import org.briarproject.bramble.api.transport.KeySet;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.HandshakeKeySet;
|
||||
import org.briarproject.bramble.api.transport.HandshakeKeySetId;
|
||||
import org.briarproject.bramble.api.transport.HandshakeKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeySet;
|
||||
import org.briarproject.bramble.api.transport.TransportKeySetId;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -33,11 +40,14 @@ import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A low-level interface to the database (DatabaseComponent provides a
|
||||
* high-level interface). Most operations take a transaction argument, which is
|
||||
* obtained by calling {@link #startTransaction()}. Every transaction must be
|
||||
* terminated by calling either {@link #abortTransaction(Object) abortTransaction(T)} or
|
||||
* {@link #commitTransaction(Object) commitTransaction(T)}, even if an exception is thrown.
|
||||
* A low-level interface to the database ({@link DatabaseComponent} provides a
|
||||
* high-level interface).
|
||||
* <p/>
|
||||
* Most operations take a transaction argument, which is obtained by calling
|
||||
* {@link #startTransaction()}. Every transaction must be terminated by calling
|
||||
* either {@link #abortTransaction(Object) abortTransaction(T)} or
|
||||
* {@link #commitTransaction(Object) commitTransaction(T)}, even if an
|
||||
* exception is thrown.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
interface Database<T> {
|
||||
@@ -77,11 +87,10 @@ interface Database<T> {
|
||||
void commitTransaction(T txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a contact associated with the given local and remote pseudonyms,
|
||||
* and returns an ID for the contact.
|
||||
* Stores a contact with the given pseudonym and returns an ID for the
|
||||
* contact.
|
||||
*/
|
||||
ContactId addContact(T txn, Author remote, AuthorId local, boolean verified,
|
||||
boolean active) throws DbException;
|
||||
ContactId addContact(T txn, Author a, boolean verified) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a group.
|
||||
@@ -95,6 +104,20 @@ interface Database<T> {
|
||||
void addGroupVisibility(T txn, ContactId c, GroupId g, boolean shared)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given handshake keys for the given contact and returns a
|
||||
* key set ID.
|
||||
*/
|
||||
HandshakeKeySetId addHandshakeKeys(T txn, ContactId c, HandshakeKeys k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given handshake keys for the given pending contact and
|
||||
* returns a key set ID.
|
||||
*/
|
||||
HandshakeKeySetId addHandshakeKeys(T txn, PendingContactId p,
|
||||
HandshakeKeys k) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a local pseudonym.
|
||||
*/
|
||||
@@ -121,6 +144,11 @@ interface Database<T> {
|
||||
*/
|
||||
void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a pending contact.
|
||||
*/
|
||||
void addPendingContact(T txn, PendingContact p) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a transport.
|
||||
*/
|
||||
@@ -131,17 +159,15 @@ interface Database<T> {
|
||||
* Stores the given transport keys for the given contact and returns a
|
||||
* key set ID.
|
||||
*/
|
||||
KeySetId addTransportKeys(T txn, ContactId c, TransportKeys k)
|
||||
TransportKeySetId addTransportKeys(T txn, ContactId c, TransportKeys k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given contact for the given
|
||||
* local pseudonym.
|
||||
* Returns true if the database contains the given contact.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
boolean containsContact(T txn, AuthorId remote, AuthorId local)
|
||||
throws DbException;
|
||||
boolean containsContact(T txn, AuthorId a) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given contact.
|
||||
@@ -171,6 +197,14 @@ interface Database<T> {
|
||||
*/
|
||||
boolean containsMessage(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given pending contact.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
boolean containsPendingContact(T txn, PendingContactId p)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given transport.
|
||||
* <p/>
|
||||
@@ -215,6 +249,13 @@ interface Database<T> {
|
||||
*/
|
||||
Contact getContact(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the contact with the given author ID.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Contact getContact(T txn, AuthorId a) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all contacts.
|
||||
* <p/>
|
||||
@@ -222,28 +263,6 @@ interface Database<T> {
|
||||
*/
|
||||
Collection<Contact> getContacts(T txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns a possibly empty collection of contacts with the given author ID.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<Contact> getContactsByAuthorId(T txn, AuthorId remote)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all contacts associated with the given local pseudonym.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<ContactId> getContacts(T txn, AuthorId a) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the amount of free storage space available to the database, in
|
||||
* bytes. This is based on the minimum of the space available on the device
|
||||
* where the database is stored and the database's configured size.
|
||||
*/
|
||||
long getFreeSpace() throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the group with the given ID.
|
||||
* <p/>
|
||||
@@ -284,6 +303,14 @@ interface Database<T> {
|
||||
Map<ContactId, Boolean> getGroupVisibility(T txn, GroupId g)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all handshake keys for the given transport.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<HandshakeKeySet> getHandshakeKeys(T txn, TransportId t)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the local pseudonym with the given ID.
|
||||
* <p/>
|
||||
@@ -474,6 +501,13 @@ interface Database<T> {
|
||||
*/
|
||||
long getNextSendTime(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all pending contacts.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<PendingContact> getPendingContacts(T txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of some messages that are eligible to be sent to the
|
||||
* given contact and have been requested by the contact, up to the given
|
||||
@@ -496,13 +530,19 @@ interface Database<T> {
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<KeySet> getTransportKeys(T txn, TransportId t)
|
||||
Collection<TransportKeySet> getTransportKeys(T txn, TransportId t)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Increments the outgoing stream counter for the given handshake keys.
|
||||
*/
|
||||
void incrementStreamCounter(T txn, TransportId t, HandshakeKeySetId k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Increments the outgoing stream counter for the given transport keys.
|
||||
*/
|
||||
void incrementStreamCounter(T txn, TransportId t, KeySetId k)
|
||||
void incrementStreamCounter(T txn, TransportId t, TransportKeySetId k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
@@ -571,6 +611,12 @@ interface Database<T> {
|
||||
void removeGroupVisibility(T txn, ContactId c, GroupId g)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Removes the given handshake keys from the database.
|
||||
*/
|
||||
void removeHandshakeKeys(T txn, TransportId t, HandshakeKeySetId k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a local pseudonym (and all associated state) from the database.
|
||||
*/
|
||||
@@ -588,6 +634,11 @@ interface Database<T> {
|
||||
void removeOfferedMessages(T txn, ContactId c,
|
||||
Collection<MessageId> requested) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a pending contact (and all associated state) from the database.
|
||||
*/
|
||||
void removePendingContact(T txn, PendingContactId p) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a transport (and all associated state) from the database.
|
||||
*/
|
||||
@@ -596,7 +647,7 @@ interface Database<T> {
|
||||
/**
|
||||
* Removes the given transport keys from the database.
|
||||
*/
|
||||
void removeTransportKeys(T txn, TransportId t, KeySetId k)
|
||||
void removeTransportKeys(T txn, TransportId t, TransportKeySetId k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
@@ -610,12 +661,6 @@ interface Database<T> {
|
||||
*/
|
||||
void setContactVerified(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given contact as active or inactive.
|
||||
*/
|
||||
void setContactActive(T txn, ContactId c, boolean active)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Sets an alias name for a contact.
|
||||
*/
|
||||
@@ -641,16 +686,29 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the reordering window for the given key set and transport in the
|
||||
* given rotation period.
|
||||
* Sets the state of the given pending contact.
|
||||
*/
|
||||
void setReorderingWindow(T txn, KeySetId k, TransportId t,
|
||||
long rotationPeriod, long base, byte[] bitmap) throws DbException;
|
||||
void setPendingContactState(T txn, PendingContactId p,
|
||||
PendingContactState state) throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the reordering window for the given transport key set in the given
|
||||
* time period.
|
||||
*/
|
||||
void setReorderingWindow(T txn, TransportKeySetId k, TransportId t,
|
||||
long timePeriod, long base, byte[] bitmap) throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the reordering window for the given handshake key set in the given
|
||||
* time period.
|
||||
*/
|
||||
void setReorderingWindow(T txn, HandshakeKeySetId k, TransportId t,
|
||||
long timePeriod, long base, byte[] bitmap) throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given transport keys as usable for outgoing streams.
|
||||
*/
|
||||
void setTransportKeysActive(T txn, TransportId t, KeySetId k)
|
||||
void setTransportKeysActive(T txn, TransportId t, TransportKeySetId k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
@@ -661,8 +719,13 @@ interface Database<T> {
|
||||
void updateExpiryTimeAndEta(T txn, ContactId c, MessageId m, int maxLatency)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Updates the given handshake keys.
|
||||
*/
|
||||
void updateHandshakeKeys(T txn, HandshakeKeySet ks) throws DbException;
|
||||
|
||||
/**
|
||||
* Updates the given transport keys following key rotation.
|
||||
*/
|
||||
void updateTransportKeys(T txn, KeySet ks) throws DbException;
|
||||
void updateTransportKeys(T txn, TransportKeySet ks) throws DbException;
|
||||
}
|
||||
|
||||
@@ -2,27 +2,34 @@ package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
import org.briarproject.bramble.api.contact.PendingContactId;
|
||||
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.ContactVerifiedEvent;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.CommitAction;
|
||||
import org.briarproject.bramble.api.db.CommitAction.Visitor;
|
||||
import org.briarproject.bramble.api.db.ContactExistsException;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbCallable;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.DbRunnable;
|
||||
import org.briarproject.bramble.api.db.EventAction;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.MigrationListener;
|
||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||
import org.briarproject.bramble.api.db.NoSuchGroupException;
|
||||
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
|
||||
import org.briarproject.bramble.api.db.NoSuchMessageException;
|
||||
import org.briarproject.bramble.api.db.NoSuchPendingContactException;
|
||||
import org.briarproject.bramble.api.db.NoSuchTransportException;
|
||||
import org.briarproject.bramble.api.db.NullableDbCallable;
|
||||
import org.briarproject.bramble.api.db.PendingContactExistsException;
|
||||
import org.briarproject.bramble.api.db.TaskAction;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
@@ -55,8 +62,11 @@ import org.briarproject.bramble.api.sync.event.MessageToRequestEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
||||
import org.briarproject.bramble.api.sync.validation.MessageState;
|
||||
import org.briarproject.bramble.api.transport.KeySet;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.HandshakeKeySet;
|
||||
import org.briarproject.bramble.api.transport.HandshakeKeySetId;
|
||||
import org.briarproject.bramble.api.transport.HandshakeKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeySet;
|
||||
import org.briarproject.bramble.api.transport.TransportKeySetId;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -64,6 +74,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.logging.Logger;
|
||||
@@ -92,25 +103,29 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
private final Database<T> db;
|
||||
private final Class<T> txnClass;
|
||||
private final EventBus eventBus;
|
||||
private final ShutdownManager shutdown;
|
||||
private final Executor eventExecutor;
|
||||
private final ShutdownManager shutdownManager;
|
||||
private final AtomicBoolean closed = new AtomicBoolean(false);
|
||||
private final ReentrantReadWriteLock lock =
|
||||
new ReentrantReadWriteLock(true);
|
||||
private final Visitor visitor = new CommitActionVisitor();
|
||||
|
||||
@Inject
|
||||
DatabaseComponentImpl(Database<T> db, Class<T> txnClass, EventBus eventBus,
|
||||
ShutdownManager shutdown) {
|
||||
@EventExecutor Executor eventExecutor,
|
||||
ShutdownManager shutdownManager) {
|
||||
this.db = db;
|
||||
this.txnClass = txnClass;
|
||||
this.eventBus = eventBus;
|
||||
this.shutdown = shutdown;
|
||||
this.eventExecutor = eventExecutor;
|
||||
this.shutdownManager = shutdownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open(SecretKey key, @Nullable MigrationListener listener)
|
||||
throws DbException {
|
||||
boolean reopened = db.open(key, listener);
|
||||
shutdown.addShutdownHook(() -> {
|
||||
shutdownManager.addShutdownHook(() -> {
|
||||
try {
|
||||
close();
|
||||
} catch (DbException e) {
|
||||
@@ -160,13 +175,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
public void endTransaction(Transaction transaction) {
|
||||
try {
|
||||
T txn = txnClass.cast(transaction.unbox());
|
||||
if (!transaction.isCommitted()) db.abortTransaction(txn);
|
||||
if (transaction.isCommitted()) {
|
||||
for (CommitAction a : transaction.getActions())
|
||||
a.accept(visitor);
|
||||
} else {
|
||||
db.abortTransaction(txn);
|
||||
}
|
||||
} finally {
|
||||
if (transaction.isReadOnly()) lock.readLock().unlock();
|
||||
else lock.writeLock().unlock();
|
||||
}
|
||||
if (transaction.isCommitted())
|
||||
for (Event e : transaction.getEvents()) eventBus.broadcast(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -214,20 +232,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactId addContact(Transaction transaction, Author remote,
|
||||
AuthorId local, boolean verified, boolean active)
|
||||
throws DbException {
|
||||
public ContactId addContact(Transaction transaction, Author a,
|
||||
boolean verified) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsLocalAuthor(txn, local))
|
||||
throw new NoSuchLocalAuthorException();
|
||||
if (db.containsLocalAuthor(txn, remote.getId()))
|
||||
if (db.containsLocalAuthor(txn, a.getId()))
|
||||
throw new ContactExistsException();
|
||||
if (db.containsContact(txn, remote.getId(), local))
|
||||
if (db.containsContact(txn, a.getId()))
|
||||
throw new ContactExistsException();
|
||||
ContactId c = db.addContact(txn, remote, local, verified, active);
|
||||
transaction.attach(new ContactAddedEvent(c, active));
|
||||
if (active) transaction.attach(new ContactStatusChangedEvent(c, true));
|
||||
ContactId c = db.addContact(txn, a, verified);
|
||||
transaction.attach(new ContactAddedEvent(c));
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -241,6 +255,30 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandshakeKeySetId addHandshakeKeys(Transaction transaction,
|
||||
ContactId c, HandshakeKeys k) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
if (!db.containsTransport(txn, k.getTransportId()))
|
||||
throw new NoSuchTransportException();
|
||||
return db.addHandshakeKeys(txn, c, k);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandshakeKeySetId addHandshakeKeys(Transaction transaction,
|
||||
PendingContactId p, HandshakeKeys k) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsPendingContact(txn, p))
|
||||
throw new NoSuchPendingContactException();
|
||||
if (!db.containsTransport(txn, k.getTransportId()))
|
||||
throw new NoSuchTransportException();
|
||||
return db.addHandshakeKeys(txn, p, k);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLocalAuthor(Transaction transaction, LocalAuthor a)
|
||||
throws DbException {
|
||||
@@ -269,6 +307,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
db.mergeMessageMetadata(txn, m.getId(), meta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPendingContact(Transaction transaction, PendingContact p)
|
||||
throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (db.containsPendingContact(txn, p.getId()))
|
||||
throw new PendingContactExistsException();
|
||||
db.addPendingContact(txn, p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTransport(Transaction transaction, TransportId t,
|
||||
int maxLatency) throws DbException {
|
||||
@@ -279,8 +327,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeySetId addTransportKeys(Transaction transaction, ContactId c,
|
||||
TransportKeys k) throws DbException {
|
||||
public TransportKeySetId addTransportKeys(Transaction transaction,
|
||||
ContactId c, TransportKeys k) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
@@ -291,12 +339,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsContact(Transaction transaction, AuthorId remote,
|
||||
AuthorId local) throws DbException {
|
||||
public boolean containsContact(Transaction transaction, AuthorId a)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsLocalAuthor(txn, local))
|
||||
throw new NoSuchLocalAuthorException();
|
||||
return db.containsContact(txn, remote, local);
|
||||
return db.containsContact(txn, a);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -313,6 +359,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.containsLocalAuthor(txn, local);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsPendingContact(Transaction transaction,
|
||||
PendingContactId p) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.containsPendingContact(txn, p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteMessage(Transaction transaction, MessageId m)
|
||||
throws DbException {
|
||||
@@ -429,6 +482,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getContact(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contact getContact(Transaction transaction, AuthorId a)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, a))
|
||||
throw new NoSuchContactException();
|
||||
return db.getContact(txn, a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Contact> getContacts(Transaction transaction)
|
||||
throws DbException {
|
||||
@@ -436,22 +498,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getContacts(txn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Contact> getContactsByAuthorId(Transaction transaction,
|
||||
AuthorId remote) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getContactsByAuthorId(txn, remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ContactId> getContacts(Transaction transaction,
|
||||
AuthorId a) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsLocalAuthor(txn, a))
|
||||
throw new NoSuchLocalAuthorException();
|
||||
return db.getContacts(txn, a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group getGroup(Transaction transaction, GroupId g)
|
||||
throws DbException {
|
||||
@@ -486,6 +532,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getGroupVisibility(txn, c, g);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<HandshakeKeySet> getHandshakeKeys(Transaction transaction,
|
||||
TransportId t) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsTransport(txn, t))
|
||||
throw new NoSuchTransportException();
|
||||
return db.getHandshakeKeys(txn, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalAuthor getLocalAuthor(Transaction transaction, AuthorId a)
|
||||
throws DbException {
|
||||
@@ -643,6 +698,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getNextSendTime(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PendingContact> getPendingContacts(
|
||||
Transaction transaction) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getPendingContacts(txn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Settings getSettings(Transaction transaction, String namespace)
|
||||
throws DbException {
|
||||
@@ -651,7 +713,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<KeySet> getTransportKeys(Transaction transaction,
|
||||
public Collection<TransportKeySet> getTransportKeys(Transaction transaction,
|
||||
TransportId t) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsTransport(txn, t))
|
||||
@@ -661,7 +723,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
|
||||
@Override
|
||||
public void incrementStreamCounter(Transaction transaction, TransportId t,
|
||||
KeySetId k) throws DbException {
|
||||
HandshakeKeySetId k) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsTransport(txn, t))
|
||||
throw new NoSuchTransportException();
|
||||
db.incrementStreamCounter(txn, t, k);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementStreamCounter(Transaction transaction, TransportId t,
|
||||
TransportKeySetId k) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsTransport(txn, t))
|
||||
@@ -810,6 +882,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeHandshakeKeys(Transaction transaction,
|
||||
TransportId t, HandshakeKeySetId k) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsTransport(txn, t))
|
||||
throw new NoSuchTransportException();
|
||||
db.removeHandshakeKeys(txn, t, k);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeLocalAuthor(Transaction transaction, AuthorId a)
|
||||
throws DbException {
|
||||
@@ -832,6 +914,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
db.removeMessage(txn, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePendingContact(Transaction transaction,
|
||||
PendingContactId p) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsPendingContact(txn, p))
|
||||
throw new NoSuchPendingContactException();
|
||||
db.removePendingContact(txn, p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTransport(Transaction transaction, TransportId t)
|
||||
throws DbException {
|
||||
@@ -844,7 +936,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
|
||||
@Override
|
||||
public void removeTransportKeys(Transaction transaction,
|
||||
TransportId t, KeySetId k) throws DbException {
|
||||
TransportId t, TransportKeySetId k) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsTransport(txn, t))
|
||||
@@ -863,17 +955,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
transaction.attach(new ContactVerifiedEvent(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContactActive(Transaction transaction, ContactId c,
|
||||
boolean active) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
db.setContactActive(txn, c, active);
|
||||
transaction.attach(new ContactStatusChangedEvent(c, active));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContactAlias(Transaction transaction, ContactId c,
|
||||
@Nullable String alias) throws DbException {
|
||||
@@ -943,19 +1024,30 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReorderingWindow(Transaction transaction, KeySetId k,
|
||||
TransportId t, long rotationPeriod, long base, byte[] bitmap)
|
||||
throws DbException {
|
||||
public void setReorderingWindow(Transaction transaction,
|
||||
TransportKeySetId k, TransportId t, long timePeriod, long base,
|
||||
byte[] bitmap) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsTransport(txn, t))
|
||||
throw new NoSuchTransportException();
|
||||
db.setReorderingWindow(txn, k, t, rotationPeriod, base, bitmap);
|
||||
db.setReorderingWindow(txn, k, t, timePeriod, base, bitmap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReorderingWindow(Transaction transaction,
|
||||
HandshakeKeySetId k, TransportId t, long timePeriod, long base,
|
||||
byte[] bitmap) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsTransport(txn, t))
|
||||
throw new NoSuchTransportException();
|
||||
db.setReorderingWindow(txn, k, t, timePeriod, base, bitmap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTransportKeysActive(Transaction transaction, TransportId t,
|
||||
KeySetId k) throws DbException {
|
||||
TransportKeySetId k) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsTransport(txn, t))
|
||||
@@ -964,14 +1056,39 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTransportKeys(Transaction transaction,
|
||||
Collection<KeySet> keys) throws DbException {
|
||||
public void updateHandshakeKeys(Transaction transaction,
|
||||
Collection<HandshakeKeySet> keys) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
for (KeySet ks : keys) {
|
||||
TransportId t = ks.getTransportKeys().getTransportId();
|
||||
for (HandshakeKeySet ks : keys) {
|
||||
TransportId t = ks.getKeys().getTransportId();
|
||||
if (db.containsTransport(txn, t))
|
||||
db.updateHandshakeKeys(txn, ks);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTransportKeys(Transaction transaction,
|
||||
Collection<TransportKeySet> keys) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
for (TransportKeySet ks : keys) {
|
||||
TransportId t = ks.getKeys().getTransportId();
|
||||
if (db.containsTransport(txn, t))
|
||||
db.updateTransportKeys(txn, ks);
|
||||
}
|
||||
}
|
||||
|
||||
private class CommitActionVisitor implements Visitor {
|
||||
|
||||
@Override
|
||||
public void visit(EventAction a) {
|
||||
eventBus.broadcast(a.getEvent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(TaskAction a) {
|
||||
eventExecutor.execute(a.getTask());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ package org.briarproject.bramble.db;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.ShutdownManager;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@@ -27,8 +29,9 @@ public class DatabaseModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
DatabaseComponent provideDatabaseComponent(Database<Connection> db,
|
||||
EventBus eventBus, ShutdownManager shutdown) {
|
||||
EventBus eventBus, @EventExecutor Executor eventExecutor,
|
||||
ShutdownManager shutdownManager) {
|
||||
return new DatabaseComponentImpl<>(db, Connection.class, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,30 +76,6 @@ class H2Database extends JdbcDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFreeSpace() {
|
||||
File dir = config.getDatabaseDirectory();
|
||||
long maxSize = config.getMaxSize();
|
||||
long free = dir.getFreeSpace();
|
||||
long used = getDiskSpace(dir);
|
||||
long quota = maxSize - used;
|
||||
return Math.min(free, quota);
|
||||
}
|
||||
|
||||
private long getDiskSpace(File f) {
|
||||
if (f.isDirectory()) {
|
||||
long total = 0;
|
||||
File[] children = f.listFiles();
|
||||
if (children != null)
|
||||
for (File child : children) total += getDiskSpace(child);
|
||||
return total;
|
||||
} else if (f.isFile()) {
|
||||
return f.length();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Connection createConnection() throws SQLException {
|
||||
SecretKey key = this.key;
|
||||
|
||||
@@ -86,30 +86,6 @@ class HyperSqlDatabase extends JdbcDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFreeSpace() {
|
||||
File dir = config.getDatabaseDirectory();
|
||||
long maxSize = config.getMaxSize();
|
||||
long free = dir.getFreeSpace();
|
||||
long used = getDiskSpace(dir);
|
||||
long quota = maxSize - used;
|
||||
return Math.min(free, quota);
|
||||
}
|
||||
|
||||
private long getDiskSpace(File f) {
|
||||
if (f.isDirectory()) {
|
||||
long total = 0;
|
||||
File[] children = f.listFiles();
|
||||
if (children != null)
|
||||
for (File child : children) total += getDiskSpace(child);
|
||||
return total;
|
||||
} else if (f.isFile()) {
|
||||
return f.length();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Connection createConnection() throws SQLException {
|
||||
SecretKey key = this.key;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,7 @@ class Migration40_41 implements Migration<Connection> {
|
||||
|
||||
private final DatabaseTypes dbTypes;
|
||||
|
||||
public Migration40_41(DatabaseTypes databaseTypes) {
|
||||
Migration40_41(DatabaseTypes databaseTypes) {
|
||||
this.dbTypes = databaseTypes;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||
|
||||
class Migration41_42 implements Migration<Connection> {
|
||||
|
||||
private static final Logger LOG = getLogger(Migration41_42.class.getName());
|
||||
|
||||
private final DatabaseTypes dbTypes;
|
||||
|
||||
Migration41_42(DatabaseTypes dbTypes) {
|
||||
this.dbTypes = dbTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartVersion() {
|
||||
return 41;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndVersion() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrate(Connection txn) throws DbException {
|
||||
Statement s = null;
|
||||
try {
|
||||
s = txn.createStatement();
|
||||
s.execute("ALTER TABLE outgoingKeys"
|
||||
+ " ALTER COLUMN rotationPeriod"
|
||||
+ " RENAME TO timePeriod");
|
||||
s.execute("ALTER TABLE incomingKeys"
|
||||
+ " ALTER COLUMN rotationPeriod"
|
||||
+ " RENAME TO timePeriod");
|
||||
s.execute("ALTER TABLE incomingKeys"
|
||||
+ " DROP COLUMN contactId");
|
||||
s.execute(dbTypes.replaceTypes("CREATE TABLE pendingContacts"
|
||||
+ " (pendingContactId _HASH NOT NULL,"
|
||||
+ " publicKey _BINARY NOT NULL,"
|
||||
+ " alias _STRING NOT NULL,"
|
||||
+ " state INT NOT NULL,"
|
||||
+ " timestamp BIGINT NOT NULL,"
|
||||
+ " PRIMARY KEY (pendingContactId))"));
|
||||
s.execute(dbTypes.replaceTypes("CREATE TABLE outgoingHandshakeKeys"
|
||||
+ " (transportId _STRING NOT NULL,"
|
||||
+ " keySetId _COUNTER,"
|
||||
+ " timePeriod BIGINT NOT NULL,"
|
||||
+ " contactId INT," // Null if contact is pending
|
||||
+ " pendingContactId _HASH," // Null if not pending
|
||||
+ " rootKey _SECRET NOT NULL,"
|
||||
+ " alice BOOLEAN NOT NULL,"
|
||||
+ " tagKey _SECRET NOT NULL,"
|
||||
+ " headerKey _SECRET NOT NULL,"
|
||||
+ " stream BIGINT NOT NULL,"
|
||||
+ " PRIMARY KEY (transportId, keySetId),"
|
||||
+ " FOREIGN KEY (transportId)"
|
||||
+ " REFERENCES transports (transportId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " UNIQUE (keySetId),"
|
||||
+ " FOREIGN KEY (contactId)"
|
||||
+ " REFERENCES contacts (contactId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY (pendingContactId)"
|
||||
+ " REFERENCES pendingContacts (pendingContactId)"
|
||||
+ " ON DELETE CASCADE)"));
|
||||
s.execute(dbTypes.replaceTypes("CREATE TABLE incomingHandshakeKeys"
|
||||
+ " (transportId _STRING NOT NULL,"
|
||||
+ " keySetId INT NOT NULL,"
|
||||
+ " timePeriod BIGINT NOT NULL,"
|
||||
+ " tagKey _SECRET NOT NULL,"
|
||||
+ " headerKey _SECRET NOT NULL,"
|
||||
+ " base BIGINT NOT NULL,"
|
||||
+ " bitmap _BINARY NOT NULL,"
|
||||
+ " periodOffset INT NOT NULL,"
|
||||
+ " PRIMARY KEY (transportId, keySetId, periodOffset),"
|
||||
+ " FOREIGN KEY (transportId)"
|
||||
+ " REFERENCES transports (transportId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY (keySetId)"
|
||||
+ " REFERENCES outgoingHandshakeKeys (keySetId)"
|
||||
+ " ON DELETE CASCADE)"));
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||
|
||||
class Migration42_43 implements Migration<Connection> {
|
||||
|
||||
private static final Logger LOG = getLogger(Migration42_43.class.getName());
|
||||
|
||||
private final DatabaseTypes dbTypes;
|
||||
|
||||
Migration42_43(DatabaseTypes dbTypes) {
|
||||
this.dbTypes = dbTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartVersion() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndVersion() {
|
||||
return 43;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrate(Connection txn) throws DbException {
|
||||
Statement s = null;
|
||||
try {
|
||||
s = txn.createStatement();
|
||||
s.execute(dbTypes.replaceTypes("ALTER TABLE localAuthors"
|
||||
+ " ADD COLUMN handshakePublicKey _BINARY"));
|
||||
s.execute(dbTypes.replaceTypes("ALTER TABLE localAuthors"
|
||||
+ " ADD COLUMN handshakePrivateKey _BINARY"));
|
||||
s.execute(dbTypes.replaceTypes("ALTER TABLE contacts"
|
||||
+ " ADD COLUMN handshakePublicKey _BINARY"));
|
||||
s.execute("ALTER TABLE contacts"
|
||||
+ " DROP COLUMN active");
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||
|
||||
class Migration43_44 implements Migration<Connection> {
|
||||
|
||||
private static final Logger LOG = getLogger(Migration43_44.class.getName());
|
||||
|
||||
@Override
|
||||
public int getStartVersion() {
|
||||
return 43;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndVersion() {
|
||||
return 44;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrate(Connection txn) throws DbException {
|
||||
Statement s = null;
|
||||
try {
|
||||
s = txn.createStatement();
|
||||
s.execute("ALTER TABLE contacts"
|
||||
+ " DROP COLUMN localAuthorId");
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.briarproject.bramble.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static java.util.concurrent.Executors.newSingleThreadExecutor;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link EventExecutor} that uses a dedicated thread
|
||||
* to notify listeners of events. Applications may prefer to supply an
|
||||
* implementation that uses an existing thread, such as the UI thread.
|
||||
*/
|
||||
@Module
|
||||
public class DefaultEventExecutorModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@EventExecutor
|
||||
Executor provideEventExecutor() {
|
||||
return newSingleThreadExecutor(r -> {
|
||||
Thread t = new Thread(r);
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,16 @@ package org.briarproject.bramble.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
@@ -16,6 +19,12 @@ class EventBusImpl implements EventBus {
|
||||
|
||||
private final Collection<EventListener> listeners =
|
||||
new CopyOnWriteArrayList<>();
|
||||
private final Executor eventExecutor;
|
||||
|
||||
@Inject
|
||||
EventBusImpl(@EventExecutor Executor eventExecutor) {
|
||||
this.eventExecutor = eventExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(EventListener l) {
|
||||
@@ -29,6 +38,8 @@ class EventBusImpl implements EventBus {
|
||||
|
||||
@Override
|
||||
public void broadcast(Event e) {
|
||||
for (EventListener l : listeners) l.eventOccurred(e);
|
||||
eventExecutor.execute(() -> {
|
||||
for (EventListener l : listeners) l.eventOccurred(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ public class EventModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
EventBus provideEventBus() {
|
||||
return new EventBusImpl();
|
||||
EventBus provideEventBus(EventBusImpl eventBus) {
|
||||
return eventBus;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_SECRET_LABEL;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_KEY_LABEL;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL;
|
||||
|
||||
@@ -90,7 +90,7 @@ class KeyAgreementProtocol {
|
||||
/**
|
||||
* Perform the BQP protocol.
|
||||
*
|
||||
* @return the negotiated master secret.
|
||||
* @return the negotiated master key.
|
||||
* @throws AbortException when the protocol may have been tampered with.
|
||||
* @throws IOException for all other other connection errors.
|
||||
*/
|
||||
@@ -115,7 +115,7 @@ class KeyAgreementProtocol {
|
||||
receiveConfirm(s, theirPublicKey);
|
||||
sendConfirm(s, theirPublicKey);
|
||||
}
|
||||
return crypto.deriveKey(MASTER_SECRET_LABEL, s);
|
||||
return crypto.deriveKey(MASTER_KEY_LABEL, s);
|
||||
} catch (AbortException e) {
|
||||
sendAbort(e.getCause() != null);
|
||||
throw e;
|
||||
|
||||
@@ -114,9 +114,9 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
||||
keyAgreementCrypto, payloadEncoder, transport, remotePayload,
|
||||
localPayload, localKeyPair, alice);
|
||||
try {
|
||||
SecretKey master = protocol.perform();
|
||||
SecretKey masterKey = protocol.perform();
|
||||
KeyAgreementResult result =
|
||||
new KeyAgreementResult(master, transport.getConnection(),
|
||||
new KeyAgreementResult(masterKey, transport.getConnection(),
|
||||
transport.getTransportId(), alice);
|
||||
LOG.info("Finished BQP protocol");
|
||||
// Broadcast result to caller
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.briarproject.bramble.plugin;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
@@ -80,12 +80,10 @@ class Poller implements EventListener {
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof ContactStatusChangedEvent) {
|
||||
ContactStatusChangedEvent c = (ContactStatusChangedEvent) e;
|
||||
if (c.isActive()) {
|
||||
// Connect to the newly activated contact
|
||||
connectToContact(c.getContactId());
|
||||
}
|
||||
if (e instanceof ContactAddedEvent) {
|
||||
ContactAddedEvent c = (ContactAddedEvent) e;
|
||||
// Connect to the newly activated contact
|
||||
connectToContact(c.getContactId());
|
||||
} else if (e instanceof ConnectionClosedEvent) {
|
||||
ConnectionClosedEvent c = (ConnectionClosedEvent) e;
|
||||
// Reschedule polling, the polling interval may have decreased
|
||||
|
||||
@@ -38,8 +38,6 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
@@ -108,7 +106,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private final ResourceProvider resourceProvider;
|
||||
private final int maxLatency, maxIdleTime, socketTimeout;
|
||||
private final File torDirectory, torFile, geoIpFile, obfs4File, configFile;
|
||||
private final File doneFile, cookieFile, jtorCTLFile;
|
||||
private final File doneFile, cookieFile;
|
||||
private final ConnectionStatus connectionStatus;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
@@ -153,7 +151,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
configFile = new File(torDirectory, "torrc");
|
||||
doneFile = new File(torDirectory, "done");
|
||||
cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
|
||||
jtorCTLFile = new File(torDirectory, "jtorctl.out");
|
||||
connectionStatus = new ConnectionStatus();
|
||||
// Don't execute more than one connection status check at a time
|
||||
connectionStatusExecutor =
|
||||
@@ -208,19 +205,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
} catch (SecurityException | IOException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
// FIXME
|
||||
long torPid = getPid(torProcess);
|
||||
LOG.info("Tor PID: " + torPid);
|
||||
/*
|
||||
pb = new ProcessBuilder("/usr/bin/strace", "-ff", "-o", "strace.out",
|
||||
"-p", String.valueOf(torPid));
|
||||
try {
|
||||
pb.start();
|
||||
LOG.info("Started strace");
|
||||
} catch (SecurityException | IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
*/
|
||||
// Log the process's standard output until it detaches
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||
@@ -265,11 +249,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
controlSocket = new Socket("127.0.0.1", CONTROL_PORT);
|
||||
controlConnection = new TorControlConnection(controlSocket);
|
||||
controlConnection.authenticate(read(cookieFile));
|
||||
controlConnection.setConf("LOG", "debug file torlog");
|
||||
controlConnection.setDebugging(
|
||||
new PrintWriter(new FileOutputStream(jtorCTLFile), true));
|
||||
// FIXME Spam the control port with enable/disable network commands
|
||||
spamControlPort();
|
||||
// Tell Tor to exit when the control connection is closed
|
||||
controlConnection.takeOwnership();
|
||||
controlConnection.resetConf(Collections.singletonList(OWNER));
|
||||
@@ -293,38 +272,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
bind();
|
||||
}
|
||||
|
||||
// FIXME
|
||||
private long getPid(Process p) {
|
||||
long pid = -1;
|
||||
try {
|
||||
if (p.getClass().getName().equals("java.lang.UNIXProcess")) {
|
||||
Field f = p.getClass().getDeclaredField("pid");
|
||||
f.setAccessible(true);
|
||||
pid = f.getLong(p);
|
||||
f.setAccessible(false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logException(LOG, INFO, e);
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
// FIXME
|
||||
private void spamControlPort() {
|
||||
ioExecutor.execute(() -> {
|
||||
LOG.info("Spamming control port");
|
||||
try {
|
||||
//noinspection InfiniteLoopStatement
|
||||
for (boolean bridges = true; ; bridges = !bridges) {
|
||||
LOG.info("Enable bridges " + bridges);
|
||||
enableBridges(bridges, false);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean assetsAreUpToDate() {
|
||||
return doneFile.lastModified() > getLastUpdateTime();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
@@ -44,6 +46,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
private final IdentityManager identityManager;
|
||||
private final ClientVersioningManager clientVersioningManager;
|
||||
private final MetadataParser metadataParser;
|
||||
private final ContactGroupFactory contactGroupFactory;
|
||||
@@ -53,11 +56,13 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
@Inject
|
||||
TransportPropertyManagerImpl(DatabaseComponent db,
|
||||
ClientHelper clientHelper,
|
||||
IdentityManager identityManager,
|
||||
ClientVersioningManager clientVersioningManager,
|
||||
MetadataParser metadataParser,
|
||||
ContactGroupFactory contactGroupFactory, Clock clock) {
|
||||
this.db = db;
|
||||
this.clientHelper = clientHelper;
|
||||
this.identityManager = identityManager;
|
||||
this.clientVersioningManager = clientVersioningManager;
|
||||
this.metadataParser = metadataParser;
|
||||
this.contactGroupFactory = contactGroupFactory;
|
||||
@@ -77,7 +82,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
@Override
|
||||
public void addingContact(Transaction txn, Contact c) throws DbException {
|
||||
// Create a group to share with the contact
|
||||
Group g = getContactGroup(c);
|
||||
Group g = getContactGroup(c, getLocalAuthorId(txn));
|
||||
db.addGroup(txn, g);
|
||||
// Apply the client's visibility to the contact group
|
||||
Visibility client = clientVersioningManager.getClientVisibility(txn,
|
||||
@@ -93,14 +98,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
|
||||
@Override
|
||||
public void removingContact(Transaction txn, Contact c) throws DbException {
|
||||
db.removeGroup(txn, getContactGroup(c));
|
||||
db.removeGroup(txn, getContactGroup(c, getLocalAuthorId(txn)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClientVisibilityChanging(Transaction txn, Contact c,
|
||||
Visibility v) throws DbException {
|
||||
// Apply the client's visibility to the contact group
|
||||
Group g = getContactGroup(c);
|
||||
Group g = getContactGroup(c, getLocalAuthorId(txn));
|
||||
db.setGroupVisibility(txn, c.getId(), g.getId(), v);
|
||||
}
|
||||
|
||||
@@ -132,7 +137,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
@Override
|
||||
public void addRemoteProperties(Transaction txn, ContactId c,
|
||||
Map<TransportId, TransportProperties> props) throws DbException {
|
||||
Group g = getContactGroup(db.getContact(txn, c));
|
||||
Group g = getContactGroup(db.getContact(txn, c), getLocalAuthorId(txn));
|
||||
for (Entry<TransportId, TransportProperties> e : props.entrySet()) {
|
||||
storeMessage(txn, g.getId(), e.getKey(), e.getValue(), 0,
|
||||
false, false);
|
||||
@@ -191,17 +196,16 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
TransportId t) throws DbException {
|
||||
return db.transactionWithResult(true, txn -> {
|
||||
Map<ContactId, TransportProperties> remote = new HashMap<>();
|
||||
AuthorId local = getLocalAuthorId(txn);
|
||||
for (Contact c : db.getContacts(txn))
|
||||
remote.put(c.getId(), getRemoteProperties(txn, c, t));
|
||||
remote.put(c.getId(), getRemoteProperties(txn, c, local, t));
|
||||
return remote;
|
||||
});
|
||||
}
|
||||
|
||||
private TransportProperties getRemoteProperties(Transaction txn, Contact c,
|
||||
TransportId t) throws DbException {
|
||||
// Don't return properties for inactive contacts
|
||||
if (!c.isActive()) return new TransportProperties();
|
||||
Group g = getContactGroup(c);
|
||||
AuthorId local, TransportId t) throws DbException {
|
||||
Group g = getContactGroup(c, local);
|
||||
try {
|
||||
// Find the latest remote update
|
||||
LatestUpdate latest = findLatest(txn, g.getId(), t, false);
|
||||
@@ -218,8 +222,11 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
@Override
|
||||
public TransportProperties getRemoteProperties(ContactId c, TransportId t)
|
||||
throws DbException {
|
||||
return db.transactionWithResult(true, txn ->
|
||||
getRemoteProperties(txn, db.getContact(txn, c), t));
|
||||
return db.transactionWithResult(true, txn -> {
|
||||
Contact contact = db.getContact(txn ,c);
|
||||
AuthorId local = getLocalAuthorId(txn);
|
||||
return getRemoteProperties(txn, contact, local, t);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -252,8 +259,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
if (latest != null)
|
||||
db.removeMessage(txn, latest.messageId);
|
||||
// Store the merged properties in each contact's group
|
||||
AuthorId localAuthorId = getLocalAuthorId(txn);
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
Group g = getContactGroup(c);
|
||||
Group g = getContactGroup(c, localAuthorId);
|
||||
latest = findLatest(txn, g.getId(), t, true);
|
||||
version = latest == null ? 1 : latest.version + 1;
|
||||
storeMessage(txn, g.getId(), t, merged, version,
|
||||
@@ -269,9 +277,13 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
}
|
||||
}
|
||||
|
||||
private Group getContactGroup(Contact c) {
|
||||
private AuthorId getLocalAuthorId(Transaction txn) throws DbException {
|
||||
return identityManager.getLocalAuthor(txn).getId();
|
||||
}
|
||||
|
||||
private Group getContactGroup(Contact c, AuthorId local) {
|
||||
return contactGroupFactory.createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, c);
|
||||
MAJOR_VERSION, c, local);
|
||||
}
|
||||
|
||||
private void storeMessage(Transaction txn, GroupId g, TransportId t,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package org.briarproject.bramble.transport;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
@@ -19,8 +17,8 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.bramble.api.transport.KeyManager;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.StreamContext;
|
||||
import org.briarproject.bramble.api.transport.TransportKeySetId;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -46,7 +44,6 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
private final Executor dbExecutor;
|
||||
private final PluginConfig pluginConfig;
|
||||
private final TransportKeyManagerFactory transportKeyManagerFactory;
|
||||
private final Map<ContactId, Boolean> activeContacts;
|
||||
private final ConcurrentHashMap<TransportId, TransportKeyManager> managers;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
@@ -58,8 +55,6 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.pluginConfig = pluginConfig;
|
||||
this.transportKeyManagerFactory = transportKeyManagerFactory;
|
||||
// Use a ConcurrentHashMap as a thread-safe set
|
||||
activeContacts = new ConcurrentHashMap<>();
|
||||
managers = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
@@ -73,8 +68,6 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
transports.put(f.getId(), f.getMaxLatency());
|
||||
try {
|
||||
db.transaction(false, txn -> {
|
||||
for (Contact c : db.getContacts(txn))
|
||||
if (c.isActive()) activeContacts.put(c.getId(), true);
|
||||
for (Entry<TransportId, Integer> e : transports.entrySet())
|
||||
db.addTransport(txn, e.getKey(), e.getValue());
|
||||
for (Entry<TransportId, Integer> e : transports.entrySet()) {
|
||||
@@ -95,22 +88,22 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TransportId, KeySetId> addContact(Transaction txn, ContactId c,
|
||||
SecretKey master, long timestamp, boolean alice, boolean active)
|
||||
throws DbException {
|
||||
Map<TransportId, KeySetId> ids = new HashMap<>();
|
||||
public Map<TransportId, TransportKeySetId> addContact(Transaction txn,
|
||||
ContactId c, SecretKey rootKey, long timestamp, boolean alice,
|
||||
boolean active) throws DbException {
|
||||
Map<TransportId, TransportKeySetId> ids = new HashMap<>();
|
||||
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
|
||||
TransportId t = e.getKey();
|
||||
TransportKeyManager m = e.getValue();
|
||||
ids.put(t, m.addContact(txn, c, master, timestamp, alice, active));
|
||||
ids.put(t, m.addContact(txn, c, rootKey, timestamp, alice, active));
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activateKeys(Transaction txn, Map<TransportId, KeySetId> keys)
|
||||
throws DbException {
|
||||
for (Entry<TransportId, KeySetId> e : keys.entrySet()) {
|
||||
public void activateKeys(Transaction txn, Map<TransportId,
|
||||
TransportKeySetId> keys) throws DbException {
|
||||
for (Entry<TransportId, TransportKeySetId> e : keys.entrySet()) {
|
||||
TransportId t = e.getKey();
|
||||
TransportKeyManager m = managers.get(t);
|
||||
if (m == null) {
|
||||
@@ -130,8 +123,6 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
@Override
|
||||
public StreamContext getStreamContext(ContactId c, TransportId t)
|
||||
throws DbException {
|
||||
// Don't allow outgoing streams to inactive contacts
|
||||
if (!activeContacts.containsKey(c)) return null;
|
||||
TransportKeyManager m = managers.get(t);
|
||||
if (m == null) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
|
||||
@@ -157,15 +148,10 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof ContactRemovedEvent) {
|
||||
removeContact(((ContactRemovedEvent) e).getContactId());
|
||||
} else if (e instanceof ContactStatusChangedEvent) {
|
||||
ContactStatusChangedEvent c = (ContactStatusChangedEvent) e;
|
||||
if (c.isActive()) activeContacts.put(c.getContactId(), true);
|
||||
else activeContacts.remove(c.getContactId());
|
||||
}
|
||||
}
|
||||
|
||||
private void removeContact(ContactId c) {
|
||||
activeContacts.remove(c);
|
||||
dbExecutor.execute(() -> {
|
||||
for (TransportKeyManager m : managers.values()) m.removeContact(c);
|
||||
});
|
||||
|
||||
@@ -11,18 +11,18 @@ import javax.annotation.concurrent.NotThreadSafe;
|
||||
class MutableIncomingKeys {
|
||||
|
||||
private final SecretKey tagKey, headerKey;
|
||||
private final long rotationPeriod;
|
||||
private final long timePeriod;
|
||||
private final ReorderingWindow window;
|
||||
|
||||
MutableIncomingKeys(IncomingKeys in) {
|
||||
tagKey = in.getTagKey();
|
||||
headerKey = in.getHeaderKey();
|
||||
rotationPeriod = in.getRotationPeriod();
|
||||
timePeriod = in.getTimePeriod();
|
||||
window = new ReorderingWindow(in.getWindowBase(), in.getWindowBitmap());
|
||||
}
|
||||
|
||||
IncomingKeys snapshot() {
|
||||
return new IncomingKeys(tagKey, headerKey, rotationPeriod,
|
||||
return new IncomingKeys(tagKey, headerKey, timePeriod,
|
||||
window.getBase(), window.getBitmap());
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ class MutableIncomingKeys {
|
||||
return headerKey;
|
||||
}
|
||||
|
||||
long getRotationPeriod() {
|
||||
return rotationPeriod;
|
||||
long getTimePeriod() {
|
||||
return timePeriod;
|
||||
}
|
||||
|
||||
ReorderingWindow getWindow() {
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package org.briarproject.bramble.transport;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.TransportKeySetId;
|
||||
|
||||
class MutableKeySet {
|
||||
|
||||
private final KeySetId keySetId;
|
||||
private final TransportKeySetId keySetId;
|
||||
private final ContactId contactId;
|
||||
private final MutableTransportKeys transportKeys;
|
||||
|
||||
MutableKeySet(KeySetId keySetId, ContactId contactId,
|
||||
MutableKeySet(TransportKeySetId keySetId, ContactId contactId,
|
||||
MutableTransportKeys transportKeys) {
|
||||
this.keySetId = keySetId;
|
||||
this.contactId = contactId;
|
||||
this.transportKeys = transportKeys;
|
||||
}
|
||||
|
||||
KeySetId getKeySetId() {
|
||||
TransportKeySetId getKeySetId() {
|
||||
return keySetId;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,20 +11,20 @@ import javax.annotation.concurrent.NotThreadSafe;
|
||||
class MutableOutgoingKeys {
|
||||
|
||||
private final SecretKey tagKey, headerKey;
|
||||
private final long rotationPeriod;
|
||||
private final long timePeriod;
|
||||
private long streamCounter;
|
||||
private boolean active;
|
||||
|
||||
MutableOutgoingKeys(OutgoingKeys out) {
|
||||
tagKey = out.getTagKey();
|
||||
headerKey = out.getHeaderKey();
|
||||
rotationPeriod = out.getRotationPeriod();
|
||||
timePeriod = out.getTimePeriod();
|
||||
streamCounter = out.getStreamCounter();
|
||||
active = out.isActive();
|
||||
}
|
||||
|
||||
OutgoingKeys snapshot() {
|
||||
return new OutgoingKeys(tagKey, headerKey, rotationPeriod,
|
||||
return new OutgoingKeys(tagKey, headerKey, timePeriod,
|
||||
streamCounter, active);
|
||||
}
|
||||
|
||||
@@ -36,8 +36,8 @@ class MutableOutgoingKeys {
|
||||
return headerKey;
|
||||
}
|
||||
|
||||
long getRotationPeriod() {
|
||||
return rotationPeriod;
|
||||
long getTimePeriod() {
|
||||
return timePeriod;
|
||||
}
|
||||
|
||||
long getStreamCounter() {
|
||||
|
||||
@@ -5,8 +5,8 @@ import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.StreamContext;
|
||||
import org.briarproject.bramble.api.transport.TransportKeySetId;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -15,10 +15,11 @@ interface TransportKeyManager {
|
||||
|
||||
void start(Transaction txn) throws DbException;
|
||||
|
||||
KeySetId addContact(Transaction txn, ContactId c, SecretKey master,
|
||||
long timestamp, boolean alice, boolean active) throws DbException;
|
||||
TransportKeySetId addContact(Transaction txn, ContactId c,
|
||||
SecretKey rootKey, long timestamp, boolean alice, boolean active)
|
||||
throws DbException;
|
||||
|
||||
void activateKeys(Transaction txn, KeySetId k) throws DbException;
|
||||
void activateKeys(Transaction txn, TransportKeySetId k) throws DbException;
|
||||
|
||||
void removeContact(ContactId c);
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.Scheduler;
|
||||
import org.briarproject.bramble.api.transport.KeySet;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.StreamContext;
|
||||
import org.briarproject.bramble.api.transport.TransportKeySet;
|
||||
import org.briarproject.bramble.api.transport.TransportKeySetId;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
import org.briarproject.bramble.transport.ReorderingWindow.Change;
|
||||
|
||||
@@ -51,12 +51,12 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final Clock clock;
|
||||
private final TransportId transportId;
|
||||
private final long rotationPeriodLength;
|
||||
private final long timePeriodLength;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
// The following are locking: lock
|
||||
private final Map<KeySetId, MutableKeySet> keys = new HashMap<>();
|
||||
private final Map<TransportKeySetId, MutableKeySet> keys = new HashMap<>();
|
||||
private final Map<Bytes, TagContext> inContexts = new HashMap<>();
|
||||
private final Map<ContactId, MutableKeySet> outContexts = new HashMap<>();
|
||||
|
||||
@@ -70,7 +70,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
this.scheduler = scheduler;
|
||||
this.clock = clock;
|
||||
this.transportId = transportId;
|
||||
rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE;
|
||||
timePeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,8 +80,9 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
lock.lock();
|
||||
try {
|
||||
// Load the transport keys from the DB
|
||||
Collection<KeySet> loaded = db.getTransportKeys(txn, transportId);
|
||||
// Rotate the keys to the current rotation period
|
||||
Collection<TransportKeySet> loaded =
|
||||
db.getTransportKeys(txn, transportId);
|
||||
// Rotate the keys to the current time period
|
||||
RotationResult rotationResult = rotateKeys(loaded, now);
|
||||
// Initialise mutable state for all contacts
|
||||
addKeys(rotationResult.current);
|
||||
@@ -95,15 +96,17 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
scheduleKeyRotation(now);
|
||||
}
|
||||
|
||||
private RotationResult rotateKeys(Collection<KeySet> keys, long now) {
|
||||
private RotationResult rotateKeys(Collection<TransportKeySet> keys,
|
||||
long now) {
|
||||
RotationResult rotationResult = new RotationResult();
|
||||
long rotationPeriod = now / rotationPeriodLength;
|
||||
for (KeySet ks : keys) {
|
||||
TransportKeys k = ks.getTransportKeys();
|
||||
TransportKeys k1 =
|
||||
transportCrypto.rotateTransportKeys(k, rotationPeriod);
|
||||
KeySet ks1 = new KeySet(ks.getKeySetId(), ks.getContactId(), k1);
|
||||
if (k1.getRotationPeriod() > k.getRotationPeriod())
|
||||
long timePeriod = now / timePeriodLength;
|
||||
for (TransportKeySet ks : keys) {
|
||||
TransportKeys k = ks.getKeys();
|
||||
TransportKeys k1 = transportCrypto.rotateTransportKeys(k,
|
||||
timePeriod);
|
||||
TransportKeySet ks1 = new TransportKeySet(ks.getKeySetId(),
|
||||
ks.getContactId(), k1);
|
||||
if (k1.getTimePeriod() > k.getTimePeriod())
|
||||
rotationResult.rotated.add(ks1);
|
||||
rotationResult.current.add(ks1);
|
||||
}
|
||||
@@ -111,15 +114,15 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
}
|
||||
|
||||
// Locking: lock
|
||||
private void addKeys(Collection<KeySet> keys) {
|
||||
for (KeySet ks : keys) {
|
||||
private void addKeys(Collection<TransportKeySet> keys) {
|
||||
for (TransportKeySet ks : keys) {
|
||||
addKeys(ks.getKeySetId(), ks.getContactId(),
|
||||
new MutableTransportKeys(ks.getTransportKeys()));
|
||||
new MutableTransportKeys(ks.getKeys()));
|
||||
}
|
||||
}
|
||||
|
||||
// Locking: lock
|
||||
private void addKeys(KeySetId keySetId, ContactId contactId,
|
||||
private void addKeys(TransportKeySetId keySetId, ContactId contactId,
|
||||
MutableTransportKeys m) {
|
||||
MutableKeySet ks = new MutableKeySet(keySetId, contactId, m);
|
||||
keys.put(keySetId, ks);
|
||||
@@ -130,7 +133,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
}
|
||||
|
||||
// Locking: lock
|
||||
private void encodeTags(KeySetId keySetId, ContactId contactId,
|
||||
private void encodeTags(TransportKeySetId keySetId, ContactId contactId,
|
||||
MutableIncomingKeys inKeys) {
|
||||
for (long streamNumber : inKeys.getWindow().getUnseen()) {
|
||||
TagContext tagCtx =
|
||||
@@ -155,7 +158,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
}
|
||||
|
||||
private void scheduleKeyRotation(long now) {
|
||||
long delay = rotationPeriodLength - now % rotationPeriodLength;
|
||||
long delay = timePeriodLength - now % timePeriodLength;
|
||||
scheduler.schedule((Runnable) this::rotateKeys, delay, MILLISECONDS);
|
||||
}
|
||||
|
||||
@@ -170,20 +173,21 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeySetId addContact(Transaction txn, ContactId c, SecretKey master,
|
||||
long timestamp, boolean alice, boolean active) throws DbException {
|
||||
public TransportKeySetId addContact(Transaction txn, ContactId c,
|
||||
SecretKey rootKey, long timestamp, boolean alice, boolean active)
|
||||
throws DbException {
|
||||
lock.lock();
|
||||
try {
|
||||
// Work out what rotation period the timestamp belongs to
|
||||
long rotationPeriod = timestamp / rotationPeriodLength;
|
||||
// Work out what time period the timestamp belongs to
|
||||
long timePeriod = timestamp / timePeriodLength;
|
||||
// Derive the transport keys
|
||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, rotationPeriod, alice, active);
|
||||
// Rotate the keys to the current rotation period if necessary
|
||||
rotationPeriod = clock.currentTimeMillis() / rotationPeriodLength;
|
||||
k = transportCrypto.rotateTransportKeys(k, rotationPeriod);
|
||||
rootKey, timePeriod, alice, active);
|
||||
// Rotate the keys to the current time period if necessary
|
||||
timePeriod = clock.currentTimeMillis() / timePeriodLength;
|
||||
k = transportCrypto.rotateTransportKeys(k, timePeriod);
|
||||
// Write the keys back to the DB
|
||||
KeySetId keySetId = db.addTransportKeys(txn, c, k);
|
||||
TransportKeySetId keySetId = db.addTransportKeys(txn, c, k);
|
||||
// Initialise mutable state for the contact
|
||||
addKeys(keySetId, c, new MutableTransportKeys(k));
|
||||
return keySetId;
|
||||
@@ -193,7 +197,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activateKeys(Transaction txn, KeySetId k) throws DbException {
|
||||
public void activateKeys(Transaction txn, TransportKeySetId k)
|
||||
throws DbException {
|
||||
lock.lock();
|
||||
try {
|
||||
MutableKeySet ks = keys.get(k);
|
||||
@@ -300,7 +305,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
}
|
||||
// Write the window back to the DB
|
||||
db.setReorderingWindow(txn, tagCtx.keySetId, transportId,
|
||||
inKeys.getRotationPeriod(), window.getBase(),
|
||||
inKeys.getTimePeriod(), window.getBase(),
|
||||
window.getBitmap());
|
||||
// If the outgoing keys are inactive, activate them
|
||||
MutableKeySet ks = keys.get(tagCtx.keySetId);
|
||||
@@ -322,11 +327,11 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
long now = clock.currentTimeMillis();
|
||||
lock.lock();
|
||||
try {
|
||||
// Rotate the keys to the current rotation period
|
||||
Collection<KeySet> snapshot = new ArrayList<>(keys.size());
|
||||
// Rotate the keys to the current time period
|
||||
Collection<TransportKeySet> snapshot = new ArrayList<>(keys.size());
|
||||
for (MutableKeySet ks : keys.values()) {
|
||||
snapshot.add(new KeySet(ks.getKeySetId(), ks.getContactId(),
|
||||
ks.getTransportKeys().snapshot()));
|
||||
snapshot.add(new TransportKeySet(ks.getKeySetId(),
|
||||
ks.getContactId(), ks.getTransportKeys().snapshot()));
|
||||
}
|
||||
RotationResult rotationResult = rotateKeys(snapshot, now);
|
||||
// Rebuild the mutable state for all contacts
|
||||
@@ -346,12 +351,12 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
|
||||
private static class TagContext {
|
||||
|
||||
private final KeySetId keySetId;
|
||||
private final TransportKeySetId keySetId;
|
||||
private final ContactId contactId;
|
||||
private final MutableIncomingKeys inKeys;
|
||||
private final long streamNumber;
|
||||
|
||||
private TagContext(KeySetId keySetId, ContactId contactId,
|
||||
private TagContext(TransportKeySetId keySetId, ContactId contactId,
|
||||
MutableIncomingKeys inKeys, long streamNumber) {
|
||||
this.keySetId = keySetId;
|
||||
this.contactId = contactId;
|
||||
@@ -362,7 +367,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
|
||||
private static class RotationResult {
|
||||
|
||||
private final Collection<KeySet> current = new ArrayList<>();
|
||||
private final Collection<KeySet> rotated = new ArrayList<>();
|
||||
private final Collection<TransportKeySet> current = new ArrayList<>();
|
||||
private final Collection<TransportKeySet> rotated = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.lifecycle.Service;
|
||||
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
@@ -58,6 +60,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
private final IdentityManager identityManager;
|
||||
private final ContactGroupFactory contactGroupFactory;
|
||||
private final Clock clock;
|
||||
private final Group localGroup;
|
||||
@@ -68,9 +71,11 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
|
||||
|
||||
@Inject
|
||||
ClientVersioningManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||
IdentityManager identityManager,
|
||||
ContactGroupFactory contactGroupFactory, Clock clock) {
|
||||
this.db = db;
|
||||
this.clientHelper = clientHelper;
|
||||
this.identityManager = identityManager;
|
||||
this.contactGroupFactory = contactGroupFactory;
|
||||
this.clock = clock;
|
||||
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
||||
@@ -154,7 +159,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
|
||||
@Override
|
||||
public void addingContact(Transaction txn, Contact c) throws DbException {
|
||||
// Create a group and share it with the contact
|
||||
Group g = getContactGroup(c);
|
||||
Group g = getContactGroup(c, getLocalAuthorId(txn));
|
||||
db.addGroup(txn, g);
|
||||
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
|
||||
// Attach the contact ID to the group
|
||||
@@ -173,7 +178,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
|
||||
|
||||
@Override
|
||||
public void removingContact(Transaction txn, Contact c) throws DbException {
|
||||
db.removeGroup(txn, getContactGroup(c));
|
||||
db.removeGroup(txn, getContactGroup(c, getLocalAuthorId(txn)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -308,7 +313,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
|
||||
List<ClientVersion> versions) throws DbException {
|
||||
try {
|
||||
// Find the latest local and remote updates
|
||||
Group g = getContactGroup(c);
|
||||
Group g = getContactGroup(c, getLocalAuthorId(txn));
|
||||
LatestUpdates latest = findLatestUpdates(txn, g.getId());
|
||||
// Load and parse the latest local update
|
||||
if (latest.local == null) throw new DbException();
|
||||
@@ -344,16 +349,20 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
|
||||
}
|
||||
}
|
||||
|
||||
private Group getContactGroup(Contact c) {
|
||||
private AuthorId getLocalAuthorId(Transaction txn) throws DbException {
|
||||
return identityManager.getLocalAuthor(txn).getId();
|
||||
}
|
||||
|
||||
private Group getContactGroup(Contact c, AuthorId local) {
|
||||
return contactGroupFactory.createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, c);
|
||||
MAJOR_VERSION, c, local);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private LatestUpdates findLatestUpdates(Transaction txn, ContactId c)
|
||||
throws DbException, FormatException {
|
||||
Contact contact = db.getContact(txn, c);
|
||||
Group g = getContactGroup(contact);
|
||||
Group g = getContactGroup(contact, getLocalAuthorId(txn));
|
||||
// Contact may be in the process of being added or removed, so
|
||||
// contact group may not exist
|
||||
if (!db.containsGroup(txn, g.getId())) return null;
|
||||
|
||||
@@ -5,27 +5,20 @@ import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.transport.KeyManager;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Random;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
|
||||
@@ -33,8 +26,8 @@ import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -49,42 +42,40 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
private final IdentityManager identityManager =
|
||||
context.mock(IdentityManager.class);
|
||||
private final ContactManager contactManager;
|
||||
private final ContactId contactId = new ContactId(42);
|
||||
private final Author remote = getAuthor();
|
||||
private final AuthorId local = new AuthorId(getRandomId());
|
||||
private final Author author = getAuthor();
|
||||
private final LocalAuthor localAuthor = getLocalAuthor();
|
||||
private final String alias = getRandomString(MAX_AUTHOR_NAME_LENGTH);
|
||||
private final boolean verified = false, active = true;
|
||||
private final Contact contact =
|
||||
new Contact(contactId, remote, local, alias, verified, active);
|
||||
private final Contact contact = getContact(author, verified);
|
||||
private final ContactId contactId = contact.getId();
|
||||
|
||||
public ContactManagerImplTest() {
|
||||
contactManager = new ContactManagerImpl(db, keyManager, identityManager);
|
||||
contactManager =
|
||||
new ContactManagerImpl(db, keyManager, identityManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddContact() throws Exception {
|
||||
SecretKey master = getSecretKey();
|
||||
SecretKey rootKey = getSecretKey();
|
||||
long timestamp = System.currentTimeMillis();
|
||||
boolean alice = new Random().nextBoolean();
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(false), withDbCallable(txn));
|
||||
oneOf(db).addContact(txn, remote, local, verified, active);
|
||||
oneOf(db).addContact(txn, author, verified);
|
||||
will(returnValue(contactId));
|
||||
oneOf(keyManager).addContact(txn, contactId, master, timestamp,
|
||||
oneOf(keyManager).addContact(txn, contactId, rootKey, timestamp,
|
||||
alice, active);
|
||||
oneOf(db).getContact(txn, contactId);
|
||||
will(returnValue(contact));
|
||||
}});
|
||||
|
||||
assertEquals(contactId, contactManager.addContact(remote, local,
|
||||
master, timestamp, alice, verified, active));
|
||||
assertEquals(contactId, contactManager.addContact(author, rootKey,
|
||||
timestamp, alice, verified, active));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetContact() throws Exception {
|
||||
public void testGetContactByContactId() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||
@@ -96,49 +87,20 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetContactByAuthor() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
Collection<Contact> contacts = Collections.singleton(contact);
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||
oneOf(db).getContactsByAuthorId(txn, remote.getId());
|
||||
will(returnValue(contacts));
|
||||
}});
|
||||
|
||||
assertEquals(contact, contactManager.getContact(remote.getId(), local));
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchContactException.class)
|
||||
public void testGetContactByUnknownAuthor() throws Exception {
|
||||
public void testGetContactByAuthorId() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||
oneOf(db).getContactsByAuthorId(txn, remote.getId());
|
||||
will(returnValue(emptyList()));
|
||||
oneOf(db).getContact(txn, author.getId());
|
||||
will(returnValue(contact));
|
||||
}});
|
||||
|
||||
contactManager.getContact(remote.getId(), local);
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchContactException.class)
|
||||
public void testGetContactByUnknownLocalAuthor() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
Collection<Contact> contacts = Collections.singleton(contact);
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||
oneOf(db).getContactsByAuthorId(txn, remote.getId());
|
||||
will(returnValue(contacts));
|
||||
}});
|
||||
|
||||
contactManager.getContact(remote.getId(), new AuthorId(getRandomId()));
|
||||
assertEquals(contact, contactManager.getContact(author.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetActiveContacts() throws Exception {
|
||||
Collection<Contact> activeContacts = Collections.singletonList(contact);
|
||||
Collection<Contact> contacts = new ArrayList<>(activeContacts);
|
||||
contacts.add(new Contact(new ContactId(3), remote, local, alias, true,
|
||||
false));
|
||||
public void testGetContacts() throws Exception {
|
||||
Collection<Contact> contacts = singletonList(contact);
|
||||
Transaction txn = new Transaction(null, true);
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||
@@ -146,7 +108,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(contacts));
|
||||
}});
|
||||
|
||||
assertEquals(activeContacts, contactManager.getActiveContacts());
|
||||
assertEquals(contacts, contactManager.getContacts());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -162,19 +124,11 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
contactManager.removeContact(contactId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetContactActive() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).setContactActive(txn, contactId, active);
|
||||
}});
|
||||
|
||||
contactManager.setContactActive(txn, contactId, active);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetContactAlias() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
String alias = getRandomString(MAX_AUTHOR_NAME_LENGTH);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||
oneOf(db).setContactAlias(txn, contactId, alias);
|
||||
@@ -195,85 +149,82 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||
oneOf(db).containsContact(txn, remote.getId(), local);
|
||||
oneOf(db).containsContact(txn, author.getId());
|
||||
will(returnValue(true));
|
||||
}});
|
||||
|
||||
assertTrue(contactManager.contactExists(remote.getId(), local));
|
||||
assertTrue(contactManager.contactExists(author.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAuthorInfo() throws Exception {
|
||||
public void testGetAuthorInfoOurselves() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
Collection<Contact> contacts = singletonList(
|
||||
new Contact(new ContactId(1), remote, localAuthor.getId(),
|
||||
alias, false, true));
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||
oneOf(identityManager).getLocalAuthor(txn);
|
||||
will(returnValue(localAuthor));
|
||||
oneOf(db).getContactsByAuthorId(txn, remote.getId());
|
||||
will(returnValue(contacts));
|
||||
}});
|
||||
AuthorInfo authorInfo =
|
||||
contactManager.getAuthorInfo(txn, remote.getId());
|
||||
assertEquals(UNVERIFIED, authorInfo.getStatus());
|
||||
assertEquals(alias, contact.getAlias());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAuthorInfoTransaction() throws DbException {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
|
||||
// check unknown author
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(identityManager).getLocalAuthor(txn);
|
||||
will(returnValue(localAuthor));
|
||||
oneOf(db).getContactsByAuthorId(txn, remote.getId());
|
||||
will(returnValue(emptyList()));
|
||||
}});
|
||||
AuthorInfo authorInfo =
|
||||
contactManager.getAuthorInfo(txn, remote.getId());
|
||||
assertEquals(UNKNOWN, authorInfo.getStatus());
|
||||
assertNull(authorInfo.getAlias());
|
||||
|
||||
// check unverified contact
|
||||
Collection<Contact> contacts = singletonList(
|
||||
new Contact(new ContactId(1), remote, localAuthor.getId(),
|
||||
alias, false, true));
|
||||
checkAuthorInfoContext(txn, remote.getId(), contacts);
|
||||
authorInfo = contactManager.getAuthorInfo(txn, remote.getId());
|
||||
assertEquals(UNVERIFIED, authorInfo.getStatus());
|
||||
assertEquals(alias, contact.getAlias());
|
||||
|
||||
// check verified contact
|
||||
contacts = singletonList(new Contact(new ContactId(1), remote,
|
||||
localAuthor.getId(), alias, true, true));
|
||||
checkAuthorInfoContext(txn, remote.getId(), contacts);
|
||||
authorInfo = contactManager.getAuthorInfo(txn, remote.getId());
|
||||
assertEquals(VERIFIED, authorInfo.getStatus());
|
||||
assertEquals(alias, contact.getAlias());
|
||||
|
||||
// check ourselves
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(identityManager).getLocalAuthor(txn);
|
||||
will(returnValue(localAuthor));
|
||||
never(db).getContactsByAuthorId(txn, remote.getId());
|
||||
}});
|
||||
authorInfo = contactManager.getAuthorInfo(txn, localAuthor.getId());
|
||||
contactManager.getAuthorInfo(txn, localAuthor.getId());
|
||||
assertEquals(OURSELVES, authorInfo.getStatus());
|
||||
assertNull(authorInfo.getAlias());
|
||||
}
|
||||
|
||||
private void checkAuthorInfoContext(Transaction txn, AuthorId authorId,
|
||||
Collection<Contact> contacts) throws DbException {
|
||||
context.checking(new Expectations() {{
|
||||
@Test
|
||||
public void testGetAuthorInfoVerified() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
Contact verified = getContact(author, true);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||
oneOf(identityManager).getLocalAuthor(txn);
|
||||
will(returnValue(localAuthor));
|
||||
oneOf(db).getContactsByAuthorId(txn, authorId);
|
||||
will(returnValue(contacts));
|
||||
oneOf(db).containsContact(txn, author.getId());
|
||||
will(returnValue(true));
|
||||
oneOf(db).getContact(txn, author.getId());
|
||||
will(returnValue(verified));
|
||||
}});
|
||||
AuthorInfo authorInfo =
|
||||
contactManager.getAuthorInfo(txn, author.getId());
|
||||
assertEquals(VERIFIED, authorInfo.getStatus());
|
||||
assertEquals(verified.getAlias(), authorInfo.getAlias());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAuthorInfoUnverified() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
Contact unverified = getContact(author, false);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||
oneOf(identityManager).getLocalAuthor(txn);
|
||||
will(returnValue(localAuthor));
|
||||
oneOf(db).containsContact(txn, author.getId());
|
||||
will(returnValue(true));
|
||||
oneOf(db).getContact(txn, author.getId());
|
||||
will(returnValue(unverified));
|
||||
}});
|
||||
AuthorInfo authorInfo =
|
||||
contactManager.getAuthorInfo(txn, author.getId());
|
||||
assertEquals(UNVERIFIED, authorInfo.getStatus());
|
||||
assertEquals(unverified.getAlias(), authorInfo.getAlias());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAuthorInfoUnknown() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||
oneOf(identityManager).getLocalAuthor(txn);
|
||||
will(returnValue(localAuthor));
|
||||
oneOf(db).containsContact(txn, author.getId());
|
||||
will(returnValue(false));
|
||||
}});
|
||||
AuthorInfo authorInfo =
|
||||
contactManager.getAuthorInfo(txn, author.getId());
|
||||
assertEquals(UNKNOWN, authorInfo.getStatus());
|
||||
assertNull(authorInfo.getAlias());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.transport.HandshakeKeys;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.briarproject.bramble.crypto.KeyDerivationTestUtils.assertAllDifferent;
|
||||
import static org.briarproject.bramble.crypto.KeyDerivationTestUtils.assertMatches;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
public class HandshakeKeyDerivationTest extends BrambleTestCase {
|
||||
|
||||
private final CryptoComponent crypto =
|
||||
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
|
||||
private final TransportCrypto transportCrypto =
|
||||
new TransportCryptoImpl(crypto);
|
||||
private final TransportId transportId = getTransportId();
|
||||
private final SecretKey rootKey = getSecretKey();
|
||||
|
||||
@Test
|
||||
public void testKeysAreDistinct() {
|
||||
HandshakeKeys kA = transportCrypto.deriveHandshakeKeys(transportId,
|
||||
rootKey, 123, true);
|
||||
HandshakeKeys kB = transportCrypto.deriveHandshakeKeys(transportId,
|
||||
rootKey, 123, false);
|
||||
assertAllDifferent(kA);
|
||||
assertAllDifferent(kB);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeysAreNotUpdatedToPreviousPeriod() {
|
||||
HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId,
|
||||
rootKey, 123, true);
|
||||
HandshakeKeys k1 = transportCrypto.updateHandshakeKeys(k, 122);
|
||||
assertSame(k, k1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeysAreNotUpdatedToCurrentPeriod() {
|
||||
HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId,
|
||||
rootKey, 123, true);
|
||||
HandshakeKeys k1 = transportCrypto.updateHandshakeKeys(k, 123);
|
||||
assertSame(k, k1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeysAreUpdatedByOnePeriod() {
|
||||
HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId,
|
||||
rootKey, 123, true);
|
||||
HandshakeKeys k1 = transportCrypto.updateHandshakeKeys(k, 124);
|
||||
assertSame(k.getCurrentIncomingKeys(), k1.getPreviousIncomingKeys());
|
||||
assertSame(k.getNextIncomingKeys(), k1.getCurrentIncomingKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeysAreUpdatedByTwoPeriods() {
|
||||
HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId,
|
||||
rootKey, 123, true);
|
||||
HandshakeKeys k1 = transportCrypto.updateHandshakeKeys(k, 125);
|
||||
assertSame(k.getNextIncomingKeys(), k1.getPreviousIncomingKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeysAreUpdatedByThreePeriods() {
|
||||
HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId,
|
||||
rootKey, 123, true);
|
||||
HandshakeKeys k1 = transportCrypto.updateHandshakeKeys(k, 126);
|
||||
assertAllDifferent(k, k1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCurrentKeysMatchContact() {
|
||||
// Start in time period 123
|
||||
HandshakeKeys kA = transportCrypto.deriveHandshakeKeys(transportId,
|
||||
rootKey, 123, true);
|
||||
HandshakeKeys kB = transportCrypto.deriveHandshakeKeys(transportId,
|
||||
rootKey, 123, false);
|
||||
// Alice's incoming keys should equal Bob's outgoing keys
|
||||
assertMatches(kA.getCurrentIncomingKeys(), kB.getCurrentOutgoingKeys());
|
||||
// Bob's incoming keys should equal Alice's outgoing keys
|
||||
assertMatches(kB.getCurrentIncomingKeys(), kA.getCurrentOutgoingKeys());
|
||||
// Update into the future
|
||||
kA = transportCrypto.updateHandshakeKeys(kA, 456);
|
||||
kB = transportCrypto.updateHandshakeKeys(kB, 456);
|
||||
// Alice's incoming keys should equal Bob's outgoing keys
|
||||
assertMatches(kA.getCurrentIncomingKeys(), kB.getCurrentOutgoingKeys());
|
||||
// Bob's incoming keys should equal Alice's outgoing keys
|
||||
assertMatches(kB.getCurrentIncomingKeys(), kA.getCurrentOutgoingKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreviousKeysMatchContact() {
|
||||
// Start in time period 123
|
||||
HandshakeKeys kA = transportCrypto.deriveHandshakeKeys(transportId,
|
||||
rootKey, 123, true);
|
||||
HandshakeKeys kB = transportCrypto.deriveHandshakeKeys(transportId,
|
||||
rootKey, 123, false);
|
||||
// Compare Alice's previous keys in period 456 with Bob's current keys
|
||||
// in period 455
|
||||
kA = transportCrypto.updateHandshakeKeys(kA, 456);
|
||||
kB = transportCrypto.updateHandshakeKeys(kB, 455);
|
||||
// Alice's previous incoming keys should equal Bob's current
|
||||
// outgoing keys
|
||||
assertMatches(kA.getPreviousIncomingKeys(),
|
||||
kB.getCurrentOutgoingKeys());
|
||||
// Compare Alice's current keys in period 456 with Bob's previous keys
|
||||
// in period 457
|
||||
kB = transportCrypto.updateHandshakeKeys(kB, 457);
|
||||
// Bob's previous incoming keys should equal Alice's current
|
||||
// outgoing keys
|
||||
assertMatches(kB.getPreviousIncomingKeys(),
|
||||
kA.getCurrentOutgoingKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextKeysMatchContact() {
|
||||
// Start in time period 123
|
||||
HandshakeKeys kA = transportCrypto.deriveHandshakeKeys(transportId,
|
||||
rootKey, 123, true);
|
||||
HandshakeKeys kB = transportCrypto.deriveHandshakeKeys(transportId,
|
||||
rootKey, 123, false);
|
||||
// Compare Alice's current keys in period 456 with Bob's next keys in
|
||||
// period 455
|
||||
kA = transportCrypto.updateHandshakeKeys(kA, 456);
|
||||
kB = transportCrypto.updateHandshakeKeys(kB, 455);
|
||||
// Bob's next incoming keys should equal Alice's current outgoing keys
|
||||
assertMatches(kB.getNextIncomingKeys(), kA.getCurrentOutgoingKeys());
|
||||
// Compare Alice's next keys in period 456 with Bob's current keys
|
||||
// in period 457
|
||||
kB = transportCrypto.updateHandshakeKeys(kB, 457);
|
||||
// Alice's next incoming keys should equal Bob's current outgoing keys
|
||||
assertMatches(kA.getNextIncomingKeys(), kB.getCurrentOutgoingKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRootKeyAffectsOutput() {
|
||||
SecretKey rootKey1 = getSecretKey();
|
||||
assertFalse(Arrays.equals(rootKey.getBytes(), rootKey1.getBytes()));
|
||||
HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId,
|
||||
rootKey, 123, true);
|
||||
HandshakeKeys k1 = transportCrypto.deriveHandshakeKeys(transportId,
|
||||
rootKey1, 123, true);
|
||||
assertAllDifferent(k, k1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportIdAffectsOutput() {
|
||||
TransportId transportId1 = getTransportId();
|
||||
assertNotEquals(transportId.getString(), transportId1.getString());
|
||||
HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId,
|
||||
rootKey, 123, true);
|
||||
HandshakeKeys k1 = transportCrypto.deriveHandshakeKeys(transportId1,
|
||||
rootKey, 123, true);
|
||||
assertAllDifferent(k, k1);
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class KeyDerivationTest extends BrambleTestCase {
|
||||
|
||||
private final CryptoComponent crypto =
|
||||
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
|
||||
private final TransportCrypto transportCrypto =
|
||||
new TransportCryptoImpl(crypto);
|
||||
private final TransportId transportId = getTransportId();
|
||||
private final SecretKey master = getSecretKey();
|
||||
|
||||
@Test
|
||||
public void testKeysAreDistinct() {
|
||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, true, true);
|
||||
assertAllDifferent(k);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCurrentKeysMatchCurrentKeysOfContact() {
|
||||
// Start in rotation period 123
|
||||
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, true, true);
|
||||
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, false, true);
|
||||
// Alice's incoming keys should equal Bob's outgoing keys
|
||||
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
|
||||
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||
assertArrayEquals(kA.getCurrentIncomingKeys().getHeaderKey().getBytes(),
|
||||
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
|
||||
// Alice's outgoing keys should equal Bob's incoming keys
|
||||
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
|
||||
kB.getCurrentIncomingKeys().getTagKey().getBytes());
|
||||
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
|
||||
kB.getCurrentIncomingKeys().getHeaderKey().getBytes());
|
||||
// Rotate into the future
|
||||
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
||||
kB = transportCrypto.rotateTransportKeys(kB, 456);
|
||||
// Alice's incoming keys should equal Bob's outgoing keys
|
||||
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
|
||||
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||
assertArrayEquals(kA.getCurrentIncomingKeys().getHeaderKey().getBytes(),
|
||||
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
|
||||
// Alice's outgoing keys should equal Bob's incoming keys
|
||||
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
|
||||
kB.getCurrentIncomingKeys().getTagKey().getBytes());
|
||||
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
|
||||
kB.getCurrentIncomingKeys().getHeaderKey().getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreviousKeysMatchPreviousKeysOfContact() {
|
||||
// Start in rotation period 123
|
||||
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, true, true);
|
||||
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, false, true);
|
||||
// Compare Alice's previous keys in period 456 with Bob's current keys
|
||||
// in period 455
|
||||
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
||||
kB = transportCrypto.rotateTransportKeys(kB, 455);
|
||||
// Alice's previous incoming keys should equal Bob's outgoing keys
|
||||
assertArrayEquals(kA.getPreviousIncomingKeys().getTagKey().getBytes(),
|
||||
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||
assertArrayEquals(
|
||||
kA.getPreviousIncomingKeys().getHeaderKey().getBytes(),
|
||||
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
|
||||
// Compare Alice's current keys in period 456 with Bob's previous keys
|
||||
// in period 457
|
||||
kB = transportCrypto.rotateTransportKeys(kB, 457);
|
||||
// Alice's outgoing keys should equal Bob's previous incoming keys
|
||||
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
|
||||
kB.getPreviousIncomingKeys().getTagKey().getBytes());
|
||||
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
|
||||
kB.getPreviousIncomingKeys().getHeaderKey().getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextKeysMatchNextKeysOfContact() {
|
||||
// Start in rotation period 123
|
||||
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, true, true);
|
||||
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, false, true);
|
||||
// Compare Alice's current keys in period 456 with Bob's next keys in
|
||||
// period 455
|
||||
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
||||
kB = transportCrypto.rotateTransportKeys(kB, 455);
|
||||
// Alice's outgoing keys should equal Bob's next incoming keys
|
||||
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
|
||||
kB.getNextIncomingKeys().getTagKey().getBytes());
|
||||
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
|
||||
kB.getNextIncomingKeys().getHeaderKey().getBytes());
|
||||
// Compare Alice's next keys in period 456 with Bob's current keys
|
||||
// in period 457
|
||||
kB = transportCrypto.rotateTransportKeys(kB, 457);
|
||||
// Alice's next incoming keys should equal Bob's outgoing keys
|
||||
assertArrayEquals(kA.getNextIncomingKeys().getTagKey().getBytes(),
|
||||
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||
assertArrayEquals(kA.getNextIncomingKeys().getHeaderKey().getBytes(),
|
||||
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMasterKeyAffectsOutput() {
|
||||
SecretKey master1 = getSecretKey();
|
||||
assertFalse(Arrays.equals(master.getBytes(), master1.getBytes()));
|
||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, true, true);
|
||||
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId,
|
||||
master1, 123, true, true);
|
||||
assertAllDifferent(k, k1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportIdAffectsOutput() {
|
||||
TransportId transportId1 = getTransportId();
|
||||
assertFalse(transportId.getString().equals(transportId1.getString()));
|
||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, true, true);
|
||||
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId1,
|
||||
master, 123, true, true);
|
||||
assertAllDifferent(k, k1);
|
||||
}
|
||||
|
||||
private void assertAllDifferent(TransportKeys... transportKeys) {
|
||||
List<SecretKey> secretKeys = new ArrayList<>();
|
||||
for (TransportKeys k : transportKeys) {
|
||||
secretKeys.add(k.getPreviousIncomingKeys().getTagKey());
|
||||
secretKeys.add(k.getPreviousIncomingKeys().getHeaderKey());
|
||||
secretKeys.add(k.getCurrentIncomingKeys().getTagKey());
|
||||
secretKeys.add(k.getCurrentIncomingKeys().getHeaderKey());
|
||||
secretKeys.add(k.getNextIncomingKeys().getTagKey());
|
||||
secretKeys.add(k.getNextIncomingKeys().getHeaderKey());
|
||||
secretKeys.add(k.getCurrentOutgoingKeys().getTagKey());
|
||||
secretKeys.add(k.getCurrentOutgoingKeys().getHeaderKey());
|
||||
}
|
||||
assertAllDifferent(secretKeys);
|
||||
}
|
||||
|
||||
private void assertAllDifferent(List<SecretKey> keys) {
|
||||
Set<Bytes> set = new HashSet<>();
|
||||
for (SecretKey k : keys) assertTrue(set.add(new Bytes(k.getBytes())));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.transport.AbstractTransportKeys;
|
||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
class KeyDerivationTestUtils {
|
||||
|
||||
static void assertAllDifferent(AbstractTransportKeys... transportKeys) {
|
||||
List<SecretKey> secretKeys = new ArrayList<>();
|
||||
for (AbstractTransportKeys k : transportKeys) {
|
||||
secretKeys.add(k.getPreviousIncomingKeys().getTagKey());
|
||||
secretKeys.add(k.getPreviousIncomingKeys().getHeaderKey());
|
||||
secretKeys.add(k.getCurrentIncomingKeys().getTagKey());
|
||||
secretKeys.add(k.getCurrentIncomingKeys().getHeaderKey());
|
||||
secretKeys.add(k.getNextIncomingKeys().getTagKey());
|
||||
secretKeys.add(k.getNextIncomingKeys().getHeaderKey());
|
||||
secretKeys.add(k.getCurrentOutgoingKeys().getTagKey());
|
||||
secretKeys.add(k.getCurrentOutgoingKeys().getHeaderKey());
|
||||
}
|
||||
assertAllDifferent(secretKeys);
|
||||
}
|
||||
|
||||
static void assertAllDifferent(List<SecretKey> keys) {
|
||||
Set<Bytes> set = new HashSet<>();
|
||||
for (SecretKey k : keys) assertTrue(set.add(new Bytes(k.getBytes())));
|
||||
}
|
||||
|
||||
static void assertMatches(IncomingKeys in, OutgoingKeys out) {
|
||||
assertArrayEquals(in.getTagKey().getBytes(),
|
||||
out.getTagKey().getBytes());
|
||||
assertArrayEquals(in.getHeaderKey().getBytes(),
|
||||
out.getHeaderKey().getBytes());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.briarproject.bramble.crypto.KeyDerivationTestUtils.assertAllDifferent;
|
||||
import static org.briarproject.bramble.crypto.KeyDerivationTestUtils.assertMatches;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
public class TransportKeyDerivationTest extends BrambleTestCase {
|
||||
|
||||
private final CryptoComponent crypto =
|
||||
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
|
||||
private final TransportCrypto transportCrypto =
|
||||
new TransportCryptoImpl(crypto);
|
||||
private final TransportId transportId = getTransportId();
|
||||
private final SecretKey rootKey = getSecretKey();
|
||||
|
||||
@Test
|
||||
public void testKeysAreDistinct() {
|
||||
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
||||
rootKey, 123, true, true);
|
||||
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
||||
rootKey, 123, false, true);
|
||||
assertAllDifferent(kA);
|
||||
assertAllDifferent(kB);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeysAreNotRotatedToPreviousPeriod() {
|
||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||
rootKey, 123, true, true);
|
||||
TransportKeys k1 = transportCrypto.rotateTransportKeys(k, 122);
|
||||
assertSame(k, k1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeysAreNotRotatedToCurrentPeriod() {
|
||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||
rootKey, 123, true, true);
|
||||
TransportKeys k1 = transportCrypto.rotateTransportKeys(k, 123);
|
||||
assertSame(k, k1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeysAreRotatedByOnePeriod() {
|
||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||
rootKey, 123, true, true);
|
||||
TransportKeys k1 = transportCrypto.rotateTransportKeys(k, 124);
|
||||
assertSame(k.getCurrentIncomingKeys(), k1.getPreviousIncomingKeys());
|
||||
assertSame(k.getNextIncomingKeys(), k1.getCurrentIncomingKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeysAreRotatedByTwoPeriods() {
|
||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||
rootKey, 123, true, true);
|
||||
TransportKeys k1 = transportCrypto.rotateTransportKeys(k, 125);
|
||||
assertSame(k.getNextIncomingKeys(), k1.getPreviousIncomingKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeysAreRotatedByThreePeriods() {
|
||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||
rootKey, 123, true, true);
|
||||
TransportKeys k1 = transportCrypto.rotateTransportKeys(k, 126);
|
||||
assertAllDifferent(k, k1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCurrentKeysMatchContact() {
|
||||
// Start in time period 123
|
||||
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
||||
rootKey, 123, true, true);
|
||||
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
||||
rootKey, 123, false, true);
|
||||
// Alice's incoming keys should equal Bob's outgoing keys
|
||||
assertMatches(kA.getCurrentIncomingKeys(), kB.getCurrentOutgoingKeys());
|
||||
// Bob's incoming keys should equal Alice's outgoing keys
|
||||
assertMatches(kB.getCurrentIncomingKeys(), kA.getCurrentOutgoingKeys());
|
||||
// Rotate into the future
|
||||
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
||||
kB = transportCrypto.rotateTransportKeys(kB, 456);
|
||||
// Alice's incoming keys should equal Bob's outgoing keys
|
||||
assertMatches(kA.getCurrentIncomingKeys(), kB.getCurrentOutgoingKeys());
|
||||
// Bob's incoming keys should equal Alice's outgoing keys
|
||||
assertMatches(kB.getCurrentIncomingKeys(), kA.getCurrentOutgoingKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreviousKeysMatchContact() {
|
||||
// Start in time period 123
|
||||
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
||||
rootKey, 123, true, true);
|
||||
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
||||
rootKey, 123, false, true);
|
||||
// Compare Alice's previous keys in period 456 with Bob's current keys
|
||||
// in period 455
|
||||
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
||||
kB = transportCrypto.rotateTransportKeys(kB, 455);
|
||||
// Alice's previous incoming keys should equal Bob's current
|
||||
// outgoing keys
|
||||
assertMatches(kA.getPreviousIncomingKeys(),
|
||||
kB.getCurrentOutgoingKeys());
|
||||
// Compare Alice's current keys in period 456 with Bob's previous keys
|
||||
// in period 457
|
||||
kB = transportCrypto.rotateTransportKeys(kB, 457);
|
||||
// Bob's previous incoming keys should equal Alice's current
|
||||
// outgoing keys
|
||||
assertMatches(kB.getPreviousIncomingKeys(),
|
||||
kA.getCurrentOutgoingKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextKeysMatchContact() {
|
||||
// Start in time period 123
|
||||
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
||||
rootKey, 123, true, true);
|
||||
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
||||
rootKey, 123, false, true);
|
||||
// Compare Alice's current keys in period 456 with Bob's next keys in
|
||||
// period 455
|
||||
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
||||
kB = transportCrypto.rotateTransportKeys(kB, 455);
|
||||
// Bob's next incoming keys should equal Alice's current outgoing keys
|
||||
assertMatches(kB.getNextIncomingKeys(), kA.getCurrentOutgoingKeys());
|
||||
// Compare Alice's next keys in period 456 with Bob's current keys
|
||||
// in period 457
|
||||
kB = transportCrypto.rotateTransportKeys(kB, 457);
|
||||
// Alice's next incoming keys should equal Bob's current outgoing keys
|
||||
assertMatches(kA.getNextIncomingKeys(), kB.getCurrentOutgoingKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRootKeyAffectsOutput() {
|
||||
SecretKey rootKey1 = getSecretKey();
|
||||
assertFalse(Arrays.equals(rootKey.getBytes(), rootKey1.getBytes()));
|
||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||
rootKey, 123, true, true);
|
||||
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId,
|
||||
rootKey1, 123, true, true);
|
||||
assertAllDifferent(k, k1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportIdAffectsOutput() {
|
||||
TransportId transportId1 = getTransportId();
|
||||
assertNotEquals(transportId.getString(), transportId1.getString());
|
||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||
rootKey, 123, true, true);
|
||||
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId1,
|
||||
rootKey, 123, true, true);
|
||||
assertAllDifferent(k, k1);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@ package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.PendingContactId;
|
||||
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.ContactExistsException;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
@@ -13,7 +13,9 @@ import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||
import org.briarproject.bramble.api.db.NoSuchGroupException;
|
||||
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
|
||||
import org.briarproject.bramble.api.db.NoSuchMessageException;
|
||||
import org.briarproject.bramble.api.db.NoSuchPendingContactException;
|
||||
import org.briarproject.bramble.api.db.NoSuchTransportException;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
@@ -43,18 +45,21 @@ import org.briarproject.bramble.api.sync.event.MessageToAckEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessageToRequestEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
||||
import org.briarproject.bramble.api.transport.HandshakeKeys;
|
||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||
import org.briarproject.bramble.api.transport.KeySet;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeySet;
|
||||
import org.briarproject.bramble.api.transport.TransportKeySetId;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Sequence;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
@@ -70,13 +75,13 @@ import static org.briarproject.bramble.api.transport.TransportConstants.REORDERI
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
@@ -87,9 +92,10 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private final Database<Object> database = context.mock(Database.class);
|
||||
private final ShutdownManager shutdown =
|
||||
private final ShutdownManager shutdownManager =
|
||||
context.mock(ShutdownManager.class);
|
||||
private final EventBus eventBus = context.mock(EventBus.class);
|
||||
private final Executor eventExecutor = context.mock(Executor.class);
|
||||
|
||||
private final SecretKey key = getSecretKey();
|
||||
private final Object txn = new Object();
|
||||
@@ -107,7 +113,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
private final int maxLatency;
|
||||
private final ContactId contactId;
|
||||
private final Contact contact;
|
||||
private final KeySetId keySetId;
|
||||
private final TransportKeySetId keySetId;
|
||||
private final PendingContactId pendingContactId;
|
||||
|
||||
public DatabaseComponentImplTest() {
|
||||
clientId = getClientId();
|
||||
@@ -116,7 +123,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
groupId = group.getId();
|
||||
author = getAuthor();
|
||||
localAuthor = getLocalAuthor();
|
||||
alias = getRandomString(5);
|
||||
message = getMessage(groupId);
|
||||
message1 = getMessage(groupId);
|
||||
messageId = message.getId();
|
||||
@@ -125,16 +131,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
metadata.put("foo", new byte[] {'b', 'a', 'r'});
|
||||
transportId = getTransportId();
|
||||
maxLatency = Integer.MAX_VALUE;
|
||||
contactId = new ContactId(234);
|
||||
contact = new Contact(contactId, author, localAuthor.getId(), alias,
|
||||
true, true);
|
||||
keySetId = new KeySetId(345);
|
||||
contact = getContact(author, true);
|
||||
contactId = contact.getId();
|
||||
alias = contact.getAlias();
|
||||
keySetId = new TransportKeySetId(345);
|
||||
pendingContactId = new PendingContactId(getRandomId());
|
||||
}
|
||||
|
||||
private DatabaseComponent createDatabaseComponent(Database<Object> database,
|
||||
EventBus eventBus, ShutdownManager shutdown) {
|
||||
EventBus eventBus, Executor eventExecutor,
|
||||
ShutdownManager shutdownManager) {
|
||||
return new DatabaseComponentImpl<>(database, Object.class, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -144,7 +152,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
// open()
|
||||
oneOf(database).open(key, null);
|
||||
will(returnValue(false));
|
||||
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
|
||||
oneOf(shutdownManager).addShutdownHook(with(any(Runnable.class)));
|
||||
will(returnValue(shutdownHandle));
|
||||
// startTransaction()
|
||||
oneOf(database).startTransaction();
|
||||
@@ -155,19 +163,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).addLocalAuthor(txn, localAuthor);
|
||||
oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class)));
|
||||
// addContact()
|
||||
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsLocalAuthor(txn, author.getId());
|
||||
will(returnValue(false));
|
||||
oneOf(database).containsContact(txn, author.getId(),
|
||||
localAuthor.getId());
|
||||
oneOf(database).containsContact(txn, author.getId());
|
||||
will(returnValue(false));
|
||||
oneOf(database).addContact(txn, author, localAuthor.getId(),
|
||||
true, true);
|
||||
oneOf(database).addContact(txn, author, true);
|
||||
will(returnValue(contactId));
|
||||
oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class)));
|
||||
oneOf(eventBus).broadcast(with(any(
|
||||
ContactStatusChangedEvent.class)));
|
||||
// getContacts()
|
||||
oneOf(database).getContacts(txn);
|
||||
will(returnValue(singletonList(contact)));
|
||||
@@ -207,13 +209,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).close();
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
assertFalse(db.open(key, null));
|
||||
db.transaction(false, transaction -> {
|
||||
db.addLocalAuthor(transaction, localAuthor);
|
||||
assertEquals(contactId, db.addContact(transaction, author,
|
||||
localAuthor.getId(), true, true));
|
||||
assertEquals(contactId, db.addContact(transaction, author, true));
|
||||
assertEquals(singletonList(contact),
|
||||
db.getContacts(transaction));
|
||||
db.addGroup(transaction, group); // First time - listeners called
|
||||
@@ -238,7 +239,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).abortTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction ->
|
||||
db.addLocalMessage(transaction, message, metadata, true));
|
||||
@@ -263,7 +264,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction ->
|
||||
db.addLocalMessage(transaction, message, metadata, true));
|
||||
@@ -274,14 +275,25 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the contact is in the DB (which it's not)
|
||||
exactly(17).of(database).startTransaction();
|
||||
exactly(18).of(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
exactly(17).of(database).containsContact(txn, contactId);
|
||||
will(returnValue(false));
|
||||
exactly(17).of(database).abortTransaction(txn);
|
||||
oneOf(database).containsContact(txn, author.getId());
|
||||
will(returnValue(false));
|
||||
exactly(18).of(database).abortTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.addHandshakeKeys(transaction, contactId,
|
||||
createHandshakeKeys()));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
@@ -332,6 +344,14 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.getContact(transaction, author.getId()));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.getMessageStatus(transaction, contactId, groupId));
|
||||
@@ -399,14 +419,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.setContactActive(transaction, contactId, true));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.setContactAlias(transaction, contactId, alias));
|
||||
@@ -430,24 +442,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the pseudonym is in the DB (which it's not)
|
||||
exactly(3).of(database).startTransaction();
|
||||
exactly(2).of(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
exactly(3).of(database).containsLocalAuthor(txn,
|
||||
exactly(2).of(database).containsLocalAuthor(txn,
|
||||
localAuthor.getId());
|
||||
will(returnValue(false));
|
||||
exactly(3).of(database).abortTransaction(txn);
|
||||
exactly(2).of(database).abortTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.addContact(transaction, author, localAuthor.getId(),
|
||||
true, true));
|
||||
fail();
|
||||
} catch (NoSuchLocalAuthorException expected) {
|
||||
// Expected
|
||||
}
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
@@ -481,7 +484,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(true));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
@@ -565,7 +568,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(true));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
@@ -668,7 +671,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
exactly(5).of(database).abortTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
@@ -713,6 +716,39 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariousMethodsThrowExceptionIfPendingContactIsMissing()
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the pending contact is in the DB (which it's not)
|
||||
exactly(2).of(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
exactly(2).of(database).containsPendingContact(txn,
|
||||
pendingContactId);
|
||||
will(returnValue(false));
|
||||
exactly(2).of(database).abortTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.addHandshakeKeys(transaction, pendingContactId,
|
||||
createHandshakeKeys()));
|
||||
fail();
|
||||
} catch (NoSuchPendingContactException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.removePendingContact(transaction, pendingContactId));
|
||||
fail();
|
||||
} catch (NoSuchPendingContactException expected) {
|
||||
// Expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateAck() throws Exception {
|
||||
Collection<MessageId> messagesToAck = asList(messageId, messageId1);
|
||||
@@ -727,7 +763,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction -> {
|
||||
Ack a = db.generateAck(transaction, contactId, 123);
|
||||
@@ -761,7 +797,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class)));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction ->
|
||||
assertEquals(messages, db.generateBatch(transaction, contactId,
|
||||
@@ -786,7 +822,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction -> {
|
||||
Offer o = db.generateOffer(transaction, contactId, 123, maxLatency);
|
||||
@@ -810,7 +846,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction -> {
|
||||
Request r = db.generateRequest(transaction, contactId, 123);
|
||||
@@ -844,7 +880,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class)));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction ->
|
||||
assertEquals(messages, db.generateRequestedBatch(transaction,
|
||||
@@ -865,7 +901,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(eventBus).broadcast(with(any(MessagesAckedEvent.class)));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction -> {
|
||||
Ack a = new Ack(singletonList(messageId));
|
||||
@@ -903,7 +939,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(eventBus).broadcast(with(any(MessageToAckEvent.class)));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction -> {
|
||||
// Receive the message twice
|
||||
@@ -931,7 +967,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(eventBus).broadcast(with(any(MessageToAckEvent.class)));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction ->
|
||||
db.receiveMessage(transaction, contactId, message));
|
||||
@@ -949,7 +985,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction ->
|
||||
db.receiveMessage(transaction, contactId, message));
|
||||
@@ -989,7 +1025,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(eventBus).broadcast(with(any(MessageToRequestEvent.class)));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
Offer o = new Offer(asList(messageId, messageId1,
|
||||
messageId2, messageId3));
|
||||
@@ -1012,7 +1048,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(eventBus).broadcast(with(any(MessageRequestedEvent.class)));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
Request r = new Request(singletonList(messageId));
|
||||
db.transaction(false, transaction ->
|
||||
@@ -1042,7 +1078,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
GroupVisibilityUpdatedEvent.class, 0));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction ->
|
||||
db.setGroupVisibility(transaction, contactId, groupId,
|
||||
@@ -1076,7 +1112,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
GroupVisibilityUpdatedEvent.class, 0));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction ->
|
||||
db.setGroupVisibility(transaction, contactId, groupId,
|
||||
@@ -1102,7 +1138,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction ->
|
||||
db.setGroupVisibility(transaction, contactId, groupId,
|
||||
@@ -1112,8 +1148,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testTransportKeys() throws Exception {
|
||||
TransportKeys transportKeys = createTransportKeys();
|
||||
KeySet ks = new KeySet(keySetId, contactId, transportKeys);
|
||||
Collection<KeySet> keys = singletonList(ks);
|
||||
TransportKeySet ks =
|
||||
new TransportKeySet(keySetId, contactId, transportKeys);
|
||||
Collection<TransportKeySet> keys = singletonList(ks);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// startTransaction()
|
||||
@@ -1132,7 +1169,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction -> {
|
||||
db.updateTransportKeys(transaction, keys);
|
||||
@@ -1171,7 +1208,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(true, transaction -> {
|
||||
// With visible group - return stored status
|
||||
@@ -1221,7 +1258,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(true, transaction -> {
|
||||
// With visible group - return stored status
|
||||
@@ -1240,6 +1277,27 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
});
|
||||
}
|
||||
|
||||
private HandshakeKeys createHandshakeKeys() {
|
||||
SecretKey inPrevTagKey = getSecretKey();
|
||||
SecretKey inPrevHeaderKey = getSecretKey();
|
||||
IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey,
|
||||
1, 123, new byte[4]);
|
||||
SecretKey inCurrTagKey = getSecretKey();
|
||||
SecretKey inCurrHeaderKey = getSecretKey();
|
||||
IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey,
|
||||
2, 234, new byte[4]);
|
||||
SecretKey inNextTagKey = getSecretKey();
|
||||
SecretKey inNextHeaderKey = getSecretKey();
|
||||
IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey,
|
||||
3, 345, new byte[4]);
|
||||
SecretKey outCurrTagKey = getSecretKey();
|
||||
SecretKey outCurrHeaderKey = getSecretKey();
|
||||
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
|
||||
2, 456, true);
|
||||
return new HandshakeKeys(transportId, inPrev, inCurr, inNext, outCurr,
|
||||
getSecretKey(), true);
|
||||
}
|
||||
|
||||
private TransportKeys createTransportKeys() {
|
||||
SecretKey inPrevTagKey = getSecretKey();
|
||||
SecretKey inPrevHeaderKey = getSecretKey();
|
||||
@@ -1287,7 +1345,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction -> {
|
||||
// First merge should broadcast an event
|
||||
@@ -1330,7 +1388,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
assertNotNull(db.startTransaction(firstTxnReadOnly));
|
||||
db.startTransaction(secondTxnReadOnly);
|
||||
@@ -1342,8 +1400,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
|
||||
will(returnValue(true));
|
||||
// Contact is a local identity
|
||||
oneOf(database).containsLocalAuthor(txn, author.getId());
|
||||
will(returnValue(true));
|
||||
@@ -1351,12 +1407,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.addContact(transaction, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addContact(transaction, author, true));
|
||||
fail();
|
||||
} catch (ContactExistsException expected) {
|
||||
// Expected
|
||||
@@ -1368,24 +1423,20 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsLocalAuthor(txn, author.getId());
|
||||
will(returnValue(false));
|
||||
// Contact already exists for this local identity
|
||||
oneOf(database).containsContact(txn, author.getId(),
|
||||
localAuthor.getId());
|
||||
// Contact already exists
|
||||
oneOf(database).containsContact(txn, author.getId());
|
||||
will(returnValue(true));
|
||||
oneOf(database).abortTransaction(txn);
|
||||
}});
|
||||
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.addContact(transaction, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addContact(transaction, author, true));
|
||||
fail();
|
||||
} catch (ContactExistsException expected) {
|
||||
// Expected
|
||||
@@ -1401,7 +1452,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
// open()
|
||||
oneOf(database).open(key, null);
|
||||
will(returnValue(false));
|
||||
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
|
||||
oneOf(shutdownManager).addShutdownHook(with(any(Runnable.class)));
|
||||
will(returnValue(shutdownHandle));
|
||||
// startTransaction()
|
||||
oneOf(database).startTransaction();
|
||||
@@ -1441,7 +1492,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).close();
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
assertFalse(db.open(key, null));
|
||||
db.transaction(false, transaction -> {
|
||||
@@ -1455,4 +1506,43 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
});
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommitActionsOccurInOrder() throws Exception {
|
||||
TestEvent action1 = new TestEvent();
|
||||
Runnable action2 = () -> {
|
||||
};
|
||||
TestEvent action3 = new TestEvent();
|
||||
Runnable action4 = () -> {
|
||||
};
|
||||
|
||||
Sequence sequence = context.sequence("sequence");
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
inSequence(sequence);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
inSequence(sequence);
|
||||
oneOf(eventBus).broadcast(action1);
|
||||
inSequence(sequence);
|
||||
oneOf(eventExecutor).execute(action2);
|
||||
inSequence(sequence);
|
||||
oneOf(eventBus).broadcast(action3);
|
||||
inSequence(sequence);
|
||||
oneOf(eventExecutor).execute(action4);
|
||||
inSequence(sequence);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction -> {
|
||||
transaction.attach(action1);
|
||||
transaction.attach(action2);
|
||||
transaction.attach(action3);
|
||||
transaction.attach(action4);
|
||||
});
|
||||
}
|
||||
|
||||
private static class TestEvent extends Event {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +45,7 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
private final Migration<Connection> migration1 =
|
||||
context.mock(Migration.class, "migration1");
|
||||
|
||||
protected final DatabaseConfig config =
|
||||
new TestDatabaseConfig(testDir, 1024 * 1024);
|
||||
protected final DatabaseConfig config = new TestDatabaseConfig(testDir);
|
||||
protected final MessageFactory messageFactory = new TestMessageFactory();
|
||||
protected final SecretKey key = getSecretKey();
|
||||
protected final Clock clock = new SystemClock();
|
||||
|
||||
@@ -76,8 +76,8 @@ public abstract class DatabasePerformanceComparisonTest
|
||||
private Database<Connection> openDatabase(boolean conditionA)
|
||||
throws DbException {
|
||||
Database<Connection> db = createDatabase(conditionA,
|
||||
new TestDatabaseConfig(testDir, MAX_SIZE),
|
||||
new TestMessageFactory(), new SystemClock());
|
||||
new TestDatabaseConfig(testDir), new TestMessageFactory(),
|
||||
new SystemClock());
|
||||
db.open(databaseKey, null);
|
||||
return db;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
@@ -49,9 +48,6 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
||||
|
||||
private static final int ONE_MEGABYTE = 1024 * 1024;
|
||||
static final int MAX_SIZE = 100 * ONE_MEGABYTE;
|
||||
|
||||
/**
|
||||
* How many contacts to simulate.
|
||||
*/
|
||||
@@ -134,11 +130,10 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testContainsContactByAuthorId() throws Exception {
|
||||
String name = "containsContact(T, AuthorId, AuthorId)";
|
||||
String name = "containsContact(T, AuthorId)";
|
||||
benchmark(name, db -> {
|
||||
Connection txn = db.startTransaction();
|
||||
AuthorId remote = pickRandom(contacts).getAuthor().getId();
|
||||
db.containsContact(txn, remote, localAuthor.getId());
|
||||
db.containsContact(txn, pickRandom(contacts).getAuthor().getId());
|
||||
db.commitTransaction(txn);
|
||||
});
|
||||
}
|
||||
@@ -205,7 +200,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetContact() throws Exception {
|
||||
public void testGetContactByContactId() throws Exception {
|
||||
String name = "getContact(T, ContactId)";
|
||||
benchmark(name, db -> {
|
||||
Connection txn = db.startTransaction();
|
||||
@@ -214,6 +209,16 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetContactByAuthorId() throws Exception {
|
||||
String name = "getContact(T, AuthorId)";
|
||||
benchmark(name, db -> {
|
||||
Connection txn = db.startTransaction();
|
||||
db.getContact(txn, pickRandom(contacts).getAuthor().getId());
|
||||
db.commitTransaction(txn);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetContacts() throws Exception {
|
||||
String name = "getContacts(T)";
|
||||
@@ -224,27 +229,6 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetContactsByRemoteAuthorId() throws Exception {
|
||||
String name = "getContactsByAuthorId(T, AuthorId)";
|
||||
benchmark(name, db -> {
|
||||
Connection txn = db.startTransaction();
|
||||
AuthorId remote = pickRandom(contacts).getAuthor().getId();
|
||||
db.getContactsByAuthorId(txn, remote);
|
||||
db.commitTransaction(txn);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetContactsByLocalAuthorId() throws Exception {
|
||||
String name = "getContacts(T, AuthorId)";
|
||||
benchmark(name, db -> {
|
||||
Connection txn = db.startTransaction();
|
||||
db.getContacts(txn, localAuthor.getId());
|
||||
db.commitTransaction(txn);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetGroup() throws Exception {
|
||||
String name = "getGroup(T, GroupId)";
|
||||
@@ -548,8 +532,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
for (int i = 0; i < CONTACTS; i++) {
|
||||
ContactId c = db.addContact(txn, getAuthor(), localAuthor.getId(),
|
||||
random.nextBoolean(), true);
|
||||
ContactId c = db.addContact(txn, getAuthor(), random.nextBoolean());
|
||||
contacts.add(db.getContact(txn, c));
|
||||
contactGroups.put(c, new ArrayList<>());
|
||||
for (int j = 0; j < GROUPS_PER_CONTACT; j++) {
|
||||
|
||||
@@ -48,8 +48,8 @@ public abstract class DatabaseTraceTest extends DatabasePerformanceTest {
|
||||
|
||||
private Database<Connection> openDatabase() throws DbException {
|
||||
Database<Connection> db = createDatabase(
|
||||
new TestDatabaseConfig(testDir, MAX_SIZE),
|
||||
new TestMessageFactory(), new SystemClock());
|
||||
new TestDatabaseConfig(testDir), new TestMessageFactory(),
|
||||
new SystemClock());
|
||||
db.open(databaseKey, null);
|
||||
return db;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
@@ -20,10 +21,13 @@ import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.MessageStatus;
|
||||
import org.briarproject.bramble.api.sync.validation.MessageState;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.transport.HandshakeKeySet;
|
||||
import org.briarproject.bramble.api.transport.HandshakeKeySetId;
|
||||
import org.briarproject.bramble.api.transport.HandshakeKeys;
|
||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||
import org.briarproject.bramble.api.transport.KeySet;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeySet;
|
||||
import org.briarproject.bramble.api.transport.TransportKeySetId;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
import org.briarproject.bramble.system.SystemClock;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
@@ -52,12 +56,12 @@ import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.briarproject.bramble.api.contact.PendingContactState.FAILED;
|
||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
||||
@@ -71,6 +75,7 @@ import static org.briarproject.bramble.test.TestUtils.getClientId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getPendingContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||
@@ -79,7 +84,6 @@ import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -88,11 +92,9 @@ import static org.junit.Assert.fail;
|
||||
public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
private static final int ONE_MEGABYTE = 1024 * 1024;
|
||||
private static final int MAX_SIZE = 5 * ONE_MEGABYTE;
|
||||
// All our transports use a maximum latency of 30 seconds
|
||||
private static final int MAX_LATENCY = 30 * 1000;
|
||||
|
||||
|
||||
private final SecretKey key = getSecretKey();
|
||||
private final File testDir = getTestDirectory();
|
||||
private final GroupId groupId;
|
||||
@@ -105,7 +107,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
private final MessageId messageId;
|
||||
private final TransportId transportId;
|
||||
private final ContactId contactId;
|
||||
private final KeySetId keySetId, keySetId1;
|
||||
private final TransportKeySetId keySetId, keySetId1;
|
||||
private final HandshakeKeySetId handshakeKeySetId, handshakeKeySetId1;
|
||||
private final PendingContact pendingContact;
|
||||
private final Random random = new Random();
|
||||
|
||||
JdbcDatabaseTest() {
|
||||
@@ -119,8 +123,11 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
messageId = message.getId();
|
||||
transportId = getTransportId();
|
||||
contactId = new ContactId(1);
|
||||
keySetId = new KeySetId(1);
|
||||
keySetId1 = new KeySetId(2);
|
||||
keySetId = new TransportKeySetId(1);
|
||||
keySetId1 = new TransportKeySetId(2);
|
||||
handshakeKeySetId = new HandshakeKeySetId(1);
|
||||
handshakeKeySetId1 = new HandshakeKeySetId(2);
|
||||
pendingContact = getPendingContact();
|
||||
}
|
||||
|
||||
protected abstract JdbcDatabase createDatabase(DatabaseConfig config,
|
||||
@@ -138,8 +145,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
assertFalse(db.containsContact(txn, contactId));
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
assertTrue(db.containsContact(txn, contactId));
|
||||
assertFalse(db.containsGroup(txn, groupId));
|
||||
db.addGroup(txn, group);
|
||||
@@ -200,9 +206,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, a shared group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
@@ -231,9 +235,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, a shared group and a shared but unvalidated message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, UNKNOWN, true, null);
|
||||
@@ -276,9 +278,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, an invisible group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
@@ -327,9 +327,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, a shared group and an unshared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, false, null);
|
||||
@@ -358,17 +356,14 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, a shared group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// The message is sendable, but too large to send
|
||||
Collection<MessageId> ids =
|
||||
db.getMessagesToSend(txn, contactId, message.getRawLength() - 1,
|
||||
MAX_LATENCY);
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
message.getRawLength() - 1, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
// The message is just the right size to send
|
||||
ids = db.getMessagesToSend(txn, contactId, message.getRawLength(),
|
||||
@@ -385,9 +380,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact and a visible group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, false);
|
||||
|
||||
@@ -426,9 +419,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, a shared group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
@@ -454,29 +445,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFreeSpace() throws Exception {
|
||||
Message message = getMessage(groupId, MAX_MESSAGE_BODY_LENGTH);
|
||||
Database<Connection> db = open(false);
|
||||
|
||||
// Sanity check: there should be enough space on disk for this test
|
||||
assertTrue(testDir.getFreeSpace() > MAX_SIZE);
|
||||
|
||||
// The free space should not be more than the allowed maximum size
|
||||
long free = db.getFreeSpace();
|
||||
assertTrue(free <= MAX_SIZE);
|
||||
assertTrue(free > 0);
|
||||
|
||||
// Storing a message should reduce the free space
|
||||
Connection txn = db.startTransaction();
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.commitTransaction(txn);
|
||||
assertTrue(db.getFreeSpace() < free);
|
||||
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseWaitsForCommit() throws Exception {
|
||||
CountDownLatch closing = new CountDownLatch(1);
|
||||
@@ -581,9 +549,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact and a shared group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
|
||||
@@ -601,9 +567,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
|
||||
// The group is not in the database
|
||||
assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
|
||||
@@ -619,9 +583,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, an invisible group and a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
@@ -638,9 +600,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact and a group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addGroup(txn, group);
|
||||
|
||||
// The group should not be visible to the contact
|
||||
@@ -678,10 +638,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testTransportKeys() throws Exception {
|
||||
long rotationPeriod = 123, rotationPeriod1 = 234;
|
||||
long timePeriod = 123, timePeriod1 = 234;
|
||||
boolean active = random.nextBoolean();
|
||||
TransportKeys keys = createTransportKeys(rotationPeriod, active);
|
||||
TransportKeys keys1 = createTransportKeys(rotationPeriod1, active);
|
||||
TransportKeys keys = createTransportKeys(timePeriod, active);
|
||||
TransportKeys keys1 = createTransportKeys(timePeriod1, active);
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
@@ -690,43 +650,44 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
||||
|
||||
// Add the contact, the transport and the transport keys
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, active));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
|
||||
assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1));
|
||||
|
||||
// Retrieve the transport keys
|
||||
Collection<KeySet> allKeys = db.getTransportKeys(txn, transportId);
|
||||
Collection<TransportKeySet> allKeys =
|
||||
db.getTransportKeys(txn, transportId);
|
||||
assertEquals(2, allKeys.size());
|
||||
for (KeySet ks : allKeys) {
|
||||
for (TransportKeySet ks : allKeys) {
|
||||
assertEquals(contactId, ks.getContactId());
|
||||
if (ks.getKeySetId().equals(keySetId)) {
|
||||
assertKeysEquals(keys, ks.getTransportKeys());
|
||||
assertKeysEquals(keys, ks.getKeys());
|
||||
} else {
|
||||
assertEquals(keySetId1, ks.getKeySetId());
|
||||
assertKeysEquals(keys1, ks.getTransportKeys());
|
||||
assertKeysEquals(keys1, ks.getKeys());
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate the transport keys
|
||||
TransportKeys rotated = createTransportKeys(rotationPeriod + 1, active);
|
||||
TransportKeys rotated = createTransportKeys(timePeriod + 1, active);
|
||||
TransportKeys rotated1 =
|
||||
createTransportKeys(rotationPeriod1 + 1, active);
|
||||
db.updateTransportKeys(txn, new KeySet(keySetId, contactId, rotated));
|
||||
db.updateTransportKeys(txn, new KeySet(keySetId1, contactId, rotated1));
|
||||
createTransportKeys(timePeriod1 + 1, active);
|
||||
db.updateTransportKeys(txn, new TransportKeySet(keySetId, contactId,
|
||||
rotated));
|
||||
db.updateTransportKeys(txn, new TransportKeySet(keySetId1, contactId,
|
||||
rotated1));
|
||||
|
||||
// Retrieve the transport keys again
|
||||
allKeys = db.getTransportKeys(txn, transportId);
|
||||
assertEquals(2, allKeys.size());
|
||||
for (KeySet ks : allKeys) {
|
||||
for (TransportKeySet ks : allKeys) {
|
||||
assertEquals(contactId, ks.getContactId());
|
||||
if (ks.getKeySetId().equals(keySetId)) {
|
||||
assertKeysEquals(rotated, ks.getTransportKeys());
|
||||
assertKeysEquals(rotated, ks.getKeys());
|
||||
} else {
|
||||
assertEquals(keySetId1, ks.getKeySetId());
|
||||
assertKeysEquals(rotated1, ks.getTransportKeys());
|
||||
assertKeysEquals(rotated1, ks.getKeys());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,7 +702,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
private void assertKeysEquals(TransportKeys expected,
|
||||
TransportKeys actual) {
|
||||
assertEquals(expected.getTransportId(), actual.getTransportId());
|
||||
assertEquals(expected.getRotationPeriod(), actual.getRotationPeriod());
|
||||
assertEquals(expected.getTimePeriod(), actual.getTimePeriod());
|
||||
assertKeysEquals(expected.getPreviousIncomingKeys(),
|
||||
actual.getPreviousIncomingKeys());
|
||||
assertKeysEquals(expected.getCurrentIncomingKeys(),
|
||||
@@ -757,7 +718,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
actual.getTagKey().getBytes());
|
||||
assertArrayEquals(expected.getHeaderKey().getBytes(),
|
||||
actual.getHeaderKey().getBytes());
|
||||
assertEquals(expected.getRotationPeriod(), actual.getRotationPeriod());
|
||||
assertEquals(expected.getTimePeriod(), actual.getTimePeriod());
|
||||
assertEquals(expected.getWindowBase(), actual.getWindowBase());
|
||||
assertArrayEquals(expected.getWindowBitmap(), actual.getWindowBitmap());
|
||||
}
|
||||
@@ -767,39 +728,240 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
actual.getTagKey().getBytes());
|
||||
assertArrayEquals(expected.getHeaderKey().getBytes(),
|
||||
actual.getHeaderKey().getBytes());
|
||||
assertEquals(expected.getRotationPeriod(), actual.getRotationPeriod());
|
||||
assertEquals(expected.getTimePeriod(), actual.getTimePeriod());
|
||||
assertEquals(expected.getStreamCounter(), actual.getStreamCounter());
|
||||
assertEquals(expected.isActive(), actual.isActive());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandshakeKeys() throws Exception {
|
||||
long timePeriod = 123, timePeriod1 = 234;
|
||||
boolean alice = random.nextBoolean();
|
||||
SecretKey rootKey = getSecretKey();
|
||||
SecretKey rootKey1 = getSecretKey();
|
||||
HandshakeKeys keys = createHandshakeKeys(timePeriod, rootKey, alice);
|
||||
HandshakeKeys keys1 = createHandshakeKeys(timePeriod1, rootKey1, alice);
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Initially there should be no handshake keys in the database
|
||||
assertEquals(emptyList(), db.getHandshakeKeys(txn, transportId));
|
||||
|
||||
// Add the contact, the transport and the handshake keys
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
assertEquals(handshakeKeySetId,
|
||||
db.addHandshakeKeys(txn, contactId, keys));
|
||||
assertEquals(handshakeKeySetId1,
|
||||
db.addHandshakeKeys(txn, contactId, keys1));
|
||||
|
||||
// Retrieve the handshake keys
|
||||
Collection<HandshakeKeySet> allKeys =
|
||||
db.getHandshakeKeys(txn, transportId);
|
||||
assertEquals(2, allKeys.size());
|
||||
for (HandshakeKeySet ks : allKeys) {
|
||||
assertEquals(contactId, ks.getContactId());
|
||||
assertNull(ks.getPendingContactId());
|
||||
if (ks.getKeySetId().equals(handshakeKeySetId)) {
|
||||
assertKeysEquals(keys, ks.getKeys());
|
||||
} else {
|
||||
assertEquals(handshakeKeySetId1, ks.getKeySetId());
|
||||
assertKeysEquals(keys1, ks.getKeys());
|
||||
}
|
||||
}
|
||||
|
||||
// Update the handshake keys
|
||||
HandshakeKeys updated =
|
||||
createHandshakeKeys(timePeriod + 1, rootKey, alice);
|
||||
HandshakeKeys updated1 =
|
||||
createHandshakeKeys(timePeriod1 + 1, rootKey1, alice);
|
||||
db.updateHandshakeKeys(txn, new HandshakeKeySet(handshakeKeySetId,
|
||||
contactId, updated));
|
||||
db.updateHandshakeKeys(txn, new HandshakeKeySet(handshakeKeySetId1,
|
||||
contactId, updated1));
|
||||
|
||||
// Retrieve the handshake keys again
|
||||
allKeys = db.getHandshakeKeys(txn, transportId);
|
||||
assertEquals(2, allKeys.size());
|
||||
for (HandshakeKeySet ks : allKeys) {
|
||||
assertEquals(contactId, ks.getContactId());
|
||||
assertNull(ks.getPendingContactId());
|
||||
if (ks.getKeySetId().equals(handshakeKeySetId)) {
|
||||
assertKeysEquals(updated, ks.getKeys());
|
||||
} else {
|
||||
assertEquals(handshakeKeySetId1, ks.getKeySetId());
|
||||
assertKeysEquals(updated1, ks.getKeys());
|
||||
}
|
||||
}
|
||||
|
||||
// Removing the contact should remove the handshake keys
|
||||
db.removeContact(txn, contactId);
|
||||
assertEquals(emptyList(), db.getHandshakeKeys(txn, transportId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
private void assertKeysEquals(HandshakeKeys expected,
|
||||
HandshakeKeys actual) {
|
||||
assertEquals(expected.getTransportId(), actual.getTransportId());
|
||||
assertEquals(expected.getTimePeriod(), actual.getTimePeriod());
|
||||
assertArrayEquals(expected.getRootKey().getBytes(),
|
||||
actual.getRootKey().getBytes());
|
||||
assertEquals(expected.isAlice(), actual.isAlice());
|
||||
assertKeysEquals(expected.getPreviousIncomingKeys(),
|
||||
actual.getPreviousIncomingKeys());
|
||||
assertKeysEquals(expected.getCurrentIncomingKeys(),
|
||||
actual.getCurrentIncomingKeys());
|
||||
assertKeysEquals(expected.getNextIncomingKeys(),
|
||||
actual.getNextIncomingKeys());
|
||||
assertKeysEquals(expected.getCurrentOutgoingKeys(),
|
||||
actual.getCurrentOutgoingKeys());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandshakeKeysForPendingContact() throws Exception {
|
||||
long timePeriod = 123, timePeriod1 = 234;
|
||||
boolean alice = random.nextBoolean();
|
||||
SecretKey rootKey = getSecretKey();
|
||||
SecretKey rootKey1 = getSecretKey();
|
||||
HandshakeKeys keys = createHandshakeKeys(timePeriod, rootKey, alice);
|
||||
HandshakeKeys keys1 = createHandshakeKeys(timePeriod1, rootKey1, alice);
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Initially there should be no handshake keys in the database
|
||||
assertEquals(emptyList(), db.getHandshakeKeys(txn, transportId));
|
||||
|
||||
// Add the pending contact, the transport and the handshake keys
|
||||
db.addPendingContact(txn, pendingContact);
|
||||
db.addTransport(txn, transportId, 123);
|
||||
assertEquals(handshakeKeySetId, db.addHandshakeKeys(txn,
|
||||
pendingContact.getId(), keys));
|
||||
assertEquals(handshakeKeySetId1, db.addHandshakeKeys(txn,
|
||||
pendingContact.getId(), keys1));
|
||||
|
||||
// Retrieve the handshake keys
|
||||
Collection<HandshakeKeySet> allKeys =
|
||||
db.getHandshakeKeys(txn, transportId);
|
||||
assertEquals(2, allKeys.size());
|
||||
for (HandshakeKeySet ks : allKeys) {
|
||||
assertNull(ks.getContactId());
|
||||
assertEquals(pendingContact.getId(), ks.getPendingContactId());
|
||||
if (ks.getKeySetId().equals(handshakeKeySetId)) {
|
||||
assertKeysEquals(keys, ks.getKeys());
|
||||
} else {
|
||||
assertEquals(handshakeKeySetId1, ks.getKeySetId());
|
||||
assertKeysEquals(keys1, ks.getKeys());
|
||||
}
|
||||
}
|
||||
|
||||
// Update the handshake keys
|
||||
HandshakeKeys updated =
|
||||
createHandshakeKeys(timePeriod + 1, rootKey, alice);
|
||||
HandshakeKeys updated1 =
|
||||
createHandshakeKeys(timePeriod1 + 1, rootKey1, alice);
|
||||
db.updateHandshakeKeys(txn, new HandshakeKeySet(handshakeKeySetId,
|
||||
pendingContact.getId(), updated));
|
||||
db.updateHandshakeKeys(txn, new HandshakeKeySet(handshakeKeySetId1,
|
||||
pendingContact.getId(), updated1));
|
||||
|
||||
// Retrieve the handshake keys again
|
||||
allKeys = db.getHandshakeKeys(txn, transportId);
|
||||
assertEquals(2, allKeys.size());
|
||||
for (HandshakeKeySet ks : allKeys) {
|
||||
assertNull(ks.getContactId());
|
||||
assertEquals(pendingContact.getId(), ks.getPendingContactId());
|
||||
if (ks.getKeySetId().equals(handshakeKeySetId)) {
|
||||
assertKeysEquals(updated, ks.getKeys());
|
||||
} else {
|
||||
assertEquals(handshakeKeySetId1, ks.getKeySetId());
|
||||
assertKeysEquals(updated1, ks.getKeys());
|
||||
}
|
||||
}
|
||||
|
||||
// Removing the pending contact should remove the handshake keys
|
||||
db.removePendingContact(txn, pendingContact.getId());
|
||||
assertEquals(emptyList(), db.getHandshakeKeys(txn, transportId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncrementStreamCounter() throws Exception {
|
||||
long rotationPeriod = 123;
|
||||
TransportKeys keys = createTransportKeys(rotationPeriod, true);
|
||||
long timePeriod = 123;
|
||||
TransportKeys keys = createTransportKeys(timePeriod, true);
|
||||
long streamCounter = keys.getCurrentOutgoingKeys().getStreamCounter();
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add the contact, transport and transport keys
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
|
||||
|
||||
// Increment the stream counter twice and retrieve the transport keys
|
||||
db.incrementStreamCounter(txn, transportId, keySetId);
|
||||
db.incrementStreamCounter(txn, transportId, keySetId);
|
||||
Collection<KeySet> newKeys = db.getTransportKeys(txn, transportId);
|
||||
Collection<TransportKeySet> newKeys =
|
||||
db.getTransportKeys(txn, transportId);
|
||||
assertEquals(1, newKeys.size());
|
||||
KeySet ks = newKeys.iterator().next();
|
||||
TransportKeySet ks = newKeys.iterator().next();
|
||||
assertEquals(keySetId, ks.getKeySetId());
|
||||
assertEquals(contactId, ks.getContactId());
|
||||
TransportKeys k = ks.getTransportKeys();
|
||||
TransportKeys k = ks.getKeys();
|
||||
assertEquals(transportId, k.getTransportId());
|
||||
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||
assertEquals(rotationPeriod, outCurr.getRotationPeriod());
|
||||
assertEquals(timePeriod, outCurr.getTimePeriod());
|
||||
assertEquals(streamCounter + 2, outCurr.getStreamCounter());
|
||||
|
||||
// The rest of the keys should be unaffected
|
||||
assertKeysEquals(keys.getPreviousIncomingKeys(),
|
||||
k.getPreviousIncomingKeys());
|
||||
assertKeysEquals(keys.getCurrentIncomingKeys(),
|
||||
k.getCurrentIncomingKeys());
|
||||
assertKeysEquals(keys.getNextIncomingKeys(), k.getNextIncomingKeys());
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncrementStreamCounterForHandshakeKeys() throws Exception {
|
||||
long timePeriod = 123;
|
||||
SecretKey rootKey = getSecretKey();
|
||||
boolean alice = random.nextBoolean();
|
||||
HandshakeKeys keys = createHandshakeKeys(timePeriod, rootKey, alice);
|
||||
long streamCounter = keys.getCurrentOutgoingKeys().getStreamCounter();
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add the contact, transport and handshake keys
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
assertEquals(handshakeKeySetId,
|
||||
db.addHandshakeKeys(txn, contactId, keys));
|
||||
|
||||
// Increment the stream counter twice and retrieve the handshake keys
|
||||
db.incrementStreamCounter(txn, transportId, handshakeKeySetId);
|
||||
db.incrementStreamCounter(txn, transportId, handshakeKeySetId);
|
||||
Collection<HandshakeKeySet> newKeys =
|
||||
db.getHandshakeKeys(txn, transportId);
|
||||
assertEquals(1, newKeys.size());
|
||||
HandshakeKeySet ks = newKeys.iterator().next();
|
||||
assertEquals(handshakeKeySetId, ks.getKeySetId());
|
||||
assertEquals(contactId, ks.getContactId());
|
||||
HandshakeKeys k = ks.getKeys();
|
||||
assertEquals(transportId, k.getTransportId());
|
||||
assertArrayEquals(rootKey.getBytes(), k.getRootKey().getBytes());
|
||||
assertEquals(alice, k.isAlice());
|
||||
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||
assertEquals(timePeriod, outCurr.getTimePeriod());
|
||||
assertEquals(streamCounter + 2, outCurr.getStreamCounter());
|
||||
|
||||
// The rest of the keys should be unaffected
|
||||
@@ -816,8 +978,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
@Test
|
||||
public void testSetReorderingWindow() throws Exception {
|
||||
boolean active = random.nextBoolean();
|
||||
long rotationPeriod = 123;
|
||||
TransportKeys keys = createTransportKeys(rotationPeriod, active);
|
||||
long timePeriod = 123;
|
||||
TransportKeys keys = createTransportKeys(timePeriod, active);
|
||||
long base = keys.getCurrentIncomingKeys().getWindowBase();
|
||||
byte[] bitmap = keys.getCurrentIncomingKeys().getWindowBitmap();
|
||||
|
||||
@@ -825,25 +987,24 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add the contact, transport and transport keys
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, active));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
|
||||
|
||||
// Update the reordering window and retrieve the transport keys
|
||||
random.nextBytes(bitmap);
|
||||
db.setReorderingWindow(txn, keySetId, transportId, rotationPeriod,
|
||||
db.setReorderingWindow(txn, keySetId, transportId, timePeriod,
|
||||
base + 1, bitmap);
|
||||
Collection<KeySet> newKeys = db.getTransportKeys(txn, transportId);
|
||||
Collection<TransportKeySet> newKeys =
|
||||
db.getTransportKeys(txn, transportId);
|
||||
assertEquals(1, newKeys.size());
|
||||
KeySet ks = newKeys.iterator().next();
|
||||
TransportKeySet ks = newKeys.iterator().next();
|
||||
assertEquals(keySetId, ks.getKeySetId());
|
||||
assertEquals(contactId, ks.getContactId());
|
||||
TransportKeys k = ks.getTransportKeys();
|
||||
TransportKeys k = ks.getKeys();
|
||||
assertEquals(transportId, k.getTransportId());
|
||||
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
||||
assertEquals(rotationPeriod, inCurr.getRotationPeriod());
|
||||
assertEquals(timePeriod, inCurr.getTimePeriod());
|
||||
assertEquals(base + 1, inCurr.getWindowBase());
|
||||
assertArrayEquals(bitmap, inCurr.getWindowBitmap());
|
||||
|
||||
@@ -859,54 +1020,90 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetContactsByAuthorId() throws Exception {
|
||||
public void testSetReorderingWindowForHandshakeKeys() throws Exception {
|
||||
long timePeriod = 123;
|
||||
SecretKey rootKey = getSecretKey();
|
||||
boolean alice = random.nextBoolean();
|
||||
HandshakeKeys keys = createHandshakeKeys(timePeriod, rootKey, alice);
|
||||
long base = keys.getCurrentIncomingKeys().getWindowBase();
|
||||
byte[] bitmap = keys.getCurrentIncomingKeys().getWindowBitmap();
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a local author - no contacts should be associated
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
// Add the contact, transport and handshake keys
|
||||
assertEquals(contactId, db.addContact(txn, author,true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
assertEquals(handshakeKeySetId,
|
||||
db.addHandshakeKeys(txn, contactId, keys));
|
||||
|
||||
// Add a contact associated with the local author
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
// Update the reordering window and retrieve the handshake keys
|
||||
random.nextBytes(bitmap);
|
||||
db.setReorderingWindow(txn, handshakeKeySetId, transportId, timePeriod,
|
||||
base + 1, bitmap);
|
||||
Collection<HandshakeKeySet> newKeys =
|
||||
db.getHandshakeKeys(txn, transportId);
|
||||
assertEquals(1, newKeys.size());
|
||||
HandshakeKeySet ks = newKeys.iterator().next();
|
||||
assertEquals(handshakeKeySetId, ks.getKeySetId());
|
||||
assertEquals(contactId, ks.getContactId());
|
||||
HandshakeKeys k = ks.getKeys();
|
||||
assertEquals(transportId, k.getTransportId());
|
||||
assertArrayEquals(rootKey.getBytes(), k.getRootKey().getBytes());
|
||||
assertEquals(alice, k.isAlice());
|
||||
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
||||
assertEquals(timePeriod, inCurr.getTimePeriod());
|
||||
assertEquals(base + 1, inCurr.getWindowBase());
|
||||
assertArrayEquals(bitmap, inCurr.getWindowBitmap());
|
||||
|
||||
// Ensure contact is returned from database by Author ID
|
||||
Collection<Contact> contacts =
|
||||
db.getContactsByAuthorId(txn, author.getId());
|
||||
assertEquals(1, contacts.size());
|
||||
assertEquals(contactId, contacts.iterator().next().getId());
|
||||
|
||||
// Ensure no contacts are returned after contact was deleted
|
||||
db.removeContact(txn, contactId);
|
||||
contacts = db.getContactsByAuthorId(txn, author.getId());
|
||||
assertEquals(0, contacts.size());
|
||||
// The rest of the keys should be unaffected
|
||||
assertKeysEquals(keys.getPreviousIncomingKeys(),
|
||||
k.getPreviousIncomingKeys());
|
||||
assertKeysEquals(keys.getNextIncomingKeys(), k.getNextIncomingKeys());
|
||||
assertKeysEquals(keys.getCurrentOutgoingKeys(),
|
||||
k.getCurrentOutgoingKeys());
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetContactsByLocalAuthorId() throws Exception {
|
||||
public void testGetContactByContactId() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a local author - no contacts should be associated
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
Collection<ContactId> contacts =
|
||||
db.getContacts(txn, localAuthor.getId());
|
||||
assertEquals(emptyList(), contacts);
|
||||
// Add a contact
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
|
||||
// Add a contact associated with the local author
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
contacts = db.getContacts(txn, localAuthor.getId());
|
||||
assertEquals(singletonList(contactId), contacts);
|
||||
// Check the contact is returned
|
||||
Contact c = db.getContact(txn, contactId);
|
||||
assertEquals(contactId, c.getId());
|
||||
assertEquals(author.getId(), c.getAuthor().getId());
|
||||
assertEquals(author.getFormatVersion(),
|
||||
c.getAuthor().getFormatVersion());
|
||||
assertEquals(author.getName(), c.getAuthor().getName());
|
||||
assertArrayEquals(author.getPublicKey(), c.getAuthor().getPublicKey());
|
||||
|
||||
// Remove the local author - the contact should be removed
|
||||
db.removeLocalAuthor(txn, localAuthor.getId());
|
||||
contacts = db.getContacts(txn, localAuthor.getId());
|
||||
assertEquals(emptyList(), contacts);
|
||||
assertFalse(db.containsContact(txn, contactId));
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetContactByAuthorId() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
|
||||
// Check the contact is returned
|
||||
Contact c = db.getContact(txn, author.getId());
|
||||
assertEquals(contactId, c.getId());
|
||||
assertEquals(author.getId(), c.getAuthor().getId());
|
||||
assertEquals(author.getFormatVersion(),
|
||||
c.getAuthor().getFormatVersion());
|
||||
assertEquals(author.getName(), c.getAuthor().getName());
|
||||
assertArrayEquals(author.getPublicKey(), c.getAuthor().getPublicKey());
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -918,9 +1115,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact - initially there should be no offered messages
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
assertEquals(0, db.countOfferedMessages(txn, contactId));
|
||||
|
||||
// Add some offered messages and count them
|
||||
@@ -1502,9 +1697,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, a shared group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
@@ -1601,43 +1794,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferentLocalAuthorsCanHaveTheSameContact()
|
||||
throws Exception {
|
||||
LocalAuthor localAuthor1 = getLocalAuthor();
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add two local authors
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
db.addLocalAuthor(txn, localAuthor1);
|
||||
|
||||
// Add the same contact for each local author
|
||||
ContactId contactId =
|
||||
db.addContact(txn, author, localAuthor.getId(), true, true);
|
||||
ContactId contactId1 =
|
||||
db.addContact(txn, author, localAuthor1.getId(), true, true);
|
||||
|
||||
// The contacts should be distinct
|
||||
assertNotEquals(contactId, contactId1);
|
||||
assertEquals(2, db.getContacts(txn).size());
|
||||
assertEquals(1, db.getContacts(txn, localAuthor.getId()).size());
|
||||
assertEquals(1, db.getContacts(txn, localAuthor1.getId()).size());
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteMessage() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, a shared group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
@@ -1683,47 +1846,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetContactActive() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
|
||||
// The contact should be active
|
||||
Contact contact = db.getContact(txn, contactId);
|
||||
assertTrue(contact.isActive());
|
||||
|
||||
// Set the contact inactive
|
||||
db.setContactActive(txn, contactId, false);
|
||||
|
||||
// The contact should be inactive
|
||||
contact = db.getContact(txn, contactId);
|
||||
assertFalse(contact.isActive());
|
||||
|
||||
// Set the contact active
|
||||
db.setContactActive(txn, contactId, true);
|
||||
|
||||
// The contact should be active
|
||||
contact = db.getContact(txn, contactId);
|
||||
assertTrue(contact.isActive());
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetContactAlias() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
|
||||
// The contact should have no alias
|
||||
Contact contact = db.getContact(txn, contactId);
|
||||
@@ -1778,9 +1907,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, a group and a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, UNKNOWN, false, null);
|
||||
|
||||
@@ -1862,9 +1989,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, a shared group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
@@ -1907,9 +2032,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, a shared group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(contactId, db.addContact(txn, author, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
@@ -1990,41 +2113,94 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPendingContacts() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
assertEquals(emptyList(), db.getPendingContacts(txn));
|
||||
|
||||
db.addPendingContact(txn, pendingContact);
|
||||
Collection<PendingContact> pendingContacts =
|
||||
db.getPendingContacts(txn);
|
||||
assertEquals(1, pendingContacts.size());
|
||||
PendingContact retrieved = pendingContacts.iterator().next();
|
||||
assertEquals(pendingContact.getId(), retrieved.getId());
|
||||
assertEquals(pendingContact.getAlias(), retrieved.getAlias());
|
||||
assertEquals(pendingContact.getState(), retrieved.getState());
|
||||
assertEquals(pendingContact.getTimestamp(), retrieved.getTimestamp());
|
||||
|
||||
db.setPendingContactState(txn, pendingContact.getId(), FAILED);
|
||||
pendingContacts = db.getPendingContacts(txn);
|
||||
assertEquals(1, pendingContacts.size());
|
||||
retrieved = pendingContacts.iterator().next();
|
||||
assertEquals(pendingContact.getId(), retrieved.getId());
|
||||
assertEquals(pendingContact.getAlias(), retrieved.getAlias());
|
||||
assertEquals(FAILED, retrieved.getState());
|
||||
assertEquals(pendingContact.getTimestamp(), retrieved.getTimestamp());
|
||||
|
||||
db.removePendingContact(txn, pendingContact.getId());
|
||||
assertEquals(emptyList(), db.getPendingContacts(txn));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
private Database<Connection> open(boolean resume) throws Exception {
|
||||
return open(resume, new TestMessageFactory(), new SystemClock());
|
||||
}
|
||||
|
||||
private Database<Connection> open(boolean resume,
|
||||
MessageFactory messageFactory, Clock clock) throws Exception {
|
||||
Database<Connection> db =
|
||||
createDatabase(new TestDatabaseConfig(testDir, MAX_SIZE),
|
||||
messageFactory, clock);
|
||||
Database<Connection> db = createDatabase(
|
||||
new TestDatabaseConfig(testDir), messageFactory, clock);
|
||||
if (!resume) deleteTestDirectory(testDir);
|
||||
db.open(key, null);
|
||||
return db;
|
||||
}
|
||||
|
||||
private TransportKeys createTransportKeys(long rotationPeriod,
|
||||
boolean active) {
|
||||
private TransportKeys createTransportKeys(long timePeriod, boolean active) {
|
||||
SecretKey inPrevTagKey = getSecretKey();
|
||||
SecretKey inPrevHeaderKey = getSecretKey();
|
||||
IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey,
|
||||
rotationPeriod - 1, 123, new byte[4]);
|
||||
timePeriod - 1, 123, new byte[4]);
|
||||
SecretKey inCurrTagKey = getSecretKey();
|
||||
SecretKey inCurrHeaderKey = getSecretKey();
|
||||
IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey,
|
||||
rotationPeriod, 234, new byte[4]);
|
||||
timePeriod, 234, new byte[4]);
|
||||
SecretKey inNextTagKey = getSecretKey();
|
||||
SecretKey inNextHeaderKey = getSecretKey();
|
||||
IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey,
|
||||
rotationPeriod + 1, 345, new byte[4]);
|
||||
timePeriod + 1, 345, new byte[4]);
|
||||
SecretKey outCurrTagKey = getSecretKey();
|
||||
SecretKey outCurrHeaderKey = getSecretKey();
|
||||
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
|
||||
rotationPeriod, 456, active);
|
||||
timePeriod, 456, active);
|
||||
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
|
||||
}
|
||||
|
||||
private HandshakeKeys createHandshakeKeys(long timePeriod,
|
||||
SecretKey rootKey, boolean alice) {
|
||||
SecretKey inPrevTagKey = getSecretKey();
|
||||
SecretKey inPrevHeaderKey = getSecretKey();
|
||||
IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey,
|
||||
timePeriod - 1, 123, new byte[4]);
|
||||
SecretKey inCurrTagKey = getSecretKey();
|
||||
SecretKey inCurrHeaderKey = getSecretKey();
|
||||
IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey,
|
||||
timePeriod, 234, new byte[4]);
|
||||
SecretKey inNextTagKey = getSecretKey();
|
||||
SecretKey inNextHeaderKey = getSecretKey();
|
||||
IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey,
|
||||
timePeriod + 1, 345, new byte[4]);
|
||||
SecretKey outCurrTagKey = getSecretKey();
|
||||
SecretKey outCurrHeaderKey = getSecretKey();
|
||||
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
|
||||
timePeriod, 456, true);
|
||||
return new HandshakeKeys(transportId, inPrev, inCurr, inNext, outCurr,
|
||||
rootKey, alice);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
deleteTestDirectory(testDir);
|
||||
|
||||
@@ -45,8 +45,8 @@ public abstract class SingleDatabasePerformanceTest
|
||||
|
||||
private Database<Connection> openDatabase() throws DbException {
|
||||
Database<Connection> db = createDatabase(
|
||||
new TestDatabaseConfig(testDir, MAX_SIZE),
|
||||
new TestMessageFactory(), new SystemClock());
|
||||
new TestDatabaseConfig(testDir), new TestMessageFactory(),
|
||||
new SystemClock());
|
||||
db.open(databaseKey, null);
|
||||
return db;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_SECRET_LABEL;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_KEY_LABEL;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
@@ -74,7 +74,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
||||
Payload ourPayload = new Payload(aliceCommit, null);
|
||||
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
|
||||
SecretKey sharedSecret = getSecretKey();
|
||||
SecretKey masterSecret = getSecretKey();
|
||||
SecretKey masterKey = getSecretKey();
|
||||
|
||||
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
|
||||
crypto, keyAgreementCrypto, payloadEncoder, transport,
|
||||
@@ -134,13 +134,13 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
||||
true, false);
|
||||
will(returnValue(bobConfirm));
|
||||
|
||||
// Alice computes master secret
|
||||
oneOf(crypto).deriveKey(MASTER_SECRET_LABEL, sharedSecret);
|
||||
will(returnValue(masterSecret));
|
||||
// Alice derives master key
|
||||
oneOf(crypto).deriveKey(MASTER_KEY_LABEL, sharedSecret);
|
||||
will(returnValue(masterKey));
|
||||
}});
|
||||
|
||||
// execute
|
||||
assertThat(masterSecret, is(equalTo(protocol.perform())));
|
||||
assertThat(masterKey, is(equalTo(protocol.perform())));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -150,7 +150,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
||||
Payload ourPayload = new Payload(bobCommit, null);
|
||||
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
|
||||
SecretKey sharedSecret = getSecretKey();
|
||||
SecretKey masterSecret = getSecretKey();
|
||||
SecretKey masterKey = getSecretKey();
|
||||
|
||||
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
|
||||
crypto, keyAgreementCrypto, payloadEncoder, transport,
|
||||
@@ -209,13 +209,13 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
||||
will(returnValue(bobConfirm));
|
||||
oneOf(transport).sendConfirm(bobConfirm);
|
||||
|
||||
// Bob computes master secret
|
||||
oneOf(crypto).deriveKey(MASTER_SECRET_LABEL, sharedSecret);
|
||||
will(returnValue(masterSecret));
|
||||
// Bob derives master key
|
||||
oneOf(crypto).deriveKey(MASTER_KEY_LABEL, sharedSecret);
|
||||
will(returnValue(masterKey));
|
||||
}});
|
||||
|
||||
// execute
|
||||
assertThat(masterSecret, is(equalTo(protocol.perform())));
|
||||
assertThat(masterKey, is(equalTo(protocol.perform())));
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
@@ -373,8 +373,8 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
||||
// Alice aborts
|
||||
oneOf(transport).sendAbort(false);
|
||||
|
||||
// Alice never computes master secret
|
||||
never(crypto).deriveKey(MASTER_SECRET_LABEL, sharedSecret);
|
||||
// Alice never derives master key
|
||||
never(crypto).deriveKey(MASTER_KEY_LABEL, sharedSecret);
|
||||
}});
|
||||
|
||||
// execute
|
||||
|
||||
@@ -17,6 +17,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -28,8 +29,8 @@ public class ConnectionRegistryImplTest extends BrambleTestCase {
|
||||
private final TransportId transportId, transportId1;
|
||||
|
||||
public ConnectionRegistryImplTest() {
|
||||
contactId = new ContactId(1);
|
||||
contactId1 = new ContactId(2);
|
||||
contactId = getContactId();
|
||||
contactId1 = getContactId();
|
||||
transportId = getTransportId();
|
||||
transportId1 = getTransportId();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.briarproject.bramble.plugin;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
||||
import org.briarproject.bramble.api.plugin.ConnectionManager;
|
||||
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
|
||||
import org.briarproject.bramble.api.plugin.Plugin;
|
||||
@@ -36,6 +36,7 @@ import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
|
||||
public class PollerTest extends BrambleMockTestCase {
|
||||
@@ -56,7 +57,7 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
|
||||
private final Executor ioExecutor = new ImmediateExecutor();
|
||||
private final TransportId transportId = getTransportId();
|
||||
private final ContactId contactId = new ContactId(234);
|
||||
private final ContactId contactId = getContactId();
|
||||
private final TransportProperties properties = new TransportProperties();
|
||||
private final int pollingInterval = 60 * 1000;
|
||||
private final long now = System.currentTimeMillis();
|
||||
@@ -67,7 +68,7 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectOnContactStatusChanged() throws Exception {
|
||||
public void testConnectOnContactAdded() throws Exception {
|
||||
// Two simplex plugins: one supports polling, the other doesn't
|
||||
SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
|
||||
SimplexPlugin simplexPlugin1 =
|
||||
@@ -143,7 +144,7 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
connectionRegistry, pluginManager, transportPropertyManager,
|
||||
random, clock);
|
||||
|
||||
p.eventOccurred(new ContactStatusChangedEvent(contactId, true));
|
||||
p.eventOccurred(new ContactAddedEvent(contactId));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user