Compare commits

..

8 Commits

Author SHA1 Message Date
akwizgran
5751958eaf Add an absurd amount of logging. 2019-04-02 16:52:28 +01:00
akwizgran
ea006d21f9 Disable strace. 2019-04-02 16:45:31 +01:00
goapunk
0237df937f fix loging, include jtorctl for debugging 2019-03-29 18:40:51 +01:00
goapunk
4c1fd94c67 log tor and jtorctl 2019-03-29 18:21:30 +01:00
akwizgran
295e97c2c7 WIP: Run strace on child processes too. 2019-03-29 16:27:19 +00:00
akwizgran
39f65fcdaf WIP: Run strace on Tor process. 2019-03-29 15:52:26 +00:00
akwizgran
ce04f89f8b WIP: Debug Tor crash when spamming the control port. 2019-03-29 15:40:34 +00:00
akwizgran
51f1cb5e9e WIP: Debug Tor crash when spamming the control port. 2019-03-29 15:28:25 +00:00
215 changed files with 4462 additions and 4648 deletions

View File

@@ -32,6 +32,9 @@ import static java.util.concurrent.TimeUnit.MINUTES;
@ParametersNotNullByDefault @ParametersNotNullByDefault
class AndroidTorPlugin extends TorPlugin { 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 Context appContext;
private final RenewableWakeLock wakeLock; private final RenewableWakeLock wakeLock;
@@ -52,7 +55,7 @@ class AndroidTorPlugin extends TorPlugin {
appContext.getSystemService(POWER_SERVICE); appContext.getSystemService(POWER_SERVICE);
if (pm == null) throw new AssertionError(); if (pm == null) throw new AssertionError();
wakeLock = new RenewableWakeLock(pm, scheduler, PARTIAL_WAKE_LOCK, wakeLock = new RenewableWakeLock(pm, scheduler, PARTIAL_WAKE_LOCK,
getWakeLockTag(), 1, MINUTES); WAKE_LOCK_TAG, 1, MINUTES);
} }
@Override @Override
@@ -84,17 +87,4 @@ class AndroidTorPlugin extends TorPlugin {
super.stop(); super.stop();
wakeLock.release(); 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();
}
} }

View File

@@ -1,13 +1,10 @@
package org.briarproject.bramble.system; 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.AndroidExecutor;
import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider; import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.api.system.SecureRandomProvider; import org.briarproject.bramble.api.system.SecureRandomProvider;
import java.util.concurrent.Executor;
import javax.inject.Singleton; import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
@@ -35,13 +32,6 @@ public class AndroidSystemModule {
return androidExecutor; return androidExecutor;
} }
@Provides
@Singleton
@EventExecutor
Executor provideEventExecutor(AndroidExecutor androidExecutor) {
return androidExecutor::runOnUiThread;
}
@Provides @Provides
@Singleton @Singleton
ResourceProvider provideResourceProvider(AndroidResourceProvider provider) { ResourceProvider provideResourceProvider(AndroidResourceProvider provider) {

View File

@@ -18,10 +18,11 @@ public interface ContactGroupFactory {
* Creates a group for the given client to share with the given contact. * Creates a group for the given client to share with the given contact.
*/ */
Group createContactGroup(ClientId clientId, int majorVersion, Group createContactGroup(ClientId clientId, int majorVersion,
Contact contact, AuthorId local); Contact contact);
/** /**
* Creates a group for the given client to share between the given authors. * Creates a group for the given client to share between the given authors
* identified by their AuthorIds.
*/ */
Group createContactGroup(ClientId clientId, int majorVersion, Group createContactGroup(ClientId clientId, int majorVersion,
AuthorId authorId1, AuthorId authorId2); AuthorId authorId1, AuthorId authorId2);

View File

@@ -1,13 +1,13 @@
package org.briarproject.bramble.api.contact; package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; 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_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.util.StringUtils.toUtf8; import static org.briarproject.bramble.util.StringUtils.toUtf8;
@Immutable @Immutable
@@ -16,28 +16,24 @@ public class Contact {
private final ContactId id; private final ContactId id;
private final Author author; private final Author author;
private final AuthorId localAuthorId;
@Nullable @Nullable
private final String alias; private final String alias;
@Nullable private final boolean verified, active;
private final byte[] handshakePublicKey;
private final boolean verified;
public Contact(ContactId id, Author author, @Nullable String alias, public Contact(ContactId id, Author author, AuthorId localAuthorId,
@Nullable byte[] handshakePublicKey, boolean verified) { @Nullable String alias, boolean verified, boolean active) {
if (alias != null) { if (alias != null) {
int aliasLength = toUtf8(alias).length; int aliasLength = toUtf8(alias).length;
if (aliasLength == 0 || aliasLength > MAX_AUTHOR_NAME_LENGTH) if (aliasLength == 0 || aliasLength > MAX_AUTHOR_NAME_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
if (handshakePublicKey != null && (handshakePublicKey.length == 0 ||
handshakePublicKey.length > MAX_PUBLIC_KEY_LENGTH)) {
throw new IllegalArgumentException();
}
this.id = id; this.id = id;
this.author = author; this.author = author;
this.localAuthorId = localAuthorId;
this.alias = alias; this.alias = alias;
this.handshakePublicKey = handshakePublicKey;
this.verified = verified; this.verified = verified;
this.active = active;
} }
public ContactId getId() { public ContactId getId() {
@@ -48,20 +44,23 @@ public class Contact {
return author; return author;
} }
public AuthorId getLocalAuthorId() {
return localAuthorId;
}
@Nullable @Nullable
public String getAlias() { public String getAlias() {
return alias; return alias;
} }
@Nullable
public byte[] getHandshakePublicKey() {
return handshakePublicKey;
}
public boolean isVerified() { public boolean isVerified() {
return verified; return verified;
} }
public boolean isActive() {
return active;
}
@Override @Override
public int hashCode() { public int hashCode() {
return id.hashCode(); return id.hashCode();

View File

@@ -18,30 +18,30 @@ public interface ContactExchangeTask {
byte PROTOCOL_VERSION = 1; byte PROTOCOL_VERSION = 1;
/** /**
* Label for deriving Alice's header key from the master key. * Label for deriving Alice's header key from the master secret.
*/ */
String ALICE_KEY_LABEL = String ALICE_KEY_LABEL =
"org.briarproject.bramble.contact/ALICE_HEADER_KEY"; "org.briarproject.bramble.contact/ALICE_HEADER_KEY";
/** /**
* Label for deriving Bob's header key from the master key. * Label for deriving Bob's header key from the master secret.
*/ */
String BOB_KEY_LABEL = "org.briarproject.bramble.contact/BOB_HEADER_KEY"; String BOB_KEY_LABEL = "org.briarproject.bramble.contact/BOB_HEADER_KEY";
/** /**
* Label for deriving Alice's key binding nonce from the master key. * Label for deriving Alice's key binding nonce from the master secret.
*/ */
String ALICE_NONCE_LABEL = "org.briarproject.bramble.contact/ALICE_NONCE"; String ALICE_NONCE_LABEL = "org.briarproject.bramble.contact/ALICE_NONCE";
/** /**
* Label for deriving Bob's key binding nonce from the master key. * Label for deriving Bob's key binding nonce from the master secret.
*/ */
String BOB_NONCE_LABEL = "org.briarproject.bramble.contact/BOB_NONCE"; String BOB_NONCE_LABEL = "org.briarproject.bramble.contact/BOB_NONCE";
/** /**
* Exchanges contact information with a remote peer. * Exchanges contact information with a remote peer.
*/ */
void startExchange(LocalAuthor localAuthor, SecretKey masterKey, void startExchange(LocalAuthor localAuthor, SecretKey masterSecret,
DuplexTransportConnection conn, TransportId transportId, DuplexTransportConnection conn, TransportId transportId,
boolean alice); boolean alice);
} }

View File

@@ -13,6 +13,8 @@ import java.util.Collection;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static org.briarproject.bramble.api.contact.PendingContact.PendingContactState.FAILED;
@NotNullByDefault @NotNullByDefault
public interface ContactManager { public interface ContactManager {
@@ -24,31 +26,34 @@ public interface ContactManager {
void registerContactHook(ContactHook hook); void registerContactHook(ContactHook hook);
/** /**
* Stores a contact with the given pseudonym, derives and stores transport * Stores a contact associated with the given local and remote pseudonyms,
* keys for each transport, and returns an ID for the contact. * derives and stores transport keys for each transport, and returns an ID
* for the contact.
* *
* @param alice true if the local party is Alice * @param alice true if the local party is Alice
*/ */
ContactId addContact(Transaction txn, Author a, SecretKey rootKey, 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,
long timestamp, boolean alice, boolean verified, boolean active) long timestamp, boolean alice, boolean verified, boolean active)
throws DbException; 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. * Returns the static link that needs to be sent to the contact to be added.
*/ */
@@ -74,8 +79,7 @@ public interface ContactManager {
Collection<PendingContact> getPendingContacts(); Collection<PendingContact> getPendingContacts();
/** /**
* Removes a {@link PendingContact} that is in state * Removes a {@link PendingContact} that is in state {@link FAILED}.
* {@link PendingContactState FAILED}.
*/ */
void removePendingContact(PendingContact pendingContact); void removePendingContact(PendingContact pendingContact);
@@ -85,19 +89,27 @@ public interface ContactManager {
Contact getContact(ContactId c) throws DbException; Contact getContact(ContactId c) throws DbException;
/** /**
* Returns the contact with the given ID. * Returns the contact with the given remoteAuthorId
* that was added by the LocalAuthor with the given localAuthorId
*
* @throws org.briarproject.bramble.api.db.NoSuchContactException
*/ */
Contact getContact(AuthorId a) throws DbException; Contact getContact(AuthorId remoteAuthorId, AuthorId localAuthorId)
throws DbException;
/** /**
* Returns the contact with the given ID. * Returns the contact with the given remoteAuthorId
* that was added by the LocalAuthor with the given localAuthorId
*
* @throws org.briarproject.bramble.api.db.NoSuchContactException
*/ */
Contact getContact(Transaction txn, AuthorId a) throws DbException; Contact getContact(Transaction txn, AuthorId remoteAuthorId,
AuthorId localAuthorId) throws DbException;
/** /**
* Returns all active contacts. * Returns all active contacts.
*/ */
Collection<Contact> getContacts() throws DbException; Collection<Contact> getActiveContacts() throws DbException;
/** /**
* Removes a contact and all associated state. * Removes a contact and all associated state.
@@ -109,6 +121,12 @@ public interface ContactManager {
*/ */
void removeContact(Transaction txn, ContactId c) throws DbException; 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. * Sets an alias name for the contact or unsets it if alias is null.
*/ */
@@ -122,14 +140,16 @@ public interface ContactManager {
throws DbException; throws DbException;
/** /**
* Returns true if a contact with this pseudonym already exists. * Return true if a contact with this name and public key already exists
*/ */
boolean contactExists(Transaction txn, AuthorId a) throws DbException; boolean contactExists(Transaction txn, AuthorId remoteAuthorId,
AuthorId localAuthorId) throws DbException;
/** /**
* Returns true if a contact with this pseudonym already exists. * Return true if a contact with this name and public key already exists
*/ */
boolean contactExists(AuthorId a) throws DbException; boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId)
throws DbException;
/** /**
* Returns the {@link AuthorInfo} for the given author. * Returns the {@link AuthorInfo} for the given author.

View File

@@ -8,29 +8,26 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault @NotNullByDefault
public class PendingContact { public class PendingContact {
public enum PendingContactState {
WAITING_FOR_CONNECTION,
CONNECTED,
ADDING_CONTACT,
FAILED
}
private final PendingContactId id; private final PendingContactId id;
private final byte[] publicKey;
private final String alias; private final String alias;
private final PendingContactState state; private final PendingContactState state;
private final long timestamp; private final long timestamp;
public PendingContact(PendingContactId id, byte[] publicKey, public PendingContact(PendingContactId id, String alias,
String alias, PendingContactState state, long timestamp) { PendingContactState state, long timestamp) {
this.id = id; this.id = id;
this.publicKey = publicKey;
this.alias = alias; this.alias = alias;
this.state = state; this.state = state;
this.timestamp = timestamp; this.timestamp = timestamp;
} }
public PendingContactId getId() {
return id;
}
public byte[] getPublicKey() {
return publicKey;
}
public String getAlias() { public String getAlias() {
return alias; return alias;
} }
@@ -53,4 +50,5 @@ public class PendingContact {
return o instanceof PendingContact && return o instanceof PendingContact &&
id.equals(((PendingContact) o).id); id.equals(((PendingContact) o).id);
} }
} }

View File

@@ -1,25 +1,11 @@
package org.briarproject.bramble.api.contact; package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.UniqueId; 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 class PendingContactId extends UniqueId {
public PendingContactId(byte[] id) { public PendingContactId(byte[] id) {
super(id); super(id);
} }
@Override
public boolean equals(Object o) {
return o instanceof PendingContactId && super.equals(o);
}
} }

View File

@@ -1,30 +0,0 @@
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();
}
}

View File

@@ -14,12 +14,18 @@ import javax.annotation.concurrent.Immutable;
public class ContactAddedEvent extends Event { public class ContactAddedEvent extends Event {
private final ContactId contactId; private final ContactId contactId;
private final boolean active;
public ContactAddedEvent(ContactId contactId) { public ContactAddedEvent(ContactId contactId, boolean active) {
this.contactId = contactId; this.contactId = contactId;
this.active = active;
} }
public ContactId getContactId() { public ContactId getContactId() {
return contactId; return contactId;
} }
public boolean isActive() {
return active;
}
} }

View File

@@ -0,0 +1,31 @@
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;
}
}

View File

@@ -1,7 +1,7 @@
package org.briarproject.bramble.api.contact.event; 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.PendingContactId;
import org.briarproject.bramble.api.contact.PendingContactState;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;

View File

@@ -2,7 +2,7 @@ package org.briarproject.bramble.api.crypto;
/** /**
* Crypto operations for the key agreement protocol - see * Crypto operations for the key agreement protocol - see
* https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BQP.md * https://code.briarproject.org/akwizgran/briar-spec/blob/master/protocols/BQP.md
*/ */
public interface KeyAgreementCrypto { public interface KeyAgreementCrypto {

View File

@@ -1,45 +1,29 @@
package org.briarproject.bramble.api.crypto; package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.HandshakeKeys;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
/** /**
* Crypto operations for the transport security protocol - see * Crypto operations for the transport security protocol - see
* https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BTP.md * https://code.briarproject.org/akwizgran/briar-spec/blob/master/protocols/BTP.md
*/ */
public interface TransportCrypto { public interface TransportCrypto {
/** /**
* Derives initial transport keys for the given transport in the given * Derives initial transport keys for the given transport in the given
* time period from the given root key. * rotation period from the given master secret.
* *
* @param alice whether the keys are for use by Alice or Bob. * @param alice whether the keys are for use by Alice or Bob.
* @param active whether the keys are usable for outgoing streams. * @param active whether the keys are usable for outgoing streams.
*/ */
TransportKeys deriveTransportKeys(TransportId t, SecretKey rootKey, TransportKeys deriveTransportKeys(TransportId t, SecretKey master,
long timePeriod, boolean alice, boolean active); long rotationPeriod, boolean alice, boolean active);
/** /**
* Rotates the given transport keys to the given time period. If the keys * Rotates the given transport keys to the given rotation period. If the
* are for the given period or any later period they are not rotated. * keys are for the given period or any later period they are not rotated.
*/ */
TransportKeys rotateTransportKeys(TransportKeys k, long timePeriod); TransportKeys rotateTransportKeys(TransportKeys k, long rotationPeriod);
/**
* 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. * Encodes the pseudo-random tag that is used to recognise a stream.

View File

@@ -1,20 +0,0 @@
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);
}
}

View File

@@ -2,8 +2,6 @@ package org.briarproject.bramble.api.db;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; 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.crypto.SecretKey;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
@@ -22,11 +20,8 @@ import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.bramble.api.sync.Offer; import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.Request; import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.validation.MessageState; import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.transport.HandshakeKeySet; import org.briarproject.bramble.api.transport.KeySet;
import org.briarproject.bramble.api.transport.HandshakeKeySetId; import org.briarproject.bramble.api.transport.KeySetId;
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 org.briarproject.bramble.api.transport.TransportKeys;
import java.util.Collection; import java.util.Collection;
@@ -102,31 +97,17 @@ public interface DatabaseComponent {
NullableDbCallable<R, E> task) throws DbException, E; NullableDbCallable<R, E> task) throws DbException, E;
/** /**
* Stores a contact with the given pseudonym and returns an ID for the * Stores a contact associated with the given local and remote pseudonyms,
* contact. * and returns an ID for the contact.
*/ */
ContactId addContact(Transaction txn, Author a, boolean verified) ContactId addContact(Transaction txn, Author remote, AuthorId local,
throws DbException; boolean verified, boolean active) throws DbException;
/** /**
* Stores a group. * Stores a group.
*/ */
void addGroup(Transaction txn, Group g) throws DbException; 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. * Stores a local pseudonym.
*/ */
@@ -138,12 +119,6 @@ public interface DatabaseComponent {
void addLocalMessage(Transaction txn, Message m, Metadata meta, void addLocalMessage(Transaction txn, Message m, Metadata meta,
boolean shared) throws DbException; boolean shared) throws DbException;
/**
* Stores a pending contact.
*/
void addPendingContact(Transaction txn, PendingContact p)
throws DbException;
/** /**
* Stores a transport. * Stores a transport.
*/ */
@@ -154,39 +129,27 @@ public interface DatabaseComponent {
* Stores the given transport keys for the given contact and returns a * Stores the given transport keys for the given contact and returns a
* key set ID. * key set ID.
*/ */
TransportKeySetId addTransportKeys(Transaction txn, ContactId c, KeySetId addTransportKeys(Transaction txn, ContactId c,
TransportKeys k) throws DbException; TransportKeys k) throws DbException;
/** /**
* Returns true if the database contains the given contact. * Returns true if the database contains the given contact for the given
* <p/> * local pseudonym.
* Read-only.
*/ */
boolean containsContact(Transaction txn, AuthorId a) throws DbException; boolean containsContact(Transaction txn, AuthorId remote, AuthorId local)
throws DbException;
/** /**
* Returns true if the database contains the given group. * Returns true if the database contains the given group.
* <p/>
* Read-only.
*/ */
boolean containsGroup(Transaction txn, GroupId g) throws DbException; boolean containsGroup(Transaction txn, GroupId g) throws DbException;
/** /**
* Returns true if the database contains the given local author. * Returns true if the database contains the given local author.
* <p/>
* Read-only.
*/ */
boolean containsLocalAuthor(Transaction txn, AuthorId local) boolean containsLocalAuthor(Transaction txn, AuthorId local)
throws DbException; 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 * Deletes the message with the given ID. Unlike
* {@link #removeMessage(Transaction, MessageId)}, the message ID, * {@link #removeMessage(Transaction, MessageId)}, the message ID,
@@ -252,13 +215,6 @@ public interface DatabaseComponent {
*/ */
Contact getContact(Transaction txn, ContactId c) throws DbException; 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. * Returns all contacts.
* <p/> * <p/>
@@ -266,6 +222,22 @@ public interface DatabaseComponent {
*/ */
Collection<Contact> getContacts(Transaction txn) throws DbException; 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. * Returns the group with the given ID.
* <p/> * <p/>
@@ -297,14 +269,6 @@ public interface DatabaseComponent {
Visibility getGroupVisibility(Transaction txn, ContactId c, GroupId g) Visibility getGroupVisibility(Transaction txn, ContactId c, GroupId g)
throws DbException; 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. * Returns the local pseudonym with the given ID.
* <p/> * <p/>
@@ -453,14 +417,6 @@ public interface DatabaseComponent {
*/ */
long getNextSendTime(Transaction txn, ContactId c) throws DbException; 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. * Returns all settings in the given namespace.
* <p/> * <p/>
@@ -473,20 +429,14 @@ public interface DatabaseComponent {
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<TransportKeySet> getTransportKeys(Transaction txn, TransportId t) Collection<KeySet> getTransportKeys(Transaction txn, TransportId t)
throws DbException; 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. * Increments the outgoing stream counter for the given transport keys.
*/ */
void incrementStreamCounter(Transaction txn, TransportId t, void incrementStreamCounter(Transaction txn, TransportId t, KeySetId k)
TransportKeySetId k) throws DbException; throws DbException;
/** /**
* Merges the given metadata with the existing metadata for the given * Merges the given metadata with the existing metadata for the given
@@ -541,12 +491,6 @@ public interface DatabaseComponent {
*/ */
void removeGroup(Transaction txn, Group g) throws DbException; 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. * Removes a local pseudonym (and all associated state) from the database.
*/ */
@@ -557,12 +501,6 @@ public interface DatabaseComponent {
*/ */
void removeMessage(Transaction txn, MessageId m) throws DbException; 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. * Removes a transport (and all associated state) from the database.
*/ */
@@ -571,14 +509,20 @@ public interface DatabaseComponent {
/** /**
* Removes the given transport keys from the database. * Removes the given transport keys from the database.
*/ */
void removeTransportKeys(Transaction txn, TransportId t, void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
TransportKeySetId k) throws DbException; throws DbException;
/** /**
* Marks the given contact as verified. * Marks the given contact as verified.
*/ */
void setContactVerified(Transaction txn, ContactId c) throws DbException; 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. * Sets an alias name for the contact or unsets it if alias is null.
*/ */
@@ -609,36 +553,21 @@ public interface DatabaseComponent {
Collection<MessageId> dependencies) throws DbException; Collection<MessageId> dependencies) throws DbException;
/** /**
* Sets the reordering window for the given transport key set in the given * Sets the reordering window for the given key set and transport in the
* time period. * given rotation period.
*/ */
void setReorderingWindow(Transaction txn, TransportKeySetId k, void setReorderingWindow(Transaction txn, KeySetId k, TransportId t,
TransportId t, long timePeriod, long base, byte[] bitmap) long rotationPeriod, long base, byte[] bitmap) throws DbException;
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. * Marks the given transport keys as usable for outgoing streams.
*/ */
void setTransportKeysActive(Transaction txn, TransportId t, void setTransportKeysActive(Transaction txn, TransportId t, KeySetId k)
TransportKeySetId k) throws DbException;
/**
* Stores the given handshake keys, deleting any keys they have replaced.
*/
void updateHandshakeKeys(Transaction txn, Collection<HandshakeKeySet> keys)
throws DbException; throws DbException;
/** /**
* Stores the given transport keys, deleting any keys they have replaced. * Stores the given transport keys, deleting any keys they have replaced.
*/ */
void updateTransportKeys(Transaction txn, Collection<TransportKeySet> keys) void updateTransportKeys(Transaction txn, Collection<KeySet> keys)
throws DbException; throws DbException;
} }

View File

@@ -10,4 +10,6 @@ public interface DatabaseConfig {
File getDatabaseDirectory(); File getDatabaseDirectory();
File getDatabaseKeyDirectory(); File getDatabaseKeyDirectory();
long getMaxSize();
} }

View File

@@ -1,24 +0,0 @@
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);
}
}

View File

@@ -1,9 +0,0 @@
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 {
}

View File

@@ -1,9 +0,0 @@
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 {
}

View File

@@ -1,24 +0,0 @@
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);
}
}

View File

@@ -1,15 +1,13 @@
package org.briarproject.bramble.api.db; package org.briarproject.bramble.api.db;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventExecutor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
import static java.util.Collections.emptyList;
/** /**
* A wrapper around a database transaction. Transactions are not thread-safe. * A wrapper around a database transaction. Transactions are not thread-safe.
*/ */
@@ -19,7 +17,7 @@ public class Transaction {
private final Object txn; private final Object txn;
private final boolean readOnly; private final boolean readOnly;
private List<CommitAction> actions = null; private List<Event> events = null;
private boolean committed = false; private boolean committed = false;
public Transaction(Object txn, boolean readOnly) { public Transaction(Object txn, boolean readOnly) {
@@ -44,27 +42,19 @@ public class Transaction {
/** /**
* Attaches an event to be broadcast when the transaction has been * Attaches an event to be broadcast when the transaction has been
* committed. The event will be broadcast on the {@link EventExecutor}. * committed.
*/ */
public void attach(Event e) { public void attach(Event e) {
if (actions == null) actions = new ArrayList<>(); if (events == null) events = new ArrayList<>();
actions.add(new EventAction(e)); events.add(e);
} }
/** /**
* Attaches a task to be executed when the transaction has been * Returns any events attached to the transaction.
* committed. The task will be run on the {@link EventExecutor}.
*/ */
public void attach(Runnable r) { public List<Event> getEvents() {
if (actions == null) actions = new ArrayList<>(); if (events == null) return Collections.emptyList();
actions.add(new TaskAction(r)); return events;
}
/**
* Returns any actions attached to the transaction.
*/
public List<CommitAction> getActions() {
return actions == null ? emptyList() : actions;
} }
/** /**

View File

@@ -16,8 +16,7 @@ public interface EventBus {
void removeListener(EventListener l); void removeListener(EventListener l);
/** /**
* Asynchronously notifies all listeners of an event. Listeners are * Notifies all listeners of an event.
* notified on the {@link EventExecutor}.
*/ */
void broadcast(Event e); void broadcast(Event e);
} }

View File

@@ -1,26 +0,0 @@
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 {
}

View File

@@ -12,6 +12,5 @@ public interface EventListener {
* Called when an event is broadcast. Implementations of this method must * Called when an event is broadcast. Implementations of this method must
* not block. * not block.
*/ */
@EventExecutor
void eventOccurred(Event e); void eventOccurred(Event e);
} }

View File

@@ -2,11 +2,8 @@ package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
/** /**
* A pseudonym for the local user. * A pseudonym for the local user.
*/ */
@@ -15,8 +12,6 @@ import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_K
public class LocalAuthor extends Author { public class LocalAuthor extends Author {
private final byte[] privateKey; private final byte[] privateKey;
@Nullable
private final byte[] handshakePublicKey, handshakePrivateKey;
private final long created; private final long created;
public LocalAuthor(AuthorId id, int formatVersion, String name, public LocalAuthor(AuthorId id, int formatVersion, String name,
@@ -24,22 +19,6 @@ public class LocalAuthor extends Author {
super(id, formatVersion, name, publicKey); super(id, formatVersion, name, publicKey);
this.privateKey = privateKey; this.privateKey = privateKey;
this.created = created; 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;
} }
/** /**
@@ -49,22 +28,6 @@ public class LocalAuthor extends Author {
return privateKey; 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 * Returns the time the pseudonym was created, in milliseconds since the
* Unix epoch. * Unix epoch.

View File

@@ -40,8 +40,8 @@ public interface KeyAgreementConstants {
"org.briarproject.bramble.keyagreement/SHARED_SECRET"; "org.briarproject.bramble.keyagreement/SHARED_SECRET";
/** /**
* Label for deriving the master key. * Label for deriving the master secret.
*/ */
String MASTER_KEY_LABEL = String MASTER_SECRET_LABEL =
"org.briarproject.bramble.keyagreement/MASTER_SECRET"; "org.briarproject.bramble.keyagreement/MASTER_SECRET";
} }

View File

@@ -1,57 +0,0 @@
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();
}
}

View File

@@ -1,70 +0,0 @@
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);
}
}

View File

@@ -1,36 +0,0 @@
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;
}
}

View File

@@ -1,36 +0,0 @@
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;
}
}

View File

@@ -1,35 +1,30 @@
package org.briarproject.bramble.api.transport; package org.briarproject.bramble.api.transport;
import org.briarproject.bramble.api.crypto.SecretKey; 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; import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
/** /**
* Contains transport keys for receiving streams from a given contact over a * Contains transport keys for receiving streams from a given contact over a
* given transport in a given time period. * given transport in a given rotation period.
*/ */
@Immutable
@NotNullByDefault
public class IncomingKeys { public class IncomingKeys {
private final SecretKey tagKey, headerKey; private final SecretKey tagKey, headerKey;
private final long timePeriod, windowBase; private final long rotationPeriod, windowBase;
private final byte[] windowBitmap; private final byte[] windowBitmap;
public IncomingKeys(SecretKey tagKey, SecretKey headerKey, public IncomingKeys(SecretKey tagKey, SecretKey headerKey,
long timePeriod) { long rotationPeriod) {
this(tagKey, headerKey, timePeriod, 0, this(tagKey, headerKey, rotationPeriod, 0,
new byte[REORDERING_WINDOW_SIZE / 8]); new byte[REORDERING_WINDOW_SIZE / 8]);
} }
public IncomingKeys(SecretKey tagKey, SecretKey headerKey, public IncomingKeys(SecretKey tagKey, SecretKey headerKey,
long timePeriod, long windowBase, byte[] windowBitmap) { long rotationPeriod, long windowBase, byte[] windowBitmap) {
this.tagKey = tagKey; this.tagKey = tagKey;
this.headerKey = headerKey; this.headerKey = headerKey;
this.timePeriod = timePeriod; this.rotationPeriod = rotationPeriod;
this.windowBase = windowBase; this.windowBase = windowBase;
this.windowBitmap = windowBitmap; this.windowBitmap = windowBitmap;
} }
@@ -42,8 +37,8 @@ public class IncomingKeys {
return headerKey; return headerKey;
} }
public long getTimePeriod() { public long getRotationPeriod() {
return timePeriod; return rotationPeriod;
} }
public long getWindowBase() { public long getWindowBase() {

View File

@@ -27,14 +27,14 @@ public interface KeyManager {
* @param alice true if the local party is Alice * @param alice true if the local party is Alice
* @param active whether the derived keys can be used for outgoing streams * @param active whether the derived keys can be used for outgoing streams
*/ */
Map<TransportId, TransportKeySetId> addContact(Transaction txn, ContactId c, Map<TransportId, KeySetId> addContact(Transaction txn, ContactId c,
SecretKey rootKey, long timestamp, boolean alice, boolean active) SecretKey master, long timestamp, boolean alice, boolean active)
throws DbException; throws DbException;
/** /**
* Marks the given transport keys as usable for outgoing streams. * Marks the given transport keys as usable for outgoing streams.
*/ */
void activateKeys(Transaction txn, Map<TransportId, TransportKeySetId> keys) void activateKeys(Transaction txn, Map<TransportId, KeySetId> keys)
throws DbException; throws DbException;
/** /**

View File

@@ -0,0 +1,47 @@
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);
}
}

View File

@@ -5,19 +5,18 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* Type-safe wrapper for an integer that uniquely identifies a * Type-safe wrapper for an integer that uniquely identifies a set of transport
* {@link TransportKeySet set of transport keys} within the scope of the local * keys within the scope of the local device.
* device.
* <p/> * <p/>
* Key sets created on a given device must have increasing identifiers. * Key sets created on a given device must have increasing identifiers.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class TransportKeySetId { public class KeySetId {
private final int id; private final int id;
public TransportKeySetId(int id) { public KeySetId(int id) {
this.id = id; this.id = id;
} }
@@ -32,7 +31,6 @@ public class TransportKeySetId {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
return o instanceof TransportKeySetId && return o instanceof KeySetId && id == ((KeySetId) o).id;
id == ((TransportKeySetId) o).id;
} }
} }

View File

@@ -1,32 +1,27 @@
package org.briarproject.bramble.api.transport; package org.briarproject.bramble.api.transport;
import org.briarproject.bramble.api.crypto.SecretKey; 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 * Contains transport keys for sending streams to a given contact over a given
* transport in a given time period. * transport in a given rotation period.
*/ */
@Immutable
@NotNullByDefault
public class OutgoingKeys { public class OutgoingKeys {
private final SecretKey tagKey, headerKey; private final SecretKey tagKey, headerKey;
private final long timePeriod, streamCounter; private final long rotationPeriod, streamCounter;
private final boolean active; private final boolean active;
public OutgoingKeys(SecretKey tagKey, SecretKey headerKey, public OutgoingKeys(SecretKey tagKey, SecretKey headerKey,
long timePeriod, boolean active) { long rotationPeriod, boolean active) {
this(tagKey, headerKey, timePeriod, 0, active); this(tagKey, headerKey, rotationPeriod, 0, active);
} }
public OutgoingKeys(SecretKey tagKey, SecretKey headerKey, public OutgoingKeys(SecretKey tagKey, SecretKey headerKey,
long timePeriod, long streamCounter, boolean active) { long rotationPeriod, long streamCounter, boolean active) {
this.tagKey = tagKey; this.tagKey = tagKey;
this.headerKey = headerKey; this.headerKey = headerKey;
this.timePeriod = timePeriod; this.rotationPeriod = rotationPeriod;
this.streamCounter = streamCounter; this.streamCounter = streamCounter;
this.active = active; this.active = active;
} }
@@ -39,8 +34,8 @@ public class OutgoingKeys {
return headerKey; return headerKey;
} }
public long getTimePeriod() { public long getRotationPeriod() {
return timePeriod; return rotationPeriod;
} }
public long getStreamCounter() { public long getStreamCounter() {

View File

@@ -2,13 +2,8 @@ package org.briarproject.bramble.api.transport;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class StreamContext { public class StreamContext {
private final ContactId contactId; private final ContactId contactId;

View File

@@ -82,58 +82,30 @@ public interface TransportConstants {
int REORDERING_WINDOW_SIZE = 32; int REORDERING_WINDOW_SIZE = 32;
/** /**
* Label for deriving Alice's initial tag key from the root key in * Label for deriving Alice's initial tag key from the master secret.
* rotation mode.
*/ */
String ALICE_TAG_LABEL = "org.briarproject.bramble.transport/ALICE_TAG_KEY"; String ALICE_TAG_LABEL = "org.briarproject.bramble.transport/ALICE_TAG_KEY";
/** /**
* Label for deriving Bob's initial tag key from the root key in rotation * Label for deriving Bob's initial tag key from the master secret.
* mode.
*/ */
String BOB_TAG_LABEL = "org.briarproject.bramble.transport/BOB_TAG_KEY"; String BOB_TAG_LABEL = "org.briarproject.bramble.transport/BOB_TAG_KEY";
/** /**
* Label for deriving Alice's initial header key from the root key in * Label for deriving Alice's initial header key from the master secret.
* rotation mode.
*/ */
String ALICE_HEADER_LABEL = String ALICE_HEADER_LABEL =
"org.briarproject.bramble.transport/ALICE_HEADER_KEY"; "org.briarproject.bramble.transport/ALICE_HEADER_KEY";
/** /**
* Label for deriving Bob's initial header key from the root key in * Label for deriving Bob's initial header key from the master secret.
* rotation mode.
*/ */
String BOB_HEADER_LABEL = String BOB_HEADER_LABEL =
"org.briarproject.bramble.transport/BOB_HEADER_KEY"; "org.briarproject.bramble.transport/BOB_HEADER_KEY";
/** /**
* Label for deriving the next period's key in rotation mode. * Label for deriving the next period's key in key rotation.
*/ */
String ROTATE_LABEL = "org.briarproject.bramble.transport/ROTATE"; 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";
} }

View File

@@ -1,49 +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 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);
}
}

View File

@@ -1,20 +1,52 @@
package org.briarproject.bramble.api.transport; package org.briarproject.bramble.api.transport;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable;
/** /**
* Keys for communicating with a given contact over a given transport. Unlike * Keys for communicating with a given contact over a given transport.
* {@link HandshakeKeys} these keys provide forward secrecy.
*/ */
@Immutable public class TransportKeys {
@NotNullByDefault
public class TransportKeys extends AbstractTransportKeys { private final TransportId transportId;
private final IncomingKeys inPrev, inCurr, inNext;
private final OutgoingKeys outCurr;
public TransportKeys(TransportId transportId, IncomingKeys inPrev, public TransportKeys(TransportId transportId, IncomingKeys inPrev,
IncomingKeys inCurr, IncomingKeys inNext, OutgoingKeys outCurr) { IncomingKeys inCurr, IncomingKeys inNext, OutgoingKeys outCurr) {
super(transportId, inPrev, inCurr, inNext, 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();
} }
} }

View File

@@ -1,10 +1,6 @@
package org.briarproject.bramble.test; package org.briarproject.bramble.test;
import org.briarproject.bramble.api.UniqueId; 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.crypto.SecretKey;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
@@ -29,7 +25,6 @@ import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Arrays.asList; 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.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; 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.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
@@ -46,7 +41,6 @@ public class TestUtils {
new AtomicInteger((int) (Math.random() * 1000 * 1000)); new AtomicInteger((int) (Math.random() * 1000 * 1000));
private static final Random random = new Random(); private static final Random random = new Random();
private static final long timestamp = System.currentTimeMillis(); private static final long timestamp = System.currentTimeMillis();
private static final AtomicInteger nextContactId = new AtomicInteger(1);
public static File getTestDirectory() { public static File getTestDirectory() {
int name = nextTestDir.getAndIncrement(); int name = nextTestDir.getAndIncrement();
@@ -146,35 +140,6 @@ public class TestUtils {
return new Message(id, groupId, timestamp, body); 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) { public static double getMedian(Collection<? extends Number> samples) {
int size = samples.size(); int size = samples.size();
if (size == 0) throw new IllegalArgumentException(); if (size == 0) throw new IllegalArgumentException();

View File

@@ -15,7 +15,6 @@ dependencies {
implementation 'org.bitlet:weupnp:0.1.4' implementation 'org.bitlet:weupnp:0.1.4'
implementation 'net.i2p.crypto:eddsa:0.2.0' implementation 'net.i2p.crypto:eddsa:0.2.0'
implementation 'org.whispersystems:curve25519-java:0.5.0' implementation 'org.whispersystems:curve25519-java:0.5.0'
implementation 'org.briarproject:jtorctl:0.3'
annotationProcessor 'com.google.dagger:dagger-compiler:2.19' annotationProcessor 'com.google.dagger:dagger-compiler:2.19'

View File

@@ -0,0 +1 @@
*.class

View File

@@ -0,0 +1,114 @@
// 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() {};
}

View File

@@ -0,0 +1,20 @@
// 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;
}

View File

@@ -0,0 +1,75 @@
// 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);
}

View File

@@ -0,0 +1,18 @@
// 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) {}
}

View File

@@ -0,0 +1,98 @@
// 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);
}
}

View File

@@ -0,0 +1,4 @@
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

View File

@@ -0,0 +1,151 @@
// 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";
}

View File

@@ -0,0 +1,998 @@
// 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);
}
}

View File

@@ -0,0 +1,39 @@
// 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;
}
}
}

View File

@@ -0,0 +1,16 @@
// 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); }
}

View File

@@ -0,0 +1 @@
*.class

View File

@@ -0,0 +1,44 @@
// 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());
}
}

View File

@@ -0,0 +1,146 @@
// 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());
}
}

View File

@@ -39,7 +39,8 @@ class ContactGroupFactoryImpl implements ContactGroupFactory {
@Override @Override
public Group createContactGroup(ClientId clientId, int majorVersion, public Group createContactGroup(ClientId clientId, int majorVersion,
Contact contact, AuthorId local) { Contact contact) {
AuthorId local = contact.getLocalAuthorId();
AuthorId remote = contact.getAuthor().getId(); AuthorId remote = contact.getAuthor().getId();
byte[] descriptor = createGroupDescriptor(local, remote); byte[] descriptor = createGroupDescriptor(local, remote);
return groupFactory.createGroup(clientId, majorVersion, descriptor); return groupFactory.createGroup(clientId, majorVersion, descriptor);

View File

@@ -77,7 +77,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
private volatile LocalAuthor localAuthor; private volatile LocalAuthor localAuthor;
private volatile DuplexTransportConnection conn; private volatile DuplexTransportConnection conn;
private volatile TransportId transportId; private volatile TransportId transportId;
private volatile SecretKey masterKey; private volatile SecretKey masterSecret;
private volatile boolean alice; private volatile boolean alice;
@Inject @Inject
@@ -104,13 +104,13 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
} }
@Override @Override
public void startExchange(LocalAuthor localAuthor, SecretKey masterKey, public void startExchange(LocalAuthor localAuthor, SecretKey masterSecret,
DuplexTransportConnection conn, TransportId transportId, DuplexTransportConnection conn, TransportId transportId,
boolean alice) { boolean alice) {
this.localAuthor = localAuthor; this.localAuthor = localAuthor;
this.conn = conn; this.conn = conn;
this.transportId = transportId; this.transportId = transportId;
this.masterKey = masterKey; this.masterSecret = masterSecret;
this.alice = alice; this.alice = alice;
start(); start();
} }
@@ -142,9 +142,9 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
} }
// Derive the header keys for the transport streams // Derive the header keys for the transport streams
SecretKey aliceHeaderKey = crypto.deriveKey(ALICE_KEY_LABEL, masterKey, SecretKey aliceHeaderKey = crypto.deriveKey(ALICE_KEY_LABEL,
new byte[] {PROTOCOL_VERSION}); masterSecret, new byte[] {PROTOCOL_VERSION});
SecretKey bobHeaderKey = crypto.deriveKey(BOB_KEY_LABEL, masterKey, SecretKey bobHeaderKey = crypto.deriveKey(BOB_KEY_LABEL, masterSecret,
new byte[] {PROTOCOL_VERSION}); new byte[] {PROTOCOL_VERSION});
// Create the readers // Create the readers
@@ -163,9 +163,9 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
.createRecordWriter(streamWriter.getOutputStream()); .createRecordWriter(streamWriter.getOutputStream());
// Derive the nonces to be signed // Derive the nonces to be signed
byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterKey, byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterSecret,
new byte[] {PROTOCOL_VERSION}); new byte[] {PROTOCOL_VERSION});
byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterKey, byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterSecret,
new byte[] {PROTOCOL_VERSION}); new byte[] {PROTOCOL_VERSION});
byte[] localNonce = alice ? aliceNonce : bobNonce; byte[] localNonce = alice ? aliceNonce : bobNonce;
byte[] remoteNonce = alice ? bobNonce : aliceNonce; byte[] remoteNonce = alice ? bobNonce : aliceNonce;
@@ -293,7 +293,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
throws DbException { throws DbException {
return db.transactionWithResult(false, txn -> { return db.transactionWithResult(false, txn -> {
ContactId contactId = contactManager.addContact(txn, remoteAuthor, ContactId contactId = contactManager.addContact(txn, remoteAuthor,
masterKey, timestamp, alice, true, true); localAuthor.getId(), masterSecret, timestamp, alice,
true, true);
transportPropertyManager.addRemoteProperties(txn, contactId, transportPropertyManager.addRemoteProperties(txn, contactId,
remoteProperties); remoteProperties);
return contactId; return contactId;

View File

@@ -8,6 +8,7 @@ import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; 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.db.Transaction;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
@@ -17,6 +18,7 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
@@ -28,9 +30,8 @@ import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION; import static org.briarproject.bramble.api.contact.PendingContact.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_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.OURSELVES;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN; 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.UNVERIFIED;
@@ -67,31 +68,31 @@ class ContactManagerImpl implements ContactManager {
} }
@Override @Override
public ContactId addContact(Transaction txn, Author a, SecretKey rootKey, 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,
long timestamp, boolean alice, boolean verified, boolean active) long timestamp, boolean alice, boolean verified, boolean active)
throws DbException { 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 -> return db.transactionWithResult(false, txn ->
addContact(txn, a, rootKey, timestamp, alice, addContact(txn, remote, local, master, timestamp, alice,
verified, active)); verified, active));
} }
@@ -122,8 +123,8 @@ class ContactManagerImpl implements ContactManager {
public PendingContact addRemoteContactRequest(String link, String alias) { public PendingContact addRemoteContactRequest(String link, String alias) {
// TODO replace with real implementation // TODO replace with real implementation
PendingContactId id = new PendingContactId(link.getBytes()); PendingContactId id = new PendingContactId(link.getBytes());
return new PendingContact(id, new byte[MAX_PUBLIC_KEY_LENGTH], alias, return new PendingContact(id, alias, WAITING_FOR_CONNECTION,
WAITING_FOR_CONNECTION, System.currentTimeMillis()); System.currentTimeMillis());
} }
@Override @Override
@@ -143,18 +144,32 @@ class ContactManagerImpl implements ContactManager {
} }
@Override @Override
public Contact getContact(AuthorId a) throws DbException { public Contact getContact(AuthorId remoteAuthorId, AuthorId localAuthorId)
return db.transactionWithResult(true, txn -> getContact(txn, a)); throws DbException {
return db.transactionWithResult(true, txn ->
getContact(txn, remoteAuthorId, localAuthorId));
} }
@Override @Override
public Contact getContact(Transaction txn, AuthorId a) throws DbException { public Contact getContact(Transaction txn, AuthorId remoteAuthorId,
return db.getContact(txn, a); 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();
} }
@Override @Override
public Collection<Contact> getContacts() throws DbException { public Collection<Contact> getActiveContacts() throws DbException {
return db.transactionWithResult(true, db::getContacts); 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;
} }
@Override @Override
@@ -162,6 +177,12 @@ class ContactManagerImpl implements ContactManager {
db.transaction(false, txn -> removeContact(txn, c)); 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 @Override
public void setContactAlias(Transaction txn, ContactId c, public void setContactAlias(Transaction txn, ContactId c,
@Nullable String alias) throws DbException { @Nullable String alias) throws DbException {
@@ -180,14 +201,16 @@ class ContactManagerImpl implements ContactManager {
} }
@Override @Override
public boolean contactExists(Transaction txn, AuthorId a) public boolean contactExists(Transaction txn, AuthorId remoteAuthorId,
throws DbException { AuthorId localAuthorId) throws DbException {
return db.containsContact(txn, a); return db.containsContact(txn, remoteAuthorId, localAuthorId);
} }
@Override @Override
public boolean contactExists(AuthorId a) throws DbException { public boolean contactExists(AuthorId remoteAuthorId,
return db.transactionWithResult(true, txn -> contactExists(txn, a)); AuthorId localAuthorId) throws DbException {
return db.transactionWithResult(true, txn ->
contactExists(txn, remoteAuthorId, localAuthorId));
} }
@Override @Override
@@ -209,12 +232,12 @@ class ContactManagerImpl implements ContactManager {
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
if (localAuthor.getId().equals(authorId)) if (localAuthor.getId().equals(authorId))
return new AuthorInfo(OURSELVES); return new AuthorInfo(OURSELVES);
if (db.containsContact(txn, authorId)) { Collection<Contact> contacts = db.getContactsByAuthorId(txn, authorId);
Contact c = db.getContact(txn, authorId); if (contacts.isEmpty()) return new AuthorInfo(UNKNOWN);
if (c.isVerified()) return new AuthorInfo(VERIFIED, c.getAlias()); if (contacts.size() > 1) throw new AssertionError();
else return new AuthorInfo(UNVERIFIED, c.getAlias()); Contact c = contacts.iterator().next();
} if (c.isVerified()) return new AuthorInfo(VERIFIED, c.getAlias());
return new AuthorInfo(UNKNOWN); else return new AuthorInfo(UNVERIFIED, c.getAlias());
} }
} }

View File

@@ -4,22 +4,18 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.TransportCrypto; import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.plugin.TransportId; 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.IncomingKeys;
import org.briarproject.bramble.api.transport.OutgoingKeys; import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.TransportKeys; 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.Digest;
import org.spongycastle.crypto.digests.Blake2bDigest; import org.spongycastle.crypto.digests.Blake2bDigest;
import javax.inject.Inject; 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_HEADER_LABEL;
import static org.briarproject.bramble.api.transport.TransportConstants.ALICE_TAG_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_HEADER_LABEL;
import static org.briarproject.bramble.api.transport.TransportConstants.BOB_TAG_LABEL; import static org.briarproject.bramble.api.transport.TransportConstants.BOB_TAG_LABEL;
import static org.briarproject.bramble.api.transport.TransportConstants.ROTATE_LABEL; import static org.briarproject.bramble.api.transport.TransportConstants.ROTATE_LABEL;
@@ -28,9 +24,6 @@ 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.INT_64_BYTES;
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED; 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.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 { class TransportCryptoImpl implements TransportCrypto {
@@ -43,44 +36,45 @@ class TransportCryptoImpl implements TransportCrypto {
@Override @Override
public TransportKeys deriveTransportKeys(TransportId t, public TransportKeys deriveTransportKeys(TransportId t,
SecretKey rootKey, long timePeriod, boolean weAreAlice, SecretKey master, long rotationPeriod, boolean alice,
boolean active) { boolean active) {
// Keys for the previous period are derived from the root key // Keys for the previous period are derived from the master secret
SecretKey inTagPrev = deriveTagKey(rootKey, t, !weAreAlice); SecretKey inTagPrev = deriveTagKey(master, t, !alice);
SecretKey inHeaderPrev = deriveHeaderKey(rootKey, t, !weAreAlice); SecretKey inHeaderPrev = deriveHeaderKey(master, t, !alice);
SecretKey outTagPrev = deriveTagKey(rootKey, t, weAreAlice); SecretKey outTagPrev = deriveTagKey(master, t, alice);
SecretKey outHeaderPrev = deriveHeaderKey(rootKey, t, weAreAlice); SecretKey outHeaderPrev = deriveHeaderKey(master, t, alice);
// Derive the keys for the current and next periods // Derive the keys for the current and next periods
SecretKey inTagCurr = rotateKey(inTagPrev, timePeriod); SecretKey inTagCurr = rotateKey(inTagPrev, rotationPeriod);
SecretKey inHeaderCurr = rotateKey(inHeaderPrev, timePeriod); SecretKey inHeaderCurr = rotateKey(inHeaderPrev, rotationPeriod);
SecretKey inTagNext = rotateKey(inTagCurr, timePeriod + 1); SecretKey inTagNext = rotateKey(inTagCurr, rotationPeriod + 1);
SecretKey inHeaderNext = rotateKey(inHeaderCurr, timePeriod + 1); SecretKey inHeaderNext = rotateKey(inHeaderCurr, rotationPeriod + 1);
SecretKey outTagCurr = rotateKey(outTagPrev, timePeriod); SecretKey outTagCurr = rotateKey(outTagPrev, rotationPeriod);
SecretKey outHeaderCurr = rotateKey(outHeaderPrev, timePeriod); SecretKey outHeaderCurr = rotateKey(outHeaderPrev, rotationPeriod);
// Initialise the reordering windows and stream counters // Initialise the reordering windows and stream counters
IncomingKeys inPrev = new IncomingKeys(inTagPrev, inHeaderPrev, IncomingKeys inPrev = new IncomingKeys(inTagPrev, inHeaderPrev,
timePeriod - 1); rotationPeriod - 1);
IncomingKeys inCurr = new IncomingKeys(inTagCurr, inHeaderCurr, IncomingKeys inCurr = new IncomingKeys(inTagCurr, inHeaderCurr,
timePeriod); rotationPeriod);
IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext, IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext,
timePeriod + 1); rotationPeriod + 1);
OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr, OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr,
timePeriod, active); rotationPeriod, active);
// Collect and return the keys // Collect and return the keys
return new TransportKeys(t, inPrev, inCurr, inNext, outCurr); return new TransportKeys(t, inPrev, inCurr, inNext, outCurr);
} }
@Override @Override
public TransportKeys rotateTransportKeys(TransportKeys k, long timePeriod) { public TransportKeys rotateTransportKeys(TransportKeys k,
if (k.getTimePeriod() >= timePeriod) return k; long rotationPeriod) {
if (k.getRotationPeriod() >= rotationPeriod) return k;
IncomingKeys inPrev = k.getPreviousIncomingKeys(); IncomingKeys inPrev = k.getPreviousIncomingKeys();
IncomingKeys inCurr = k.getCurrentIncomingKeys(); IncomingKeys inCurr = k.getCurrentIncomingKeys();
IncomingKeys inNext = k.getNextIncomingKeys(); IncomingKeys inNext = k.getNextIncomingKeys();
OutgoingKeys outCurr = k.getCurrentOutgoingKeys(); OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
long startPeriod = outCurr.getTimePeriod(); long startPeriod = outCurr.getRotationPeriod();
boolean active = outCurr.isActive(); boolean active = outCurr.isActive();
// Rotate the keys // Rotate the keys
for (long p = startPeriod + 1; p <= timePeriod; p++) { for (long p = startPeriod + 1; p <= rotationPeriod; p++) {
inPrev = inCurr; inPrev = inCurr;
inCurr = inNext; inCurr = inNext;
SecretKey inNextTag = rotateKey(inNext.getTagKey(), p + 1); SecretKey inNextTag = rotateKey(inNext.getTagKey(), p + 1);
@@ -95,117 +89,24 @@ class TransportCryptoImpl implements TransportCrypto {
outCurr); outCurr);
} }
private SecretKey rotateKey(SecretKey k, long timePeriod) { private SecretKey rotateKey(SecretKey k, long rotationPeriod) {
byte[] period = new byte[INT_64_BYTES]; byte[] period = new byte[INT_64_BYTES];
writeUint64(timePeriod, period, 0); ByteUtils.writeUint64(rotationPeriod, period, 0);
return crypto.deriveKey(ROTATE_LABEL, k, period); return crypto.deriveKey(ROTATE_LABEL, k, period);
} }
private SecretKey deriveTagKey(SecretKey rootKey, TransportId t, private SecretKey deriveTagKey(SecretKey master, TransportId t,
boolean keyBelongsToAlice) { boolean alice) {
String label = keyBelongsToAlice ? ALICE_TAG_LABEL : BOB_TAG_LABEL; String label = alice ? ALICE_TAG_LABEL : BOB_TAG_LABEL;
byte[] id = toUtf8(t.getString()); byte[] id = StringUtils.toUtf8(t.getString());
return crypto.deriveKey(label, rootKey, id); return crypto.deriveKey(label, master, id);
} }
private SecretKey deriveHeaderKey(SecretKey rootKey, TransportId t, private SecretKey deriveHeaderKey(SecretKey master, TransportId t,
boolean keyBelongsToAlice) { boolean alice) {
String label = keyBelongsToAlice ? ALICE_HEADER_LABEL : String label = alice ? ALICE_HEADER_LABEL : BOB_HEADER_LABEL;
BOB_HEADER_LABEL; byte[] id = StringUtils.toUtf8(t.getString());
byte[] id = toUtf8(t.getString()); return crypto.deriveKey(label, master, id);
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 @Override
@@ -224,14 +125,14 @@ class TransportCryptoImpl implements TransportCrypto {
// The input is the protocol version as a 16-bit integer, followed by // The input is the protocol version as a 16-bit integer, followed by
// the stream number as a 64-bit integer // the stream number as a 64-bit integer
byte[] protocolVersionBytes = new byte[INT_16_BYTES]; byte[] protocolVersionBytes = new byte[INT_16_BYTES];
writeUint16(protocolVersion, protocolVersionBytes, 0); ByteUtils.writeUint16(protocolVersion, protocolVersionBytes, 0);
prf.update(protocolVersionBytes, 0, protocolVersionBytes.length); prf.update(protocolVersionBytes, 0, protocolVersionBytes.length);
byte[] streamNumberBytes = new byte[INT_64_BYTES]; byte[] streamNumberBytes = new byte[INT_64_BYTES];
writeUint64(streamNumber, streamNumberBytes, 0); ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
prf.update(streamNumberBytes, 0, streamNumberBytes.length); prf.update(streamNumberBytes, 0, streamNumberBytes.length);
byte[] mac = new byte[macLength]; byte[] mac = new byte[macLength];
prf.doFinal(mac, 0); prf.doFinal(mac, 0);
// The output is the first TAG_LENGTH bytes of the MAC // The output is the first TAG_LENGTH bytes of the MAC
arraycopy(mac, 0, tag, 0, TAG_LENGTH); System.arraycopy(mac, 0, tag, 0, TAG_LENGTH);
} }
} }

View File

@@ -2,13 +2,9 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; 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.crypto.SecretKey;
import org.briarproject.bramble.api.db.DataTooNewException; import org.briarproject.bramble.api.db.DataTooNewException;
import org.briarproject.bramble.api.db.DataTooOldException; 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.DbException;
import org.briarproject.bramble.api.db.MessageDeletedException; import org.briarproject.bramble.api.db.MessageDeletedException;
import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Metadata;
@@ -27,11 +23,8 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.MessageStatus; import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.bramble.api.sync.validation.MessageState; import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.transport.HandshakeKeySet; import org.briarproject.bramble.api.transport.KeySet;
import org.briarproject.bramble.api.transport.HandshakeKeySetId; import org.briarproject.bramble.api.transport.KeySetId;
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 org.briarproject.bramble.api.transport.TransportKeys;
import java.util.Collection; import java.util.Collection;
@@ -40,14 +33,11 @@ import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
/** /**
* A low-level interface to the database ({@link DatabaseComponent} provides a * A low-level interface to the database (DatabaseComponent provides a
* high-level interface). * high-level interface). Most operations take a transaction argument, which is
* <p/> * obtained by calling {@link #startTransaction()}. Every transaction must be
* Most operations take a transaction argument, which is obtained by calling * terminated by calling either {@link #abortTransaction(Object) abortTransaction(T)} or
* {@link #startTransaction()}. Every transaction must be terminated by calling * {@link #commitTransaction(Object) commitTransaction(T)}, even if an exception is thrown.
* either {@link #abortTransaction(Object) abortTransaction(T)} or
* {@link #commitTransaction(Object) commitTransaction(T)}, even if an
* exception is thrown.
*/ */
@NotNullByDefault @NotNullByDefault
interface Database<T> { interface Database<T> {
@@ -87,10 +77,11 @@ interface Database<T> {
void commitTransaction(T txn) throws DbException; void commitTransaction(T txn) throws DbException;
/** /**
* Stores a contact with the given pseudonym and returns an ID for the * Stores a contact associated with the given local and remote pseudonyms,
* contact. * and returns an ID for the contact.
*/ */
ContactId addContact(T txn, Author a, boolean verified) throws DbException; ContactId addContact(T txn, Author remote, AuthorId local, boolean verified,
boolean active) throws DbException;
/** /**
* Stores a group. * Stores a group.
@@ -104,20 +95,6 @@ interface Database<T> {
void addGroupVisibility(T txn, ContactId c, GroupId g, boolean shared) void addGroupVisibility(T txn, ContactId c, GroupId g, boolean shared)
throws DbException; 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. * Stores a local pseudonym.
*/ */
@@ -144,11 +121,6 @@ interface Database<T> {
*/ */
void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException; 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. * Stores a transport.
*/ */
@@ -159,15 +131,17 @@ interface Database<T> {
* Stores the given transport keys for the given contact and returns a * Stores the given transport keys for the given contact and returns a
* key set ID. * key set ID.
*/ */
TransportKeySetId addTransportKeys(T txn, ContactId c, TransportKeys k) KeySetId addTransportKeys(T txn, ContactId c, TransportKeys k)
throws DbException; throws DbException;
/** /**
* Returns true if the database contains the given contact. * Returns true if the database contains the given contact for the given
* local pseudonym.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
boolean containsContact(T txn, AuthorId a) throws DbException; boolean containsContact(T txn, AuthorId remote, AuthorId local)
throws DbException;
/** /**
* Returns true if the database contains the given contact. * Returns true if the database contains the given contact.
@@ -197,14 +171,6 @@ interface Database<T> {
*/ */
boolean containsMessage(T txn, MessageId m) throws DbException; 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. * Returns true if the database contains the given transport.
* <p/> * <p/>
@@ -249,13 +215,6 @@ interface Database<T> {
*/ */
Contact getContact(T txn, ContactId c) throws DbException; 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. * Returns all contacts.
* <p/> * <p/>
@@ -263,6 +222,28 @@ interface Database<T> {
*/ */
Collection<Contact> getContacts(T txn) throws DbException; 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. * Returns the group with the given ID.
* <p/> * <p/>
@@ -303,14 +284,6 @@ interface Database<T> {
Map<ContactId, Boolean> getGroupVisibility(T txn, GroupId g) Map<ContactId, Boolean> getGroupVisibility(T txn, GroupId g)
throws DbException; 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. * Returns the local pseudonym with the given ID.
* <p/> * <p/>
@@ -501,13 +474,6 @@ interface Database<T> {
*/ */
long getNextSendTime(T txn, ContactId c) throws DbException; 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 * 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 * given contact and have been requested by the contact, up to the given
@@ -530,19 +496,13 @@ interface Database<T> {
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<TransportKeySet> getTransportKeys(T txn, TransportId t) Collection<KeySet> 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; throws DbException;
/** /**
* Increments the outgoing stream counter for the given transport keys. * Increments the outgoing stream counter for the given transport keys.
*/ */
void incrementStreamCounter(T txn, TransportId t, TransportKeySetId k) void incrementStreamCounter(T txn, TransportId t, KeySetId k)
throws DbException; throws DbException;
/** /**
@@ -611,12 +571,6 @@ interface Database<T> {
void removeGroupVisibility(T txn, ContactId c, GroupId g) void removeGroupVisibility(T txn, ContactId c, GroupId g)
throws DbException; 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. * Removes a local pseudonym (and all associated state) from the database.
*/ */
@@ -634,11 +588,6 @@ interface Database<T> {
void removeOfferedMessages(T txn, ContactId c, void removeOfferedMessages(T txn, ContactId c,
Collection<MessageId> requested) throws DbException; 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. * Removes a transport (and all associated state) from the database.
*/ */
@@ -647,7 +596,7 @@ interface Database<T> {
/** /**
* Removes the given transport keys from the database. * Removes the given transport keys from the database.
*/ */
void removeTransportKeys(T txn, TransportId t, TransportKeySetId k) void removeTransportKeys(T txn, TransportId t, KeySetId k)
throws DbException; throws DbException;
/** /**
@@ -661,6 +610,12 @@ interface Database<T> {
*/ */
void setContactVerified(T txn, ContactId c) throws DbException; 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. * Sets an alias name for a contact.
*/ */
@@ -686,29 +641,16 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Sets the state of the given pending contact. * Sets the reordering window for the given key set and transport in the
* given rotation period.
*/ */
void setPendingContactState(T txn, PendingContactId p, void setReorderingWindow(T txn, KeySetId k, TransportId t,
PendingContactState state) throws DbException; long rotationPeriod, long base, byte[] bitmap) 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. * Marks the given transport keys as usable for outgoing streams.
*/ */
void setTransportKeysActive(T txn, TransportId t, TransportKeySetId k) void setTransportKeysActive(T txn, TransportId t, KeySetId k)
throws DbException; throws DbException;
/** /**
@@ -719,13 +661,8 @@ interface Database<T> {
void updateExpiryTimeAndEta(T txn, ContactId c, MessageId m, int maxLatency) void updateExpiryTimeAndEta(T txn, ContactId c, MessageId m, int maxLatency)
throws DbException; throws DbException;
/**
* Updates the given handshake keys.
*/
void updateHandshakeKeys(T txn, HandshakeKeySet ks) throws DbException;
/** /**
* Updates the given transport keys following key rotation. * Updates the given transport keys following key rotation.
*/ */
void updateTransportKeys(T txn, TransportKeySet ks) throws DbException; void updateTransportKeys(T txn, KeySet ks) throws DbException;
} }

View File

@@ -2,34 +2,27 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; 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.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; 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.contact.event.ContactVerifiedEvent;
import org.briarproject.bramble.api.crypto.SecretKey; 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.ContactExistsException;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbCallable; import org.briarproject.bramble.api.db.DbCallable;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.DbRunnable; 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.Metadata;
import org.briarproject.bramble.api.db.MigrationListener; import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.db.NoSuchContactException; import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.NoSuchGroupException; import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException; import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
import org.briarproject.bramble.api.db.NoSuchMessageException; 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.NoSuchTransportException;
import org.briarproject.bramble.api.db.NullableDbCallable; 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.db.Transaction;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; 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.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
@@ -62,11 +55,8 @@ import org.briarproject.bramble.api.sync.event.MessageToRequestEvent;
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent; import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
import org.briarproject.bramble.api.sync.event.MessagesSentEvent; import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
import org.briarproject.bramble.api.sync.validation.MessageState; import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.transport.HandshakeKeySet; import org.briarproject.bramble.api.transport.KeySet;
import org.briarproject.bramble.api.transport.HandshakeKeySetId; import org.briarproject.bramble.api.transport.KeySetId;
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 org.briarproject.bramble.api.transport.TransportKeys;
import java.util.ArrayList; import java.util.ArrayList;
@@ -74,7 +64,6 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -103,29 +92,25 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
private final Database<T> db; private final Database<T> db;
private final Class<T> txnClass; private final Class<T> txnClass;
private final EventBus eventBus; private final EventBus eventBus;
private final Executor eventExecutor; private final ShutdownManager shutdown;
private final ShutdownManager shutdownManager;
private final AtomicBoolean closed = new AtomicBoolean(false); private final AtomicBoolean closed = new AtomicBoolean(false);
private final ReentrantReadWriteLock lock = private final ReentrantReadWriteLock lock =
new ReentrantReadWriteLock(true); new ReentrantReadWriteLock(true);
private final Visitor visitor = new CommitActionVisitor();
@Inject @Inject
DatabaseComponentImpl(Database<T> db, Class<T> txnClass, EventBus eventBus, DatabaseComponentImpl(Database<T> db, Class<T> txnClass, EventBus eventBus,
@EventExecutor Executor eventExecutor, ShutdownManager shutdown) {
ShutdownManager shutdownManager) {
this.db = db; this.db = db;
this.txnClass = txnClass; this.txnClass = txnClass;
this.eventBus = eventBus; this.eventBus = eventBus;
this.eventExecutor = eventExecutor; this.shutdown = shutdown;
this.shutdownManager = shutdownManager;
} }
@Override @Override
public boolean open(SecretKey key, @Nullable MigrationListener listener) public boolean open(SecretKey key, @Nullable MigrationListener listener)
throws DbException { throws DbException {
boolean reopened = db.open(key, listener); boolean reopened = db.open(key, listener);
shutdownManager.addShutdownHook(() -> { shutdown.addShutdownHook(() -> {
try { try {
close(); close();
} catch (DbException e) { } catch (DbException e) {
@@ -175,16 +160,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
public void endTransaction(Transaction transaction) { public void endTransaction(Transaction transaction) {
try { try {
T txn = txnClass.cast(transaction.unbox()); T txn = txnClass.cast(transaction.unbox());
if (transaction.isCommitted()) { if (!transaction.isCommitted()) db.abortTransaction(txn);
for (CommitAction a : transaction.getActions())
a.accept(visitor);
} else {
db.abortTransaction(txn);
}
} finally { } finally {
if (transaction.isReadOnly()) lock.readLock().unlock(); if (transaction.isReadOnly()) lock.readLock().unlock();
else lock.writeLock().unlock(); else lock.writeLock().unlock();
} }
if (transaction.isCommitted())
for (Event e : transaction.getEvents()) eventBus.broadcast(e);
} }
@Override @Override
@@ -232,16 +214,20 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public ContactId addContact(Transaction transaction, Author a, public ContactId addContact(Transaction transaction, Author remote,
boolean verified) throws DbException { AuthorId local, boolean verified, boolean active)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (db.containsLocalAuthor(txn, a.getId())) if (!db.containsLocalAuthor(txn, local))
throw new NoSuchLocalAuthorException();
if (db.containsLocalAuthor(txn, remote.getId()))
throw new ContactExistsException(); throw new ContactExistsException();
if (db.containsContact(txn, a.getId())) if (db.containsContact(txn, remote.getId(), local))
throw new ContactExistsException(); throw new ContactExistsException();
ContactId c = db.addContact(txn, a, verified); ContactId c = db.addContact(txn, remote, local, verified, active);
transaction.attach(new ContactAddedEvent(c)); transaction.attach(new ContactAddedEvent(c, active));
if (active) transaction.attach(new ContactStatusChangedEvent(c, true));
return c; return c;
} }
@@ -255,30 +241,6 @@ 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 @Override
public void addLocalAuthor(Transaction transaction, LocalAuthor a) public void addLocalAuthor(Transaction transaction, LocalAuthor a)
throws DbException { throws DbException {
@@ -307,16 +269,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.mergeMessageMetadata(txn, m.getId(), meta); 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 @Override
public void addTransport(Transaction transaction, TransportId t, public void addTransport(Transaction transaction, TransportId t,
int maxLatency) throws DbException { int maxLatency) throws DbException {
@@ -327,8 +279,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public TransportKeySetId addTransportKeys(Transaction transaction, public KeySetId addTransportKeys(Transaction transaction, ContactId c,
ContactId c, TransportKeys k) throws DbException { TransportKeys k) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
@@ -339,10 +291,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public boolean containsContact(Transaction transaction, AuthorId a) public boolean containsContact(Transaction transaction, AuthorId remote,
throws DbException { AuthorId local) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.containsContact(txn, a); if (!db.containsLocalAuthor(txn, local))
throw new NoSuchLocalAuthorException();
return db.containsContact(txn, remote, local);
} }
@Override @Override
@@ -359,13 +313,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.containsLocalAuthor(txn, local); 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 @Override
public void deleteMessage(Transaction transaction, MessageId m) public void deleteMessage(Transaction transaction, MessageId m)
throws DbException { throws DbException {
@@ -482,15 +429,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getContact(txn, c); 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 @Override
public Collection<Contact> getContacts(Transaction transaction) public Collection<Contact> getContacts(Transaction transaction)
throws DbException { throws DbException {
@@ -498,6 +436,22 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getContacts(txn); 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 @Override
public Group getGroup(Transaction transaction, GroupId g) public Group getGroup(Transaction transaction, GroupId g)
throws DbException { throws DbException {
@@ -532,15 +486,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getGroupVisibility(txn, c, g); 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 @Override
public LocalAuthor getLocalAuthor(Transaction transaction, AuthorId a) public LocalAuthor getLocalAuthor(Transaction transaction, AuthorId a)
throws DbException { throws DbException {
@@ -698,13 +643,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getNextSendTime(txn, c); return db.getNextSendTime(txn, c);
} }
@Override
public Collection<PendingContact> getPendingContacts(
Transaction transaction) throws DbException {
T txn = unbox(transaction);
return db.getPendingContacts(txn);
}
@Override @Override
public Settings getSettings(Transaction transaction, String namespace) public Settings getSettings(Transaction transaction, String namespace)
throws DbException { throws DbException {
@@ -713,7 +651,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public Collection<TransportKeySet> getTransportKeys(Transaction transaction, public Collection<KeySet> getTransportKeys(Transaction transaction,
TransportId t) throws DbException { TransportId t) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsTransport(txn, t)) if (!db.containsTransport(txn, t))
@@ -723,17 +661,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Override @Override
public void incrementStreamCounter(Transaction transaction, TransportId t, public void incrementStreamCounter(Transaction transaction, TransportId t,
HandshakeKeySetId k) throws DbException { KeySetId 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(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsTransport(txn, t)) if (!db.containsTransport(txn, t))
@@ -882,16 +810,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new GroupVisibilityUpdatedEvent(affected)); 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 @Override
public void removeLocalAuthor(Transaction transaction, AuthorId a) public void removeLocalAuthor(Transaction transaction, AuthorId a)
throws DbException { throws DbException {
@@ -914,16 +832,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.removeMessage(txn, m); 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 @Override
public void removeTransport(Transaction transaction, TransportId t) public void removeTransport(Transaction transaction, TransportId t)
throws DbException { throws DbException {
@@ -936,7 +844,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Override @Override
public void removeTransportKeys(Transaction transaction, public void removeTransportKeys(Transaction transaction,
TransportId t, TransportKeySetId k) throws DbException { TransportId t, KeySetId k) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsTransport(txn, t)) if (!db.containsTransport(txn, t))
@@ -955,6 +863,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new ContactVerifiedEvent(c)); 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 @Override
public void setContactAlias(Transaction transaction, ContactId c, public void setContactAlias(Transaction transaction, ContactId c,
@Nullable String alias) throws DbException { @Nullable String alias) throws DbException {
@@ -1024,30 +943,19 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public void setReorderingWindow(Transaction transaction, public void setReorderingWindow(Transaction transaction, KeySetId k,
TransportKeySetId k, TransportId t, long timePeriod, long base, TransportId t, long rotationPeriod, long base, byte[] bitmap)
byte[] bitmap) throws DbException { throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsTransport(txn, t)) if (!db.containsTransport(txn, t))
throw new NoSuchTransportException(); throw new NoSuchTransportException();
db.setReorderingWindow(txn, k, t, timePeriod, base, bitmap); db.setReorderingWindow(txn, k, t, rotationPeriod, 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 @Override
public void setTransportKeysActive(Transaction transaction, TransportId t, public void setTransportKeysActive(Transaction transaction, TransportId t,
TransportKeySetId k) throws DbException { KeySetId k) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsTransport(txn, t)) if (!db.containsTransport(txn, t))
@@ -1055,40 +963,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.setTransportKeysActive(txn, t, k); db.setTransportKeysActive(txn, t, k);
} }
@Override
public void updateHandshakeKeys(Transaction transaction,
Collection<HandshakeKeySet> keys) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
for (HandshakeKeySet ks : keys) {
TransportId t = ks.getKeys().getTransportId();
if (db.containsTransport(txn, t))
db.updateHandshakeKeys(txn, ks);
}
}
@Override @Override
public void updateTransportKeys(Transaction transaction, public void updateTransportKeys(Transaction transaction,
Collection<TransportKeySet> keys) throws DbException { Collection<KeySet> keys) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
for (TransportKeySet ks : keys) { for (KeySet ks : keys) {
TransportId t = ks.getKeys().getTransportId(); TransportId t = ks.getTransportKeys().getTransportId();
if (db.containsTransport(txn, t)) if (db.containsTransport(txn, t))
db.updateTransportKeys(txn, ks); 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());
}
}
} }

View File

@@ -3,13 +3,11 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.event.EventBus; 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.lifecycle.ShutdownManager;
import org.briarproject.bramble.api.sync.MessageFactory; import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import java.sql.Connection; import java.sql.Connection;
import java.util.concurrent.Executor;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -29,9 +27,8 @@ public class DatabaseModule {
@Provides @Provides
@Singleton @Singleton
DatabaseComponent provideDatabaseComponent(Database<Connection> db, DatabaseComponent provideDatabaseComponent(Database<Connection> db,
EventBus eventBus, @EventExecutor Executor eventExecutor, EventBus eventBus, ShutdownManager shutdown) {
ShutdownManager shutdownManager) {
return new DatabaseComponentImpl<>(db, Connection.class, eventBus, return new DatabaseComponentImpl<>(db, Connection.class, eventBus,
eventExecutor, shutdownManager); shutdown);
} }
} }

View File

@@ -76,6 +76,30 @@ 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 @Override
protected Connection createConnection() throws SQLException { protected Connection createConnection() throws SQLException {
SecretKey key = this.key; SecretKey key = this.key;

View File

@@ -86,6 +86,30 @@ 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 @Override
protected Connection createConnection() throws SQLException { protected Connection createConnection() throws SQLException {
SecretKey key = this.key; SecretKey key = this.key;

View File

@@ -17,7 +17,7 @@ class Migration40_41 implements Migration<Connection> {
private final DatabaseTypes dbTypes; private final DatabaseTypes dbTypes;
Migration40_41(DatabaseTypes databaseTypes) { public Migration40_41(DatabaseTypes databaseTypes) {
this.dbTypes = databaseTypes; this.dbTypes = databaseTypes;
} }

View File

@@ -1,97 +0,0 @@
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);
}
}
}

View File

@@ -1,52 +0,0 @@
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);
}
}
}

View File

@@ -1,40 +0,0 @@
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);
}
}
}

View File

@@ -1,32 +0,0 @@
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;
});
}
}

View File

@@ -2,16 +2,13 @@ package org.briarproject.bramble.event;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; 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.event.EventListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
@@ -19,12 +16,6 @@ class EventBusImpl implements EventBus {
private final Collection<EventListener> listeners = private final Collection<EventListener> listeners =
new CopyOnWriteArrayList<>(); new CopyOnWriteArrayList<>();
private final Executor eventExecutor;
@Inject
EventBusImpl(@EventExecutor Executor eventExecutor) {
this.eventExecutor = eventExecutor;
}
@Override @Override
public void addListener(EventListener l) { public void addListener(EventListener l) {
@@ -38,8 +29,6 @@ class EventBusImpl implements EventBus {
@Override @Override
public void broadcast(Event e) { public void broadcast(Event e) {
eventExecutor.execute(() -> { for (EventListener l : listeners) l.eventOccurred(e);
for (EventListener l : listeners) l.eventOccurred(e);
});
} }
} }

View File

@@ -12,7 +12,7 @@ public class EventModule {
@Provides @Provides
@Singleton @Singleton
EventBus provideEventBus(EventBusImpl eventBus) { EventBus provideEventBus() {
return eventBus; return new EventBusImpl();
} }
} }

View File

@@ -14,7 +14,7 @@ import java.io.IOException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Arrays; import java.util.Arrays;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_KEY_LABEL; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_SECRET_LABEL;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; 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.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL;
@@ -90,7 +90,7 @@ class KeyAgreementProtocol {
/** /**
* Perform the BQP protocol. * Perform the BQP protocol.
* *
* @return the negotiated master key. * @return the negotiated master secret.
* @throws AbortException when the protocol may have been tampered with. * @throws AbortException when the protocol may have been tampered with.
* @throws IOException for all other other connection errors. * @throws IOException for all other other connection errors.
*/ */
@@ -115,7 +115,7 @@ class KeyAgreementProtocol {
receiveConfirm(s, theirPublicKey); receiveConfirm(s, theirPublicKey);
sendConfirm(s, theirPublicKey); sendConfirm(s, theirPublicKey);
} }
return crypto.deriveKey(MASTER_KEY_LABEL, s); return crypto.deriveKey(MASTER_SECRET_LABEL, s);
} catch (AbortException e) { } catch (AbortException e) {
sendAbort(e.getCause() != null); sendAbort(e.getCause() != null);
throw e; throw e;

View File

@@ -114,9 +114,9 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
keyAgreementCrypto, payloadEncoder, transport, remotePayload, keyAgreementCrypto, payloadEncoder, transport, remotePayload,
localPayload, localKeyPair, alice); localPayload, localKeyPair, alice);
try { try {
SecretKey masterKey = protocol.perform(); SecretKey master = protocol.perform();
KeyAgreementResult result = KeyAgreementResult result =
new KeyAgreementResult(masterKey, transport.getConnection(), new KeyAgreementResult(master, transport.getConnection(),
transport.getTransportId(), alice); transport.getTransportId(), alice);
LOG.info("Finished BQP protocol"); LOG.info("Finished BQP protocol");
// Broadcast result to caller // Broadcast result to caller

View File

@@ -1,7 +1,7 @@
package org.briarproject.bramble.plugin; package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent; import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
@@ -80,10 +80,12 @@ class Poller implements EventListener {
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof ContactAddedEvent) { if (e instanceof ContactStatusChangedEvent) {
ContactAddedEvent c = (ContactAddedEvent) e; ContactStatusChangedEvent c = (ContactStatusChangedEvent) e;
// Connect to the newly activated contact if (c.isActive()) {
connectToContact(c.getContactId()); // Connect to the newly activated contact
connectToContact(c.getContactId());
}
} else if (e instanceof ConnectionClosedEvent) { } else if (e instanceof ConnectionClosedEvent) {
ConnectionClosedEvent c = (ConnectionClosedEvent) e; ConnectionClosedEvent c = (ConnectionClosedEvent) e;
// Reschedule polling, the polling interval may have decreased // Reschedule polling, the polling interval may have decreased

View File

@@ -38,6 +38,8 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
@@ -106,7 +108,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final ResourceProvider resourceProvider; private final ResourceProvider resourceProvider;
private final int maxLatency, maxIdleTime, socketTimeout; private final int maxLatency, maxIdleTime, socketTimeout;
private final File torDirectory, torFile, geoIpFile, obfs4File, configFile; private final File torDirectory, torFile, geoIpFile, obfs4File, configFile;
private final File doneFile, cookieFile; private final File doneFile, cookieFile, jtorCTLFile;
private final ConnectionStatus connectionStatus; private final ConnectionStatus connectionStatus;
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
@@ -151,6 +153,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
configFile = new File(torDirectory, "torrc"); configFile = new File(torDirectory, "torrc");
doneFile = new File(torDirectory, "done"); doneFile = new File(torDirectory, "done");
cookieFile = new File(torDirectory, ".tor/control_auth_cookie"); cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
jtorCTLFile = new File(torDirectory, "jtorctl.out");
connectionStatus = new ConnectionStatus(); connectionStatus = new ConnectionStatus();
// Don't execute more than one connection status check at a time // Don't execute more than one connection status check at a time
connectionStatusExecutor = connectionStatusExecutor =
@@ -205,6 +208,19 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} catch (SecurityException | IOException e) { } catch (SecurityException | IOException e) {
throw new PluginException(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 // Log the process's standard output until it detaches
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
Scanner stdout = new Scanner(torProcess.getInputStream()); Scanner stdout = new Scanner(torProcess.getInputStream());
@@ -249,6 +265,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
controlSocket = new Socket("127.0.0.1", CONTROL_PORT); controlSocket = new Socket("127.0.0.1", CONTROL_PORT);
controlConnection = new TorControlConnection(controlSocket); controlConnection = new TorControlConnection(controlSocket);
controlConnection.authenticate(read(cookieFile)); 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 // Tell Tor to exit when the control connection is closed
controlConnection.takeOwnership(); controlConnection.takeOwnership();
controlConnection.resetConf(Collections.singletonList(OWNER)); controlConnection.resetConf(Collections.singletonList(OWNER));
@@ -272,6 +293,38 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
bind(); 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() { private boolean assetsAreUpToDate() {
return doneFile.lastModified() > getLastUpdateTime(); return doneFile.lastModified() > getLastUpdateTime();
} }

View File

@@ -13,8 +13,6 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
@@ -46,7 +44,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
private final DatabaseComponent db; private final DatabaseComponent db;
private final ClientHelper clientHelper; private final ClientHelper clientHelper;
private final IdentityManager identityManager;
private final ClientVersioningManager clientVersioningManager; private final ClientVersioningManager clientVersioningManager;
private final MetadataParser metadataParser; private final MetadataParser metadataParser;
private final ContactGroupFactory contactGroupFactory; private final ContactGroupFactory contactGroupFactory;
@@ -56,13 +53,11 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Inject @Inject
TransportPropertyManagerImpl(DatabaseComponent db, TransportPropertyManagerImpl(DatabaseComponent db,
ClientHelper clientHelper, ClientHelper clientHelper,
IdentityManager identityManager,
ClientVersioningManager clientVersioningManager, ClientVersioningManager clientVersioningManager,
MetadataParser metadataParser, MetadataParser metadataParser,
ContactGroupFactory contactGroupFactory, Clock clock) { ContactGroupFactory contactGroupFactory, Clock clock) {
this.db = db; this.db = db;
this.clientHelper = clientHelper; this.clientHelper = clientHelper;
this.identityManager = identityManager;
this.clientVersioningManager = clientVersioningManager; this.clientVersioningManager = clientVersioningManager;
this.metadataParser = metadataParser; this.metadataParser = metadataParser;
this.contactGroupFactory = contactGroupFactory; this.contactGroupFactory = contactGroupFactory;
@@ -82,7 +77,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Override @Override
public void addingContact(Transaction txn, Contact c) throws DbException { public void addingContact(Transaction txn, Contact c) throws DbException {
// Create a group to share with the contact // Create a group to share with the contact
Group g = getContactGroup(c, getLocalAuthorId(txn)); Group g = getContactGroup(c);
db.addGroup(txn, g); db.addGroup(txn, g);
// Apply the client's visibility to the contact group // Apply the client's visibility to the contact group
Visibility client = clientVersioningManager.getClientVisibility(txn, Visibility client = clientVersioningManager.getClientVisibility(txn,
@@ -98,14 +93,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Override @Override
public void removingContact(Transaction txn, Contact c) throws DbException { public void removingContact(Transaction txn, Contact c) throws DbException {
db.removeGroup(txn, getContactGroup(c, getLocalAuthorId(txn))); db.removeGroup(txn, getContactGroup(c));
} }
@Override @Override
public void onClientVisibilityChanging(Transaction txn, Contact c, public void onClientVisibilityChanging(Transaction txn, Contact c,
Visibility v) throws DbException { Visibility v) throws DbException {
// Apply the client's visibility to the contact group // Apply the client's visibility to the contact group
Group g = getContactGroup(c, getLocalAuthorId(txn)); Group g = getContactGroup(c);
db.setGroupVisibility(txn, c.getId(), g.getId(), v); db.setGroupVisibility(txn, c.getId(), g.getId(), v);
} }
@@ -137,7 +132,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Override @Override
public void addRemoteProperties(Transaction txn, ContactId c, public void addRemoteProperties(Transaction txn, ContactId c,
Map<TransportId, TransportProperties> props) throws DbException { Map<TransportId, TransportProperties> props) throws DbException {
Group g = getContactGroup(db.getContact(txn, c), getLocalAuthorId(txn)); Group g = getContactGroup(db.getContact(txn, c));
for (Entry<TransportId, TransportProperties> e : props.entrySet()) { for (Entry<TransportId, TransportProperties> e : props.entrySet()) {
storeMessage(txn, g.getId(), e.getKey(), e.getValue(), 0, storeMessage(txn, g.getId(), e.getKey(), e.getValue(), 0,
false, false); false, false);
@@ -196,16 +191,17 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
TransportId t) throws DbException { TransportId t) throws DbException {
return db.transactionWithResult(true, txn -> { return db.transactionWithResult(true, txn -> {
Map<ContactId, TransportProperties> remote = new HashMap<>(); Map<ContactId, TransportProperties> remote = new HashMap<>();
AuthorId local = getLocalAuthorId(txn);
for (Contact c : db.getContacts(txn)) for (Contact c : db.getContacts(txn))
remote.put(c.getId(), getRemoteProperties(txn, c, local, t)); remote.put(c.getId(), getRemoteProperties(txn, c, t));
return remote; return remote;
}); });
} }
private TransportProperties getRemoteProperties(Transaction txn, Contact c, private TransportProperties getRemoteProperties(Transaction txn, Contact c,
AuthorId local, TransportId t) throws DbException { TransportId t) throws DbException {
Group g = getContactGroup(c, local); // Don't return properties for inactive contacts
if (!c.isActive()) return new TransportProperties();
Group g = getContactGroup(c);
try { try {
// Find the latest remote update // Find the latest remote update
LatestUpdate latest = findLatest(txn, g.getId(), t, false); LatestUpdate latest = findLatest(txn, g.getId(), t, false);
@@ -222,11 +218,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Override @Override
public TransportProperties getRemoteProperties(ContactId c, TransportId t) public TransportProperties getRemoteProperties(ContactId c, TransportId t)
throws DbException { throws DbException {
return db.transactionWithResult(true, txn -> { return db.transactionWithResult(true, txn ->
Contact contact = db.getContact(txn ,c); getRemoteProperties(txn, db.getContact(txn, c), t));
AuthorId local = getLocalAuthorId(txn);
return getRemoteProperties(txn, contact, local, t);
});
} }
@Override @Override
@@ -259,9 +252,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
if (latest != null) if (latest != null)
db.removeMessage(txn, latest.messageId); db.removeMessage(txn, latest.messageId);
// Store the merged properties in each contact's group // Store the merged properties in each contact's group
AuthorId localAuthorId = getLocalAuthorId(txn);
for (Contact c : db.getContacts(txn)) { for (Contact c : db.getContacts(txn)) {
Group g = getContactGroup(c, localAuthorId); Group g = getContactGroup(c);
latest = findLatest(txn, g.getId(), t, true); latest = findLatest(txn, g.getId(), t, true);
version = latest == null ? 1 : latest.version + 1; version = latest == null ? 1 : latest.version + 1;
storeMessage(txn, g.getId(), t, merged, version, storeMessage(txn, g.getId(), t, merged, version,
@@ -277,13 +269,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
} }
} }
private AuthorId getLocalAuthorId(Transaction txn) throws DbException { private Group getContactGroup(Contact c) {
return identityManager.getLocalAuthor(txn).getId();
}
private Group getContactGroup(Contact c, AuthorId local) {
return contactGroupFactory.createContactGroup(CLIENT_ID, return contactGroupFactory.createContactGroup(CLIENT_ID,
MAJOR_VERSION, c, local); MAJOR_VERSION, c);
} }
private void storeMessage(Transaction txn, GroupId g, TransportId t, private void storeMessage(Transaction txn, GroupId g, TransportId t,

View File

@@ -1,7 +1,9 @@
package org.briarproject.bramble.transport; 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.ContactId;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; 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.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
@@ -17,8 +19,8 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.transport.KeyManager; 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.StreamContext;
import org.briarproject.bramble.api.transport.TransportKeySetId;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -44,6 +46,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
private final Executor dbExecutor; private final Executor dbExecutor;
private final PluginConfig pluginConfig; private final PluginConfig pluginConfig;
private final TransportKeyManagerFactory transportKeyManagerFactory; private final TransportKeyManagerFactory transportKeyManagerFactory;
private final Map<ContactId, Boolean> activeContacts;
private final ConcurrentHashMap<TransportId, TransportKeyManager> managers; private final ConcurrentHashMap<TransportId, TransportKeyManager> managers;
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
@@ -55,6 +58,8 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.pluginConfig = pluginConfig; this.pluginConfig = pluginConfig;
this.transportKeyManagerFactory = transportKeyManagerFactory; this.transportKeyManagerFactory = transportKeyManagerFactory;
// Use a ConcurrentHashMap as a thread-safe set
activeContacts = new ConcurrentHashMap<>();
managers = new ConcurrentHashMap<>(); managers = new ConcurrentHashMap<>();
} }
@@ -68,6 +73,8 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
transports.put(f.getId(), f.getMaxLatency()); transports.put(f.getId(), f.getMaxLatency());
try { try {
db.transaction(false, txn -> { 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()) for (Entry<TransportId, Integer> e : transports.entrySet())
db.addTransport(txn, e.getKey(), e.getValue()); db.addTransport(txn, e.getKey(), e.getValue());
for (Entry<TransportId, Integer> e : transports.entrySet()) { for (Entry<TransportId, Integer> e : transports.entrySet()) {
@@ -88,22 +95,22 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
} }
@Override @Override
public Map<TransportId, TransportKeySetId> addContact(Transaction txn, public Map<TransportId, KeySetId> addContact(Transaction txn, ContactId c,
ContactId c, SecretKey rootKey, long timestamp, boolean alice, SecretKey master, long timestamp, boolean alice, boolean active)
boolean active) throws DbException { throws DbException {
Map<TransportId, TransportKeySetId> ids = new HashMap<>(); Map<TransportId, KeySetId> ids = new HashMap<>();
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) { for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
TransportId t = e.getKey(); TransportId t = e.getKey();
TransportKeyManager m = e.getValue(); TransportKeyManager m = e.getValue();
ids.put(t, m.addContact(txn, c, rootKey, timestamp, alice, active)); ids.put(t, m.addContact(txn, c, master, timestamp, alice, active));
} }
return ids; return ids;
} }
@Override @Override
public void activateKeys(Transaction txn, Map<TransportId, public void activateKeys(Transaction txn, Map<TransportId, KeySetId> keys)
TransportKeySetId> keys) throws DbException { throws DbException {
for (Entry<TransportId, TransportKeySetId> e : keys.entrySet()) { for (Entry<TransportId, KeySetId> e : keys.entrySet()) {
TransportId t = e.getKey(); TransportId t = e.getKey();
TransportKeyManager m = managers.get(t); TransportKeyManager m = managers.get(t);
if (m == null) { if (m == null) {
@@ -123,6 +130,8 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
@Override @Override
public StreamContext getStreamContext(ContactId c, TransportId t) public StreamContext getStreamContext(ContactId c, TransportId t)
throws DbException { throws DbException {
// Don't allow outgoing streams to inactive contacts
if (!activeContacts.containsKey(c)) return null;
TransportKeyManager m = managers.get(t); TransportKeyManager m = managers.get(t);
if (m == null) { if (m == null) {
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t); if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
@@ -148,10 +157,15 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof ContactRemovedEvent) { if (e instanceof ContactRemovedEvent) {
removeContact(((ContactRemovedEvent) e).getContactId()); 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) { private void removeContact(ContactId c) {
activeContacts.remove(c);
dbExecutor.execute(() -> { dbExecutor.execute(() -> {
for (TransportKeyManager m : managers.values()) m.removeContact(c); for (TransportKeyManager m : managers.values()) m.removeContact(c);
}); });

View File

@@ -11,18 +11,18 @@ import javax.annotation.concurrent.NotThreadSafe;
class MutableIncomingKeys { class MutableIncomingKeys {
private final SecretKey tagKey, headerKey; private final SecretKey tagKey, headerKey;
private final long timePeriod; private final long rotationPeriod;
private final ReorderingWindow window; private final ReorderingWindow window;
MutableIncomingKeys(IncomingKeys in) { MutableIncomingKeys(IncomingKeys in) {
tagKey = in.getTagKey(); tagKey = in.getTagKey();
headerKey = in.getHeaderKey(); headerKey = in.getHeaderKey();
timePeriod = in.getTimePeriod(); rotationPeriod = in.getRotationPeriod();
window = new ReorderingWindow(in.getWindowBase(), in.getWindowBitmap()); window = new ReorderingWindow(in.getWindowBase(), in.getWindowBitmap());
} }
IncomingKeys snapshot() { IncomingKeys snapshot() {
return new IncomingKeys(tagKey, headerKey, timePeriod, return new IncomingKeys(tagKey, headerKey, rotationPeriod,
window.getBase(), window.getBitmap()); window.getBase(), window.getBitmap());
} }
@@ -34,8 +34,8 @@ class MutableIncomingKeys {
return headerKey; return headerKey;
} }
long getTimePeriod() { long getRotationPeriod() {
return timePeriod; return rotationPeriod;
} }
ReorderingWindow getWindow() { ReorderingWindow getWindow() {

View File

@@ -1,22 +1,22 @@
package org.briarproject.bramble.transport; package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.transport.TransportKeySetId; import org.briarproject.bramble.api.transport.KeySetId;
class MutableKeySet { class MutableKeySet {
private final TransportKeySetId keySetId; private final KeySetId keySetId;
private final ContactId contactId; private final ContactId contactId;
private final MutableTransportKeys transportKeys; private final MutableTransportKeys transportKeys;
MutableKeySet(TransportKeySetId keySetId, ContactId contactId, MutableKeySet(KeySetId keySetId, ContactId contactId,
MutableTransportKeys transportKeys) { MutableTransportKeys transportKeys) {
this.keySetId = keySetId; this.keySetId = keySetId;
this.contactId = contactId; this.contactId = contactId;
this.transportKeys = transportKeys; this.transportKeys = transportKeys;
} }
TransportKeySetId getKeySetId() { KeySetId getKeySetId() {
return keySetId; return keySetId;
} }

View File

@@ -11,20 +11,20 @@ import javax.annotation.concurrent.NotThreadSafe;
class MutableOutgoingKeys { class MutableOutgoingKeys {
private final SecretKey tagKey, headerKey; private final SecretKey tagKey, headerKey;
private final long timePeriod; private final long rotationPeriod;
private long streamCounter; private long streamCounter;
private boolean active; private boolean active;
MutableOutgoingKeys(OutgoingKeys out) { MutableOutgoingKeys(OutgoingKeys out) {
tagKey = out.getTagKey(); tagKey = out.getTagKey();
headerKey = out.getHeaderKey(); headerKey = out.getHeaderKey();
timePeriod = out.getTimePeriod(); rotationPeriod = out.getRotationPeriod();
streamCounter = out.getStreamCounter(); streamCounter = out.getStreamCounter();
active = out.isActive(); active = out.isActive();
} }
OutgoingKeys snapshot() { OutgoingKeys snapshot() {
return new OutgoingKeys(tagKey, headerKey, timePeriod, return new OutgoingKeys(tagKey, headerKey, rotationPeriod,
streamCounter, active); streamCounter, active);
} }
@@ -36,8 +36,8 @@ class MutableOutgoingKeys {
return headerKey; return headerKey;
} }
long getTimePeriod() { long getRotationPeriod() {
return timePeriod; return rotationPeriod;
} }
long getStreamCounter() { long getStreamCounter() {

View File

@@ -5,8 +5,8 @@ import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; 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.StreamContext;
import org.briarproject.bramble.api.transport.TransportKeySetId;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -15,11 +15,10 @@ interface TransportKeyManager {
void start(Transaction txn) throws DbException; void start(Transaction txn) throws DbException;
TransportKeySetId addContact(Transaction txn, ContactId c, KeySetId addContact(Transaction txn, ContactId c, SecretKey master,
SecretKey rootKey, long timestamp, boolean alice, boolean active) long timestamp, boolean alice, boolean active) throws DbException;
throws DbException;
void activateKeys(Transaction txn, TransportKeySetId k) throws DbException; void activateKeys(Transaction txn, KeySetId k) throws DbException;
void removeContact(ContactId c); void removeContact(ContactId c);

View File

@@ -11,9 +11,9 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.Scheduler; 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.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.api.transport.TransportKeys;
import org.briarproject.bramble.transport.ReorderingWindow.Change; import org.briarproject.bramble.transport.ReorderingWindow.Change;
@@ -51,12 +51,12 @@ class TransportKeyManagerImpl implements TransportKeyManager {
private final ScheduledExecutorService scheduler; private final ScheduledExecutorService scheduler;
private final Clock clock; private final Clock clock;
private final TransportId transportId; private final TransportId transportId;
private final long timePeriodLength; private final long rotationPeriodLength;
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
private final ReentrantLock lock = new ReentrantLock(); private final ReentrantLock lock = new ReentrantLock();
// The following are locking: lock // The following are locking: lock
private final Map<TransportKeySetId, MutableKeySet> keys = new HashMap<>(); private final Map<KeySetId, MutableKeySet> keys = new HashMap<>();
private final Map<Bytes, TagContext> inContexts = new HashMap<>(); private final Map<Bytes, TagContext> inContexts = new HashMap<>();
private final Map<ContactId, MutableKeySet> outContexts = new HashMap<>(); private final Map<ContactId, MutableKeySet> outContexts = new HashMap<>();
@@ -70,7 +70,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
this.scheduler = scheduler; this.scheduler = scheduler;
this.clock = clock; this.clock = clock;
this.transportId = transportId; this.transportId = transportId;
timePeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE; rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE;
} }
@Override @Override
@@ -80,9 +80,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
lock.lock(); lock.lock();
try { try {
// Load the transport keys from the DB // Load the transport keys from the DB
Collection<TransportKeySet> loaded = Collection<KeySet> loaded = db.getTransportKeys(txn, transportId);
db.getTransportKeys(txn, transportId); // Rotate the keys to the current rotation period
// Rotate the keys to the current time period
RotationResult rotationResult = rotateKeys(loaded, now); RotationResult rotationResult = rotateKeys(loaded, now);
// Initialise mutable state for all contacts // Initialise mutable state for all contacts
addKeys(rotationResult.current); addKeys(rotationResult.current);
@@ -96,17 +95,15 @@ class TransportKeyManagerImpl implements TransportKeyManager {
scheduleKeyRotation(now); scheduleKeyRotation(now);
} }
private RotationResult rotateKeys(Collection<TransportKeySet> keys, private RotationResult rotateKeys(Collection<KeySet> keys, long now) {
long now) {
RotationResult rotationResult = new RotationResult(); RotationResult rotationResult = new RotationResult();
long timePeriod = now / timePeriodLength; long rotationPeriod = now / rotationPeriodLength;
for (TransportKeySet ks : keys) { for (KeySet ks : keys) {
TransportKeys k = ks.getKeys(); TransportKeys k = ks.getTransportKeys();
TransportKeys k1 = transportCrypto.rotateTransportKeys(k, TransportKeys k1 =
timePeriod); transportCrypto.rotateTransportKeys(k, rotationPeriod);
TransportKeySet ks1 = new TransportKeySet(ks.getKeySetId(), KeySet ks1 = new KeySet(ks.getKeySetId(), ks.getContactId(), k1);
ks.getContactId(), k1); if (k1.getRotationPeriod() > k.getRotationPeriod())
if (k1.getTimePeriod() > k.getTimePeriod())
rotationResult.rotated.add(ks1); rotationResult.rotated.add(ks1);
rotationResult.current.add(ks1); rotationResult.current.add(ks1);
} }
@@ -114,15 +111,15 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
// Locking: lock // Locking: lock
private void addKeys(Collection<TransportKeySet> keys) { private void addKeys(Collection<KeySet> keys) {
for (TransportKeySet ks : keys) { for (KeySet ks : keys) {
addKeys(ks.getKeySetId(), ks.getContactId(), addKeys(ks.getKeySetId(), ks.getContactId(),
new MutableTransportKeys(ks.getKeys())); new MutableTransportKeys(ks.getTransportKeys()));
} }
} }
// Locking: lock // Locking: lock
private void addKeys(TransportKeySetId keySetId, ContactId contactId, private void addKeys(KeySetId keySetId, ContactId contactId,
MutableTransportKeys m) { MutableTransportKeys m) {
MutableKeySet ks = new MutableKeySet(keySetId, contactId, m); MutableKeySet ks = new MutableKeySet(keySetId, contactId, m);
keys.put(keySetId, ks); keys.put(keySetId, ks);
@@ -133,7 +130,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
// Locking: lock // Locking: lock
private void encodeTags(TransportKeySetId keySetId, ContactId contactId, private void encodeTags(KeySetId keySetId, ContactId contactId,
MutableIncomingKeys inKeys) { MutableIncomingKeys inKeys) {
for (long streamNumber : inKeys.getWindow().getUnseen()) { for (long streamNumber : inKeys.getWindow().getUnseen()) {
TagContext tagCtx = TagContext tagCtx =
@@ -158,7 +155,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
private void scheduleKeyRotation(long now) { private void scheduleKeyRotation(long now) {
long delay = timePeriodLength - now % timePeriodLength; long delay = rotationPeriodLength - now % rotationPeriodLength;
scheduler.schedule((Runnable) this::rotateKeys, delay, MILLISECONDS); scheduler.schedule((Runnable) this::rotateKeys, delay, MILLISECONDS);
} }
@@ -173,21 +170,20 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
@Override @Override
public TransportKeySetId addContact(Transaction txn, ContactId c, public KeySetId addContact(Transaction txn, ContactId c, SecretKey master,
SecretKey rootKey, long timestamp, boolean alice, boolean active) long timestamp, boolean alice, boolean active) throws DbException {
throws DbException {
lock.lock(); lock.lock();
try { try {
// Work out what time period the timestamp belongs to // Work out what rotation period the timestamp belongs to
long timePeriod = timestamp / timePeriodLength; long rotationPeriod = timestamp / rotationPeriodLength;
// Derive the transport keys // Derive the transport keys
TransportKeys k = transportCrypto.deriveTransportKeys(transportId, TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
rootKey, timePeriod, alice, active); master, rotationPeriod, alice, active);
// Rotate the keys to the current time period if necessary // Rotate the keys to the current rotation period if necessary
timePeriod = clock.currentTimeMillis() / timePeriodLength; rotationPeriod = clock.currentTimeMillis() / rotationPeriodLength;
k = transportCrypto.rotateTransportKeys(k, timePeriod); k = transportCrypto.rotateTransportKeys(k, rotationPeriod);
// Write the keys back to the DB // Write the keys back to the DB
TransportKeySetId keySetId = db.addTransportKeys(txn, c, k); KeySetId keySetId = db.addTransportKeys(txn, c, k);
// Initialise mutable state for the contact // Initialise mutable state for the contact
addKeys(keySetId, c, new MutableTransportKeys(k)); addKeys(keySetId, c, new MutableTransportKeys(k));
return keySetId; return keySetId;
@@ -197,8 +193,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
@Override @Override
public void activateKeys(Transaction txn, TransportKeySetId k) public void activateKeys(Transaction txn, KeySetId k) throws DbException {
throws DbException {
lock.lock(); lock.lock();
try { try {
MutableKeySet ks = keys.get(k); MutableKeySet ks = keys.get(k);
@@ -305,7 +300,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
// Write the window back to the DB // Write the window back to the DB
db.setReorderingWindow(txn, tagCtx.keySetId, transportId, db.setReorderingWindow(txn, tagCtx.keySetId, transportId,
inKeys.getTimePeriod(), window.getBase(), inKeys.getRotationPeriod(), window.getBase(),
window.getBitmap()); window.getBitmap());
// If the outgoing keys are inactive, activate them // If the outgoing keys are inactive, activate them
MutableKeySet ks = keys.get(tagCtx.keySetId); MutableKeySet ks = keys.get(tagCtx.keySetId);
@@ -327,11 +322,11 @@ class TransportKeyManagerImpl implements TransportKeyManager {
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
lock.lock(); lock.lock();
try { try {
// Rotate the keys to the current time period // Rotate the keys to the current rotation period
Collection<TransportKeySet> snapshot = new ArrayList<>(keys.size()); Collection<KeySet> snapshot = new ArrayList<>(keys.size());
for (MutableKeySet ks : keys.values()) { for (MutableKeySet ks : keys.values()) {
snapshot.add(new TransportKeySet(ks.getKeySetId(), snapshot.add(new KeySet(ks.getKeySetId(), ks.getContactId(),
ks.getContactId(), ks.getTransportKeys().snapshot())); ks.getTransportKeys().snapshot()));
} }
RotationResult rotationResult = rotateKeys(snapshot, now); RotationResult rotationResult = rotateKeys(snapshot, now);
// Rebuild the mutable state for all contacts // Rebuild the mutable state for all contacts
@@ -351,12 +346,12 @@ class TransportKeyManagerImpl implements TransportKeyManager {
private static class TagContext { private static class TagContext {
private final TransportKeySetId keySetId; private final KeySetId keySetId;
private final ContactId contactId; private final ContactId contactId;
private final MutableIncomingKeys inKeys; private final MutableIncomingKeys inKeys;
private final long streamNumber; private final long streamNumber;
private TagContext(TransportKeySetId keySetId, ContactId contactId, private TagContext(KeySetId keySetId, ContactId contactId,
MutableIncomingKeys inKeys, long streamNumber) { MutableIncomingKeys inKeys, long streamNumber) {
this.keySetId = keySetId; this.keySetId = keySetId;
this.contactId = contactId; this.contactId = contactId;
@@ -367,7 +362,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
private static class RotationResult { private static class RotationResult {
private final Collection<TransportKeySet> current = new ArrayList<>(); private final Collection<KeySet> current = new ArrayList<>();
private final Collection<TransportKeySet> rotated = new ArrayList<>(); private final Collection<KeySet> rotated = new ArrayList<>();
} }
} }

View File

@@ -12,8 +12,6 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction; 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.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException; import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -60,7 +58,6 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
private final DatabaseComponent db; private final DatabaseComponent db;
private final ClientHelper clientHelper; private final ClientHelper clientHelper;
private final IdentityManager identityManager;
private final ContactGroupFactory contactGroupFactory; private final ContactGroupFactory contactGroupFactory;
private final Clock clock; private final Clock clock;
private final Group localGroup; private final Group localGroup;
@@ -71,11 +68,9 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
@Inject @Inject
ClientVersioningManagerImpl(DatabaseComponent db, ClientHelper clientHelper, ClientVersioningManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
IdentityManager identityManager,
ContactGroupFactory contactGroupFactory, Clock clock) { ContactGroupFactory contactGroupFactory, Clock clock) {
this.db = db; this.db = db;
this.clientHelper = clientHelper; this.clientHelper = clientHelper;
this.identityManager = identityManager;
this.contactGroupFactory = contactGroupFactory; this.contactGroupFactory = contactGroupFactory;
this.clock = clock; this.clock = clock;
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID, localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
@@ -159,7 +154,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
@Override @Override
public void addingContact(Transaction txn, Contact c) throws DbException { public void addingContact(Transaction txn, Contact c) throws DbException {
// Create a group and share it with the contact // Create a group and share it with the contact
Group g = getContactGroup(c, getLocalAuthorId(txn)); Group g = getContactGroup(c);
db.addGroup(txn, g); db.addGroup(txn, g);
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
// Attach the contact ID to the group // Attach the contact ID to the group
@@ -178,7 +173,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
@Override @Override
public void removingContact(Transaction txn, Contact c) throws DbException { public void removingContact(Transaction txn, Contact c) throws DbException {
db.removeGroup(txn, getContactGroup(c, getLocalAuthorId(txn))); db.removeGroup(txn, getContactGroup(c));
} }
@Override @Override
@@ -313,7 +308,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
List<ClientVersion> versions) throws DbException { List<ClientVersion> versions) throws DbException {
try { try {
// Find the latest local and remote updates // Find the latest local and remote updates
Group g = getContactGroup(c, getLocalAuthorId(txn)); Group g = getContactGroup(c);
LatestUpdates latest = findLatestUpdates(txn, g.getId()); LatestUpdates latest = findLatestUpdates(txn, g.getId());
// Load and parse the latest local update // Load and parse the latest local update
if (latest.local == null) throw new DbException(); if (latest.local == null) throw new DbException();
@@ -349,20 +344,16 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
} }
} }
private AuthorId getLocalAuthorId(Transaction txn) throws DbException { private Group getContactGroup(Contact c) {
return identityManager.getLocalAuthor(txn).getId();
}
private Group getContactGroup(Contact c, AuthorId local) {
return contactGroupFactory.createContactGroup(CLIENT_ID, return contactGroupFactory.createContactGroup(CLIENT_ID,
MAJOR_VERSION, c, local); MAJOR_VERSION, c);
} }
@Nullable @Nullable
private LatestUpdates findLatestUpdates(Transaction txn, ContactId c) private LatestUpdates findLatestUpdates(Transaction txn, ContactId c)
throws DbException, FormatException { throws DbException, FormatException {
Contact contact = db.getContact(txn, c); Contact contact = db.getContact(txn, c);
Group g = getContactGroup(contact, getLocalAuthorId(txn)); Group g = getContactGroup(contact);
// Contact may be in the process of being added or removed, so // Contact may be in the process of being added or removed, so
// contact group may not exist // contact group may not exist
if (!db.containsGroup(txn, g.getId())) return null; if (!db.containsGroup(txn, g.getId())) return null;

View File

@@ -5,20 +5,27 @@ import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent; 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.db.Transaction;
import org.briarproject.bramble.api.identity.Author; 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.AuthorInfo;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations;
import org.jmock.Mockery; import org.jmock.Mockery;
import org.junit.Test; import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Random; import java.util.Random;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; 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.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
@@ -26,8 +33,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.UNVERIFIED;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED; 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.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; 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.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -42,40 +49,42 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
private final IdentityManager identityManager = private final IdentityManager identityManager =
context.mock(IdentityManager.class); context.mock(IdentityManager.class);
private final ContactManager contactManager; private final ContactManager contactManager;
private final Author author = getAuthor(); private final ContactId contactId = new ContactId(42);
private final Author remote = getAuthor();
private final AuthorId local = new AuthorId(getRandomId());
private final LocalAuthor localAuthor = getLocalAuthor(); private final LocalAuthor localAuthor = getLocalAuthor();
private final String alias = getRandomString(MAX_AUTHOR_NAME_LENGTH);
private final boolean verified = false, active = true; private final boolean verified = false, active = true;
private final Contact contact = getContact(author, verified); private final Contact contact =
private final ContactId contactId = contact.getId(); new Contact(contactId, remote, local, alias, verified, active);
public ContactManagerImplTest() { public ContactManagerImplTest() {
contactManager = contactManager = new ContactManagerImpl(db, keyManager, identityManager);
new ContactManagerImpl(db, keyManager, identityManager);
} }
@Test @Test
public void testAddContact() throws Exception { public void testAddContact() throws Exception {
SecretKey rootKey = getSecretKey(); SecretKey master = getSecretKey();
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
boolean alice = new Random().nextBoolean(); boolean alice = new Random().nextBoolean();
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(false), withDbCallable(txn)); oneOf(db).transactionWithResult(with(false), withDbCallable(txn));
oneOf(db).addContact(txn, author, verified); oneOf(db).addContact(txn, remote, local, verified, active);
will(returnValue(contactId)); will(returnValue(contactId));
oneOf(keyManager).addContact(txn, contactId, rootKey, timestamp, oneOf(keyManager).addContact(txn, contactId, master, timestamp,
alice, active); alice, active);
oneOf(db).getContact(txn, contactId); oneOf(db).getContact(txn, contactId);
will(returnValue(contact)); will(returnValue(contact));
}}); }});
assertEquals(contactId, contactManager.addContact(author, rootKey, assertEquals(contactId, contactManager.addContact(remote, local,
timestamp, alice, verified, active)); master, timestamp, alice, verified, active));
} }
@Test @Test
public void testGetContactByContactId() throws Exception { public void testGetContact() throws Exception {
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
@@ -87,20 +96,49 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testGetContactByAuthorId() throws Exception { 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 {
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(db).getContact(txn, author.getId()); oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(contact)); will(returnValue(emptyList()));
}}); }});
assertEquals(contact, contactManager.getContact(author.getId())); 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()));
} }
@Test @Test
public void testGetContacts() throws Exception { public void testGetActiveContacts() throws Exception {
Collection<Contact> contacts = singletonList(contact); Collection<Contact> activeContacts = Collections.singletonList(contact);
Collection<Contact> contacts = new ArrayList<>(activeContacts);
contacts.add(new Contact(new ContactId(3), remote, local, alias, true,
false));
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
@@ -108,7 +146,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
will(returnValue(contacts)); will(returnValue(contacts));
}}); }});
assertEquals(contacts, contactManager.getContacts()); assertEquals(activeContacts, contactManager.getActiveContacts());
} }
@Test @Test
@@ -124,11 +162,19 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
contactManager.removeContact(contactId); 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 @Test
public void testSetContactAlias() throws Exception { public void testSetContactAlias() throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
String alias = getRandomString(MAX_AUTHOR_NAME_LENGTH);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transaction(with(false), withDbRunnable(txn)); oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(db).setContactAlias(txn, contactId, alias); oneOf(db).setContactAlias(txn, contactId, alias);
@@ -149,82 +195,85 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(db).containsContact(txn, author.getId()); oneOf(db).containsContact(txn, remote.getId(), local);
will(returnValue(true)); will(returnValue(true));
}}); }});
assertTrue(contactManager.contactExists(author.getId())); assertTrue(contactManager.contactExists(remote.getId(), local));
} }
@Test @Test
public void testGetAuthorInfoOurselves() throws Exception { public void testGetAuthorInfo() throws Exception {
Transaction txn = new Transaction(null, true); 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() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(identityManager).getLocalAuthor(txn); oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor)); will(returnValue(localAuthor));
oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(contacts));
}}); }});
AuthorInfo authorInfo = AuthorInfo authorInfo =
contactManager.getAuthorInfo(txn, localAuthor.getId()); 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());
assertEquals(OURSELVES, authorInfo.getStatus()); assertEquals(OURSELVES, authorInfo.getStatus());
assertNull(authorInfo.getAlias()); assertNull(authorInfo.getAlias());
} }
@Test private void checkAuthorInfoContext(Transaction txn, AuthorId authorId,
public void testGetAuthorInfoVerified() throws Exception { Collection<Contact> contacts) throws DbException {
Transaction txn = new Transaction(null, true); context.checking(new Expectations() {{
Contact verified = getContact(author, true);
context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(identityManager).getLocalAuthor(txn); oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor)); will(returnValue(localAuthor));
oneOf(db).containsContact(txn, author.getId()); oneOf(db).getContactsByAuthorId(txn, authorId);
will(returnValue(true)); will(returnValue(contacts));
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());
}
} }

View File

@@ -1,167 +0,0 @@
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);
}
}

View File

@@ -0,0 +1,167 @@
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())));
}
}

View File

@@ -1,45 +0,0 @@
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());
}
}

View File

@@ -1,167 +0,0 @@
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);
}
}

View File

@@ -2,9 +2,9 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; 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.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; 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.crypto.SecretKey;
import org.briarproject.bramble.api.db.ContactExistsException; import org.briarproject.bramble.api.db.ContactExistsException;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -13,9 +13,7 @@ import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.NoSuchGroupException; import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException; import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
import org.briarproject.bramble.api.db.NoSuchMessageException; 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.NoSuchTransportException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
@@ -45,21 +43,18 @@ import org.briarproject.bramble.api.sync.event.MessageToAckEvent;
import org.briarproject.bramble.api.sync.event.MessageToRequestEvent; import org.briarproject.bramble.api.sync.event.MessageToRequestEvent;
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent; import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
import org.briarproject.bramble.api.sync.event.MessagesSentEvent; 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.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.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.api.transport.TransportKeys;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction; import org.briarproject.bramble.test.CaptureArgumentAction;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.jmock.Sequence;
import org.junit.Test; import org.junit.Test;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
@@ -75,13 +70,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.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getClientId; 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.getGroup;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTransportId; 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.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
@@ -92,10 +87,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private final Database<Object> database = context.mock(Database.class); private final Database<Object> database = context.mock(Database.class);
private final ShutdownManager shutdownManager = private final ShutdownManager shutdown =
context.mock(ShutdownManager.class); context.mock(ShutdownManager.class);
private final EventBus eventBus = context.mock(EventBus.class); private final EventBus eventBus = context.mock(EventBus.class);
private final Executor eventExecutor = context.mock(Executor.class);
private final SecretKey key = getSecretKey(); private final SecretKey key = getSecretKey();
private final Object txn = new Object(); private final Object txn = new Object();
@@ -113,8 +107,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
private final int maxLatency; private final int maxLatency;
private final ContactId contactId; private final ContactId contactId;
private final Contact contact; private final Contact contact;
private final TransportKeySetId keySetId; private final KeySetId keySetId;
private final PendingContactId pendingContactId;
public DatabaseComponentImplTest() { public DatabaseComponentImplTest() {
clientId = getClientId(); clientId = getClientId();
@@ -123,6 +116,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
groupId = group.getId(); groupId = group.getId();
author = getAuthor(); author = getAuthor();
localAuthor = getLocalAuthor(); localAuthor = getLocalAuthor();
alias = getRandomString(5);
message = getMessage(groupId); message = getMessage(groupId);
message1 = getMessage(groupId); message1 = getMessage(groupId);
messageId = message.getId(); messageId = message.getId();
@@ -131,18 +125,16 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
metadata.put("foo", new byte[] {'b', 'a', 'r'}); metadata.put("foo", new byte[] {'b', 'a', 'r'});
transportId = getTransportId(); transportId = getTransportId();
maxLatency = Integer.MAX_VALUE; maxLatency = Integer.MAX_VALUE;
contact = getContact(author, true); contactId = new ContactId(234);
contactId = contact.getId(); contact = new Contact(contactId, author, localAuthor.getId(), alias,
alias = contact.getAlias(); true, true);
keySetId = new TransportKeySetId(345); keySetId = new KeySetId(345);
pendingContactId = new PendingContactId(getRandomId());
} }
private DatabaseComponent createDatabaseComponent(Database<Object> database, private DatabaseComponent createDatabaseComponent(Database<Object> database,
EventBus eventBus, Executor eventExecutor, EventBus eventBus, ShutdownManager shutdown) {
ShutdownManager shutdownManager) {
return new DatabaseComponentImpl<>(database, Object.class, eventBus, return new DatabaseComponentImpl<>(database, Object.class, eventBus,
eventExecutor, shutdownManager); shutdown);
} }
@Test @Test
@@ -152,7 +144,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// open() // open()
oneOf(database).open(key, null); oneOf(database).open(key, null);
will(returnValue(false)); will(returnValue(false));
oneOf(shutdownManager).addShutdownHook(with(any(Runnable.class))); oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
will(returnValue(shutdownHandle)); will(returnValue(shutdownHandle));
// startTransaction() // startTransaction()
oneOf(database).startTransaction(); oneOf(database).startTransaction();
@@ -163,13 +155,19 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).addLocalAuthor(txn, localAuthor); oneOf(database).addLocalAuthor(txn, localAuthor);
oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class))); oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class)));
// addContact() // addContact()
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
will(returnValue(true));
oneOf(database).containsLocalAuthor(txn, author.getId()); oneOf(database).containsLocalAuthor(txn, author.getId());
will(returnValue(false)); will(returnValue(false));
oneOf(database).containsContact(txn, author.getId()); oneOf(database).containsContact(txn, author.getId(),
localAuthor.getId());
will(returnValue(false)); will(returnValue(false));
oneOf(database).addContact(txn, author, true); oneOf(database).addContact(txn, author, localAuthor.getId(),
true, true);
will(returnValue(contactId)); will(returnValue(contactId));
oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class))); oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class)));
oneOf(eventBus).broadcast(with(any(
ContactStatusChangedEvent.class)));
// getContacts() // getContacts()
oneOf(database).getContacts(txn); oneOf(database).getContacts(txn);
will(returnValue(singletonList(contact))); will(returnValue(singletonList(contact)));
@@ -209,12 +207,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).close(); oneOf(database).close();
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
assertFalse(db.open(key, null)); assertFalse(db.open(key, null));
db.transaction(false, transaction -> { db.transaction(false, transaction -> {
db.addLocalAuthor(transaction, localAuthor); db.addLocalAuthor(transaction, localAuthor);
assertEquals(contactId, db.addContact(transaction, author, true)); assertEquals(contactId, db.addContact(transaction, author,
localAuthor.getId(), true, true));
assertEquals(singletonList(contact), assertEquals(singletonList(contact),
db.getContacts(transaction)); db.getContacts(transaction));
db.addGroup(transaction, group); // First time - listeners called db.addGroup(transaction, group); // First time - listeners called
@@ -239,7 +238,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).abortTransaction(txn); oneOf(database).abortTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.addLocalMessage(transaction, message, metadata, true)); db.addLocalMessage(transaction, message, metadata, true));
@@ -264,7 +263,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class))); oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.addLocalMessage(transaction, message, metadata, true)); db.addLocalMessage(transaction, message, metadata, true));
@@ -275,25 +274,14 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Check whether the contact is in the DB (which it's not) // Check whether the contact is in the DB (which it's not)
exactly(18).of(database).startTransaction(); exactly(17).of(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
exactly(17).of(database).containsContact(txn, contactId); exactly(17).of(database).containsContact(txn, contactId);
will(returnValue(false)); will(returnValue(false));
oneOf(database).containsContact(txn, author.getId()); exactly(17).of(database).abortTransaction(txn);
will(returnValue(false));
exactly(18).of(database).abortTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
try {
db.transaction(false, transaction ->
db.addHandshakeKeys(transaction, contactId,
createHandshakeKeys()));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
@@ -344,14 +332,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// Expected // Expected
} }
try {
db.transaction(false, transaction ->
db.getContact(transaction, author.getId()));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.getMessageStatus(transaction, contactId, groupId)); db.getMessageStatus(transaction, contactId, groupId));
@@ -419,6 +399,14 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// Expected // Expected
} }
try {
db.transaction(false, transaction ->
db.setContactActive(transaction, contactId, true));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.setContactAlias(transaction, contactId, alias)); db.setContactAlias(transaction, contactId, alias));
@@ -442,15 +430,24 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Check whether the pseudonym is in the DB (which it's not) // Check whether the pseudonym is in the DB (which it's not)
exactly(2).of(database).startTransaction(); exactly(3).of(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
exactly(2).of(database).containsLocalAuthor(txn, exactly(3).of(database).containsLocalAuthor(txn,
localAuthor.getId()); localAuthor.getId());
will(returnValue(false)); will(returnValue(false));
exactly(2).of(database).abortTransaction(txn); exactly(3).of(database).abortTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
try {
db.transaction(false, transaction ->
db.addContact(transaction, author, localAuthor.getId(),
true, true));
fail();
} catch (NoSuchLocalAuthorException expected) {
// Expected
}
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
@@ -484,7 +481,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(true)); will(returnValue(true));
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
@@ -568,7 +565,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(true)); will(returnValue(true));
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
@@ -671,7 +668,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
exactly(5).of(database).abortTransaction(txn); exactly(5).of(database).abortTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
@@ -716,39 +713,6 @@ 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 @Test
public void testGenerateAck() throws Exception { public void testGenerateAck() throws Exception {
Collection<MessageId> messagesToAck = asList(messageId, messageId1); Collection<MessageId> messagesToAck = asList(messageId, messageId1);
@@ -763,7 +727,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(false, transaction -> { db.transaction(false, transaction -> {
Ack a = db.generateAck(transaction, contactId, 123); Ack a = db.generateAck(transaction, contactId, 123);
@@ -797,7 +761,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class))); oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class)));
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(false, transaction -> db.transaction(false, transaction ->
assertEquals(messages, db.generateBatch(transaction, contactId, assertEquals(messages, db.generateBatch(transaction, contactId,
@@ -822,7 +786,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(false, transaction -> { db.transaction(false, transaction -> {
Offer o = db.generateOffer(transaction, contactId, 123, maxLatency); Offer o = db.generateOffer(transaction, contactId, 123, maxLatency);
@@ -846,7 +810,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(false, transaction -> { db.transaction(false, transaction -> {
Request r = db.generateRequest(transaction, contactId, 123); Request r = db.generateRequest(transaction, contactId, 123);
@@ -880,7 +844,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class))); oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class)));
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(false, transaction -> db.transaction(false, transaction ->
assertEquals(messages, db.generateRequestedBatch(transaction, assertEquals(messages, db.generateRequestedBatch(transaction,
@@ -901,7 +865,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(eventBus).broadcast(with(any(MessagesAckedEvent.class))); oneOf(eventBus).broadcast(with(any(MessagesAckedEvent.class)));
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(false, transaction -> { db.transaction(false, transaction -> {
Ack a = new Ack(singletonList(messageId)); Ack a = new Ack(singletonList(messageId));
@@ -939,7 +903,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(eventBus).broadcast(with(any(MessageToAckEvent.class))); oneOf(eventBus).broadcast(with(any(MessageToAckEvent.class)));
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(false, transaction -> { db.transaction(false, transaction -> {
// Receive the message twice // Receive the message twice
@@ -967,7 +931,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(eventBus).broadcast(with(any(MessageToAckEvent.class))); oneOf(eventBus).broadcast(with(any(MessageToAckEvent.class)));
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.receiveMessage(transaction, contactId, message)); db.receiveMessage(transaction, contactId, message));
@@ -985,7 +949,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.receiveMessage(transaction, contactId, message)); db.receiveMessage(transaction, contactId, message));
@@ -1025,7 +989,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(eventBus).broadcast(with(any(MessageToRequestEvent.class))); oneOf(eventBus).broadcast(with(any(MessageToRequestEvent.class)));
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
Offer o = new Offer(asList(messageId, messageId1, Offer o = new Offer(asList(messageId, messageId1,
messageId2, messageId3)); messageId2, messageId3));
@@ -1048,7 +1012,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(eventBus).broadcast(with(any(MessageRequestedEvent.class))); oneOf(eventBus).broadcast(with(any(MessageRequestedEvent.class)));
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
Request r = new Request(singletonList(messageId)); Request r = new Request(singletonList(messageId));
db.transaction(false, transaction -> db.transaction(false, transaction ->
@@ -1078,7 +1042,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
GroupVisibilityUpdatedEvent.class, 0)); GroupVisibilityUpdatedEvent.class, 0));
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.setGroupVisibility(transaction, contactId, groupId, db.setGroupVisibility(transaction, contactId, groupId,
@@ -1112,7 +1076,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
GroupVisibilityUpdatedEvent.class, 0)); GroupVisibilityUpdatedEvent.class, 0));
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.setGroupVisibility(transaction, contactId, groupId, db.setGroupVisibility(transaction, contactId, groupId,
@@ -1138,7 +1102,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.setGroupVisibility(transaction, contactId, groupId, db.setGroupVisibility(transaction, contactId, groupId,
@@ -1148,9 +1112,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
@Test @Test
public void testTransportKeys() throws Exception { public void testTransportKeys() throws Exception {
TransportKeys transportKeys = createTransportKeys(); TransportKeys transportKeys = createTransportKeys();
TransportKeySet ks = KeySet ks = new KeySet(keySetId, contactId, transportKeys);
new TransportKeySet(keySetId, contactId, transportKeys); Collection<KeySet> keys = singletonList(ks);
Collection<TransportKeySet> keys = singletonList(ks);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// startTransaction() // startTransaction()
@@ -1169,7 +1132,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(false, transaction -> { db.transaction(false, transaction -> {
db.updateTransportKeys(transaction, keys); db.updateTransportKeys(transaction, keys);
@@ -1208,7 +1171,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(true, transaction -> { db.transaction(true, transaction -> {
// With visible group - return stored status // With visible group - return stored status
@@ -1258,7 +1221,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(true, transaction -> { db.transaction(true, transaction -> {
// With visible group - return stored status // With visible group - return stored status
@@ -1277,27 +1240,6 @@ 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() { private TransportKeys createTransportKeys() {
SecretKey inPrevTagKey = getSecretKey(); SecretKey inPrevTagKey = getSecretKey();
SecretKey inPrevHeaderKey = getSecretKey(); SecretKey inPrevHeaderKey = getSecretKey();
@@ -1345,7 +1287,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
db.transaction(false, transaction -> { db.transaction(false, transaction -> {
// First merge should broadcast an event // First merge should broadcast an event
@@ -1388,7 +1330,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
assertNotNull(db.startTransaction(firstTxnReadOnly)); assertNotNull(db.startTransaction(firstTxnReadOnly));
db.startTransaction(secondTxnReadOnly); db.startTransaction(secondTxnReadOnly);
@@ -1400,6 +1342,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
will(returnValue(true));
// Contact is a local identity // Contact is a local identity
oneOf(database).containsLocalAuthor(txn, author.getId()); oneOf(database).containsLocalAuthor(txn, author.getId());
will(returnValue(true)); will(returnValue(true));
@@ -1407,11 +1351,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.addContact(transaction, author, true)); db.addContact(transaction, author, localAuthor.getId(),
true, true));
fail(); fail();
} catch (ContactExistsException expected) { } catch (ContactExistsException expected) {
// Expected // Expected
@@ -1423,20 +1368,24 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
will(returnValue(true));
oneOf(database).containsLocalAuthor(txn, author.getId()); oneOf(database).containsLocalAuthor(txn, author.getId());
will(returnValue(false)); will(returnValue(false));
// Contact already exists // Contact already exists for this local identity
oneOf(database).containsContact(txn, author.getId()); oneOf(database).containsContact(txn, author.getId(),
localAuthor.getId());
will(returnValue(true)); will(returnValue(true));
oneOf(database).abortTransaction(txn); oneOf(database).abortTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.addContact(transaction, author, true)); db.addContact(transaction, author, localAuthor.getId(),
true, true));
fail(); fail();
} catch (ContactExistsException expected) { } catch (ContactExistsException expected) {
// Expected // Expected
@@ -1452,7 +1401,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// open() // open()
oneOf(database).open(key, null); oneOf(database).open(key, null);
will(returnValue(false)); will(returnValue(false));
oneOf(shutdownManager).addShutdownHook(with(any(Runnable.class))); oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
will(returnValue(shutdownHandle)); will(returnValue(shutdownHandle));
// startTransaction() // startTransaction()
oneOf(database).startTransaction(); oneOf(database).startTransaction();
@@ -1492,7 +1441,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).close(); oneOf(database).close();
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); shutdown);
assertFalse(db.open(key, null)); assertFalse(db.open(key, null));
db.transaction(false, transaction -> { db.transaction(false, transaction -> {
@@ -1506,43 +1455,4 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}); });
db.close(); 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 {
}
} }

View File

@@ -45,7 +45,8 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
private final Migration<Connection> migration1 = private final Migration<Connection> migration1 =
context.mock(Migration.class, "migration1"); context.mock(Migration.class, "migration1");
protected final DatabaseConfig config = new TestDatabaseConfig(testDir); protected final DatabaseConfig config =
new TestDatabaseConfig(testDir, 1024 * 1024);
protected final MessageFactory messageFactory = new TestMessageFactory(); protected final MessageFactory messageFactory = new TestMessageFactory();
protected final SecretKey key = getSecretKey(); protected final SecretKey key = getSecretKey();
protected final Clock clock = new SystemClock(); protected final Clock clock = new SystemClock();

View File

@@ -76,8 +76,8 @@ public abstract class DatabasePerformanceComparisonTest
private Database<Connection> openDatabase(boolean conditionA) private Database<Connection> openDatabase(boolean conditionA)
throws DbException { throws DbException {
Database<Connection> db = createDatabase(conditionA, Database<Connection> db = createDatabase(conditionA,
new TestDatabaseConfig(testDir), new TestMessageFactory(), new TestDatabaseConfig(testDir, MAX_SIZE),
new SystemClock()); new TestMessageFactory(), new SystemClock());
db.open(databaseKey, null); db.open(databaseKey, null);
return db; return db;
} }

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; 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.identity.LocalAuthor;
import org.briarproject.bramble.api.sync.ClientId; import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
@@ -48,6 +49,9 @@ import static org.junit.Assert.assertTrue;
public abstract class DatabasePerformanceTest extends BrambleTestCase { 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. * How many contacts to simulate.
*/ */
@@ -130,10 +134,11 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
@Test @Test
public void testContainsContactByAuthorId() throws Exception { public void testContainsContactByAuthorId() throws Exception {
String name = "containsContact(T, AuthorId)"; String name = "containsContact(T, AuthorId, AuthorId)";
benchmark(name, db -> { benchmark(name, db -> {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
db.containsContact(txn, pickRandom(contacts).getAuthor().getId()); AuthorId remote = pickRandom(contacts).getAuthor().getId();
db.containsContact(txn, remote, localAuthor.getId());
db.commitTransaction(txn); db.commitTransaction(txn);
}); });
} }
@@ -200,7 +205,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
} }
@Test @Test
public void testGetContactByContactId() throws Exception { public void testGetContact() throws Exception {
String name = "getContact(T, ContactId)"; String name = "getContact(T, ContactId)";
benchmark(name, db -> { benchmark(name, db -> {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
@@ -209,16 +214,6 @@ 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 @Test
public void testGetContacts() throws Exception { public void testGetContacts() throws Exception {
String name = "getContacts(T)"; String name = "getContacts(T)";
@@ -229,6 +224,27 @@ 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 @Test
public void testGetGroup() throws Exception { public void testGetGroup() throws Exception {
String name = "getGroup(T, GroupId)"; String name = "getGroup(T, GroupId)";
@@ -532,7 +548,8 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
for (int i = 0; i < CONTACTS; i++) { for (int i = 0; i < CONTACTS; i++) {
ContactId c = db.addContact(txn, getAuthor(), random.nextBoolean()); ContactId c = db.addContact(txn, getAuthor(), localAuthor.getId(),
random.nextBoolean(), true);
contacts.add(db.getContact(txn, c)); contacts.add(db.getContact(txn, c));
contactGroups.put(c, new ArrayList<>()); contactGroups.put(c, new ArrayList<>());
for (int j = 0; j < GROUPS_PER_CONTACT; j++) { for (int j = 0; j < GROUPS_PER_CONTACT; j++) {

View File

@@ -48,8 +48,8 @@ public abstract class DatabaseTraceTest extends DatabasePerformanceTest {
private Database<Connection> openDatabase() throws DbException { private Database<Connection> openDatabase() throws DbException {
Database<Connection> db = createDatabase( Database<Connection> db = createDatabase(
new TestDatabaseConfig(testDir), new TestMessageFactory(), new TestDatabaseConfig(testDir, MAX_SIZE),
new SystemClock()); new TestMessageFactory(), new SystemClock());
db.open(databaseKey, null); db.open(databaseKey, null);
return db; return db;
} }

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; 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.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
@@ -21,13 +20,10 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.MessageStatus; import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.bramble.api.sync.validation.MessageState; import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.system.Clock; 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.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.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.api.transport.TransportKeys;
import org.briarproject.bramble.system.SystemClock; import org.briarproject.bramble.system.SystemClock;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
@@ -56,12 +52,12 @@ import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static java.util.concurrent.TimeUnit.SECONDS; 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.db.Metadata.REMOVE;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; 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.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; 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.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.DELIVERED;
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID; import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING; import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
@@ -75,7 +71,6 @@ import static org.briarproject.bramble.test.TestUtils.getClientId;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.briarproject.bramble.test.TestUtils.getMessage; 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.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
@@ -84,6 +79,7 @@ import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -92,9 +88,11 @@ import static org.junit.Assert.fail;
public abstract class JdbcDatabaseTest extends BrambleTestCase { public abstract class JdbcDatabaseTest extends BrambleTestCase {
private static final int ONE_MEGABYTE = 1024 * 1024; 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 // All our transports use a maximum latency of 30 seconds
private static final int MAX_LATENCY = 30 * 1000; private static final int MAX_LATENCY = 30 * 1000;
private final SecretKey key = getSecretKey(); private final SecretKey key = getSecretKey();
private final File testDir = getTestDirectory(); private final File testDir = getTestDirectory();
private final GroupId groupId; private final GroupId groupId;
@@ -107,9 +105,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private final MessageId messageId; private final MessageId messageId;
private final TransportId transportId; private final TransportId transportId;
private final ContactId contactId; private final ContactId contactId;
private final TransportKeySetId keySetId, keySetId1; private final KeySetId keySetId, keySetId1;
private final HandshakeKeySetId handshakeKeySetId, handshakeKeySetId1;
private final PendingContact pendingContact;
private final Random random = new Random(); private final Random random = new Random();
JdbcDatabaseTest() { JdbcDatabaseTest() {
@@ -123,11 +119,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
messageId = message.getId(); messageId = message.getId();
transportId = getTransportId(); transportId = getTransportId();
contactId = new ContactId(1); contactId = new ContactId(1);
keySetId = new TransportKeySetId(1); keySetId = new KeySetId(1);
keySetId1 = new TransportKeySetId(2); keySetId1 = new KeySetId(2);
handshakeKeySetId = new HandshakeKeySetId(1);
handshakeKeySetId1 = new HandshakeKeySetId(2);
pendingContact = getPendingContact();
} }
protected abstract JdbcDatabase createDatabase(DatabaseConfig config, protected abstract JdbcDatabase createDatabase(DatabaseConfig config,
@@ -145,7 +138,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
assertFalse(db.containsContact(txn, contactId)); assertFalse(db.containsContact(txn, contactId));
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, true)); assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
assertTrue(db.containsContact(txn, contactId)); assertTrue(db.containsContact(txn, contactId));
assertFalse(db.containsGroup(txn, groupId)); assertFalse(db.containsGroup(txn, groupId));
db.addGroup(txn, group); db.addGroup(txn, group);
@@ -206,7 +200,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
@@ -235,7 +231,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared but unvalidated message // Add a contact, a shared group and a shared but unvalidated message
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, UNKNOWN, true, null); db.addMessage(txn, message, UNKNOWN, true, null);
@@ -278,7 +276,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, an invisible group and a shared message // Add a contact, an invisible group and a shared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
@@ -327,7 +327,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and an unshared message // Add a contact, a shared group and an unshared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, false, null); db.addMessage(txn, message, DELIVERED, false, null);
@@ -356,14 +358,17 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
// The message is sendable, but too large to send // The message is sendable, but too large to send
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, Collection<MessageId> ids =
message.getRawLength() - 1, MAX_LATENCY); db.getMessagesToSend(txn, contactId, message.getRawLength() - 1,
MAX_LATENCY);
assertTrue(ids.isEmpty()); assertTrue(ids.isEmpty());
// The message is just the right size to send // The message is just the right size to send
ids = db.getMessagesToSend(txn, contactId, message.getRawLength(), ids = db.getMessagesToSend(txn, contactId, message.getRawLength(),
@@ -380,7 +385,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact and a visible group // Add a contact and a visible group
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, false); db.addGroupVisibility(txn, contactId, groupId, false);
@@ -419,7 +426,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
@@ -445,6 +454,29 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.close(); 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 @Test
public void testCloseWaitsForCommit() throws Exception { public void testCloseWaitsForCommit() throws Exception {
CountDownLatch closing = new CountDownLatch(1); CountDownLatch closing = new CountDownLatch(1);
@@ -549,7 +581,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact and a shared group // Add a contact and a shared group
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
@@ -567,7 +601,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact // Add a contact
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
// The group is not in the database // The group is not in the database
assertFalse(db.containsVisibleMessage(txn, contactId, messageId)); assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
@@ -583,7 +619,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, an invisible group and a message // Add a contact, an invisible group and a message
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
@@ -600,7 +638,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact and a group // Add a contact and a group
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
// The group should not be visible to the contact // The group should not be visible to the contact
@@ -638,10 +678,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
@Test @Test
public void testTransportKeys() throws Exception { public void testTransportKeys() throws Exception {
long timePeriod = 123, timePeriod1 = 234; long rotationPeriod = 123, rotationPeriod1 = 234;
boolean active = random.nextBoolean(); boolean active = random.nextBoolean();
TransportKeys keys = createTransportKeys(timePeriod, active); TransportKeys keys = createTransportKeys(rotationPeriod, active);
TransportKeys keys1 = createTransportKeys(timePeriod1, active); TransportKeys keys1 = createTransportKeys(rotationPeriod1, active);
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
@@ -650,44 +690,43 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(emptyList(), db.getTransportKeys(txn, transportId)); assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
// Add the contact, the transport and the transport keys // Add the contact, the transport and the transport keys
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, active));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys)); assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1)); assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1));
// Retrieve the transport keys // Retrieve the transport keys
Collection<TransportKeySet> allKeys = Collection<KeySet> allKeys = db.getTransportKeys(txn, transportId);
db.getTransportKeys(txn, transportId);
assertEquals(2, allKeys.size()); assertEquals(2, allKeys.size());
for (TransportKeySet ks : allKeys) { for (KeySet ks : allKeys) {
assertEquals(contactId, ks.getContactId()); assertEquals(contactId, ks.getContactId());
if (ks.getKeySetId().equals(keySetId)) { if (ks.getKeySetId().equals(keySetId)) {
assertKeysEquals(keys, ks.getKeys()); assertKeysEquals(keys, ks.getTransportKeys());
} else { } else {
assertEquals(keySetId1, ks.getKeySetId()); assertEquals(keySetId1, ks.getKeySetId());
assertKeysEquals(keys1, ks.getKeys()); assertKeysEquals(keys1, ks.getTransportKeys());
} }
} }
// Rotate the transport keys // Rotate the transport keys
TransportKeys rotated = createTransportKeys(timePeriod + 1, active); TransportKeys rotated = createTransportKeys(rotationPeriod + 1, active);
TransportKeys rotated1 = TransportKeys rotated1 =
createTransportKeys(timePeriod1 + 1, active); createTransportKeys(rotationPeriod1 + 1, active);
db.updateTransportKeys(txn, new TransportKeySet(keySetId, contactId, db.updateTransportKeys(txn, new KeySet(keySetId, contactId, rotated));
rotated)); db.updateTransportKeys(txn, new KeySet(keySetId1, contactId, rotated1));
db.updateTransportKeys(txn, new TransportKeySet(keySetId1, contactId,
rotated1));
// Retrieve the transport keys again // Retrieve the transport keys again
allKeys = db.getTransportKeys(txn, transportId); allKeys = db.getTransportKeys(txn, transportId);
assertEquals(2, allKeys.size()); assertEquals(2, allKeys.size());
for (TransportKeySet ks : allKeys) { for (KeySet ks : allKeys) {
assertEquals(contactId, ks.getContactId()); assertEquals(contactId, ks.getContactId());
if (ks.getKeySetId().equals(keySetId)) { if (ks.getKeySetId().equals(keySetId)) {
assertKeysEquals(rotated, ks.getKeys()); assertKeysEquals(rotated, ks.getTransportKeys());
} else { } else {
assertEquals(keySetId1, ks.getKeySetId()); assertEquals(keySetId1, ks.getKeySetId());
assertKeysEquals(rotated1, ks.getKeys()); assertKeysEquals(rotated1, ks.getTransportKeys());
} }
} }
@@ -702,7 +741,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private void assertKeysEquals(TransportKeys expected, private void assertKeysEquals(TransportKeys expected,
TransportKeys actual) { TransportKeys actual) {
assertEquals(expected.getTransportId(), actual.getTransportId()); assertEquals(expected.getTransportId(), actual.getTransportId());
assertEquals(expected.getTimePeriod(), actual.getTimePeriod()); assertEquals(expected.getRotationPeriod(), actual.getRotationPeriod());
assertKeysEquals(expected.getPreviousIncomingKeys(), assertKeysEquals(expected.getPreviousIncomingKeys(),
actual.getPreviousIncomingKeys()); actual.getPreviousIncomingKeys());
assertKeysEquals(expected.getCurrentIncomingKeys(), assertKeysEquals(expected.getCurrentIncomingKeys(),
@@ -718,7 +757,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
actual.getTagKey().getBytes()); actual.getTagKey().getBytes());
assertArrayEquals(expected.getHeaderKey().getBytes(), assertArrayEquals(expected.getHeaderKey().getBytes(),
actual.getHeaderKey().getBytes()); actual.getHeaderKey().getBytes());
assertEquals(expected.getTimePeriod(), actual.getTimePeriod()); assertEquals(expected.getRotationPeriod(), actual.getRotationPeriod());
assertEquals(expected.getWindowBase(), actual.getWindowBase()); assertEquals(expected.getWindowBase(), actual.getWindowBase());
assertArrayEquals(expected.getWindowBitmap(), actual.getWindowBitmap()); assertArrayEquals(expected.getWindowBitmap(), actual.getWindowBitmap());
} }
@@ -728,240 +767,39 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
actual.getTagKey().getBytes()); actual.getTagKey().getBytes());
assertArrayEquals(expected.getHeaderKey().getBytes(), assertArrayEquals(expected.getHeaderKey().getBytes(),
actual.getHeaderKey().getBytes()); actual.getHeaderKey().getBytes());
assertEquals(expected.getTimePeriod(), actual.getTimePeriod()); assertEquals(expected.getRotationPeriod(), actual.getRotationPeriod());
assertEquals(expected.getStreamCounter(), actual.getStreamCounter()); assertEquals(expected.getStreamCounter(), actual.getStreamCounter());
assertEquals(expected.isActive(), actual.isActive()); 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 @Test
public void testIncrementStreamCounter() throws Exception { public void testIncrementStreamCounter() throws Exception {
long timePeriod = 123; long rotationPeriod = 123;
TransportKeys keys = createTransportKeys(timePeriod, true); TransportKeys keys = createTransportKeys(rotationPeriod, true);
long streamCounter = keys.getCurrentOutgoingKeys().getStreamCounter(); long streamCounter = keys.getCurrentOutgoingKeys().getStreamCounter();
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add the contact, transport and transport keys // Add the contact, transport and transport keys
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys)); assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
// Increment the stream counter twice and retrieve the transport keys // Increment the stream counter twice and retrieve the transport keys
db.incrementStreamCounter(txn, transportId, keySetId); db.incrementStreamCounter(txn, transportId, keySetId);
db.incrementStreamCounter(txn, transportId, keySetId); db.incrementStreamCounter(txn, transportId, keySetId);
Collection<TransportKeySet> newKeys = Collection<KeySet> newKeys = db.getTransportKeys(txn, transportId);
db.getTransportKeys(txn, transportId);
assertEquals(1, newKeys.size()); assertEquals(1, newKeys.size());
TransportKeySet ks = newKeys.iterator().next(); KeySet ks = newKeys.iterator().next();
assertEquals(keySetId, ks.getKeySetId()); assertEquals(keySetId, ks.getKeySetId());
assertEquals(contactId, ks.getContactId()); assertEquals(contactId, ks.getContactId());
TransportKeys k = ks.getKeys(); TransportKeys k = ks.getTransportKeys();
assertEquals(transportId, k.getTransportId()); assertEquals(transportId, k.getTransportId());
OutgoingKeys outCurr = k.getCurrentOutgoingKeys(); OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
assertEquals(timePeriod, outCurr.getTimePeriod()); assertEquals(rotationPeriod, outCurr.getRotationPeriod());
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()); assertEquals(streamCounter + 2, outCurr.getStreamCounter());
// The rest of the keys should be unaffected // The rest of the keys should be unaffected
@@ -978,8 +816,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
@Test @Test
public void testSetReorderingWindow() throws Exception { public void testSetReorderingWindow() throws Exception {
boolean active = random.nextBoolean(); boolean active = random.nextBoolean();
long timePeriod = 123; long rotationPeriod = 123;
TransportKeys keys = createTransportKeys(timePeriod, active); TransportKeys keys = createTransportKeys(rotationPeriod, active);
long base = keys.getCurrentIncomingKeys().getWindowBase(); long base = keys.getCurrentIncomingKeys().getWindowBase();
byte[] bitmap = keys.getCurrentIncomingKeys().getWindowBitmap(); byte[] bitmap = keys.getCurrentIncomingKeys().getWindowBitmap();
@@ -987,24 +825,25 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add the contact, transport and transport keys // Add the contact, transport and transport keys
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, active));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys)); assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
// Update the reordering window and retrieve the transport keys // Update the reordering window and retrieve the transport keys
random.nextBytes(bitmap); random.nextBytes(bitmap);
db.setReorderingWindow(txn, keySetId, transportId, timePeriod, db.setReorderingWindow(txn, keySetId, transportId, rotationPeriod,
base + 1, bitmap); base + 1, bitmap);
Collection<TransportKeySet> newKeys = Collection<KeySet> newKeys = db.getTransportKeys(txn, transportId);
db.getTransportKeys(txn, transportId);
assertEquals(1, newKeys.size()); assertEquals(1, newKeys.size());
TransportKeySet ks = newKeys.iterator().next(); KeySet ks = newKeys.iterator().next();
assertEquals(keySetId, ks.getKeySetId()); assertEquals(keySetId, ks.getKeySetId());
assertEquals(contactId, ks.getContactId()); assertEquals(contactId, ks.getContactId());
TransportKeys k = ks.getKeys(); TransportKeys k = ks.getTransportKeys();
assertEquals(transportId, k.getTransportId()); assertEquals(transportId, k.getTransportId());
IncomingKeys inCurr = k.getCurrentIncomingKeys(); IncomingKeys inCurr = k.getCurrentIncomingKeys();
assertEquals(timePeriod, inCurr.getTimePeriod()); assertEquals(rotationPeriod, inCurr.getRotationPeriod());
assertEquals(base + 1, inCurr.getWindowBase()); assertEquals(base + 1, inCurr.getWindowBase());
assertArrayEquals(bitmap, inCurr.getWindowBitmap()); assertArrayEquals(bitmap, inCurr.getWindowBitmap());
@@ -1020,90 +859,54 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
} }
@Test @Test
public void testSetReorderingWindowForHandshakeKeys() throws Exception { public void testGetContactsByAuthorId() 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); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add the contact, transport and handshake keys // Add a local author - no contacts should be associated
assertEquals(contactId, db.addContact(txn, author,true)); db.addLocalAuthor(txn, localAuthor);
db.addTransport(txn, transportId, 123);
assertEquals(handshakeKeySetId,
db.addHandshakeKeys(txn, contactId, keys));
// Update the reordering window and retrieve the handshake keys // Add a contact associated with the local author
random.nextBytes(bitmap); assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
db.setReorderingWindow(txn, handshakeKeySetId, transportId, timePeriod, true, true));
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());
// The rest of the keys should be unaffected // Ensure contact is returned from database by Author ID
assertKeysEquals(keys.getPreviousIncomingKeys(), Collection<Contact> contacts =
k.getPreviousIncomingKeys()); db.getContactsByAuthorId(txn, author.getId());
assertKeysEquals(keys.getNextIncomingKeys(), k.getNextIncomingKeys()); assertEquals(1, contacts.size());
assertKeysEquals(keys.getCurrentOutgoingKeys(), assertEquals(contactId, contacts.iterator().next().getId());
k.getCurrentOutgoingKeys());
// Ensure no contacts are returned after contact was deleted
db.removeContact(txn, contactId);
contacts = db.getContactsByAuthorId(txn, author.getId());
assertEquals(0, contacts.size());
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
} }
@Test @Test
public void testGetContactByContactId() throws Exception { public void testGetContactsByLocalAuthorId() throws Exception {
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact // Add a local author - no contacts should be associated
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
Collection<ContactId> contacts =
db.getContacts(txn, localAuthor.getId());
assertEquals(emptyList(), contacts);
// Check the contact is returned // Add a contact associated with the local author
Contact c = db.getContact(txn, contactId); assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
assertEquals(contactId, c.getId()); true, true));
assertEquals(author.getId(), c.getAuthor().getId()); contacts = db.getContacts(txn, localAuthor.getId());
assertEquals(author.getFormatVersion(), assertEquals(singletonList(contactId), contacts);
c.getAuthor().getFormatVersion());
assertEquals(author.getName(), c.getAuthor().getName());
assertArrayEquals(author.getPublicKey(), c.getAuthor().getPublicKey());
db.commitTransaction(txn); // Remove the local author - the contact should be removed
db.close(); db.removeLocalAuthor(txn, localAuthor.getId());
} contacts = db.getContacts(txn, localAuthor.getId());
assertEquals(emptyList(), contacts);
@Test assertFalse(db.containsContact(txn, contactId));
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.commitTransaction(txn);
db.close(); db.close();
@@ -1115,7 +918,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact - initially there should be no offered messages // Add a contact - initially there should be no offered messages
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
assertEquals(0, db.countOfferedMessages(txn, contactId)); assertEquals(0, db.countOfferedMessages(txn, contactId));
// Add some offered messages and count them // Add some offered messages and count them
@@ -1697,7 +1502,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
@@ -1794,13 +1601,43 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.close(); 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 @Test
public void testDeleteMessage() throws Exception { public void testDeleteMessage() throws Exception {
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
@@ -1846,13 +1683,47 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.close(); 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 @Test
public void testSetContactAlias() throws Exception { public void testSetContactAlias() throws Exception {
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact // Add a contact
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
// The contact should have no alias // The contact should have no alias
Contact contact = db.getContact(txn, contactId); Contact contact = db.getContact(txn, contactId);
@@ -1907,7 +1778,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a group and a message // Add a contact, a group and a message
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, UNKNOWN, false, null); db.addMessage(txn, message, UNKNOWN, false, null);
@@ -1989,7 +1862,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
@@ -2032,7 +1907,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
@@ -2113,94 +1990,41 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.close(); 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 { private Database<Connection> open(boolean resume) throws Exception {
return open(resume, new TestMessageFactory(), new SystemClock()); return open(resume, new TestMessageFactory(), new SystemClock());
} }
private Database<Connection> open(boolean resume, private Database<Connection> open(boolean resume,
MessageFactory messageFactory, Clock clock) throws Exception { MessageFactory messageFactory, Clock clock) throws Exception {
Database<Connection> db = createDatabase( Database<Connection> db =
new TestDatabaseConfig(testDir), messageFactory, clock); createDatabase(new TestDatabaseConfig(testDir, MAX_SIZE),
messageFactory, clock);
if (!resume) deleteTestDirectory(testDir); if (!resume) deleteTestDirectory(testDir);
db.open(key, null); db.open(key, null);
return db; return db;
} }
private TransportKeys createTransportKeys(long timePeriod, boolean active) { private TransportKeys createTransportKeys(long rotationPeriod,
boolean active) {
SecretKey inPrevTagKey = getSecretKey(); SecretKey inPrevTagKey = getSecretKey();
SecretKey inPrevHeaderKey = getSecretKey(); SecretKey inPrevHeaderKey = getSecretKey();
IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey, IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey,
timePeriod - 1, 123, new byte[4]); rotationPeriod - 1, 123, new byte[4]);
SecretKey inCurrTagKey = getSecretKey(); SecretKey inCurrTagKey = getSecretKey();
SecretKey inCurrHeaderKey = getSecretKey(); SecretKey inCurrHeaderKey = getSecretKey();
IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey, IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey,
timePeriod, 234, new byte[4]); rotationPeriod, 234, new byte[4]);
SecretKey inNextTagKey = getSecretKey(); SecretKey inNextTagKey = getSecretKey();
SecretKey inNextHeaderKey = getSecretKey(); SecretKey inNextHeaderKey = getSecretKey();
IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey, IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey,
timePeriod + 1, 345, new byte[4]); rotationPeriod + 1, 345, new byte[4]);
SecretKey outCurrTagKey = getSecretKey(); SecretKey outCurrTagKey = getSecretKey();
SecretKey outCurrHeaderKey = getSecretKey(); SecretKey outCurrHeaderKey = getSecretKey();
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey, OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
timePeriod, 456, active); rotationPeriod, 456, active);
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr); 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 @After
public void tearDown() { public void tearDown() {
deleteTestDirectory(testDir); deleteTestDirectory(testDir);

View File

@@ -45,8 +45,8 @@ public abstract class SingleDatabasePerformanceTest
private Database<Connection> openDatabase() throws DbException { private Database<Connection> openDatabase() throws DbException {
Database<Connection> db = createDatabase( Database<Connection> db = createDatabase(
new TestDatabaseConfig(testDir), new TestMessageFactory(), new TestDatabaseConfig(testDir, MAX_SIZE),
new SystemClock()); new TestMessageFactory(), new SystemClock());
db.open(databaseKey, null); db.open(databaseKey, null);
return db; return db;
} }

View File

@@ -17,7 +17,7 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_KEY_LABEL; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_SECRET_LABEL;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; 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.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
@@ -74,7 +74,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
Payload ourPayload = new Payload(aliceCommit, null); Payload ourPayload = new Payload(aliceCommit, null);
KeyPair ourKeyPair = new KeyPair(ourPubKey, null); KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
SecretKey sharedSecret = getSecretKey(); SecretKey sharedSecret = getSecretKey();
SecretKey masterKey = getSecretKey(); SecretKey masterSecret = getSecretKey();
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks, KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
crypto, keyAgreementCrypto, payloadEncoder, transport, crypto, keyAgreementCrypto, payloadEncoder, transport,
@@ -134,13 +134,13 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
true, false); true, false);
will(returnValue(bobConfirm)); will(returnValue(bobConfirm));
// Alice derives master key // Alice computes master secret
oneOf(crypto).deriveKey(MASTER_KEY_LABEL, sharedSecret); oneOf(crypto).deriveKey(MASTER_SECRET_LABEL, sharedSecret);
will(returnValue(masterKey)); will(returnValue(masterSecret));
}}); }});
// execute // execute
assertThat(masterKey, is(equalTo(protocol.perform()))); assertThat(masterSecret, is(equalTo(protocol.perform())));
} }
@Test @Test
@@ -150,7 +150,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
Payload ourPayload = new Payload(bobCommit, null); Payload ourPayload = new Payload(bobCommit, null);
KeyPair ourKeyPair = new KeyPair(ourPubKey, null); KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
SecretKey sharedSecret = getSecretKey(); SecretKey sharedSecret = getSecretKey();
SecretKey masterKey = getSecretKey(); SecretKey masterSecret = getSecretKey();
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks, KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
crypto, keyAgreementCrypto, payloadEncoder, transport, crypto, keyAgreementCrypto, payloadEncoder, transport,
@@ -209,13 +209,13 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
will(returnValue(bobConfirm)); will(returnValue(bobConfirm));
oneOf(transport).sendConfirm(bobConfirm); oneOf(transport).sendConfirm(bobConfirm);
// Bob derives master key // Bob computes master secret
oneOf(crypto).deriveKey(MASTER_KEY_LABEL, sharedSecret); oneOf(crypto).deriveKey(MASTER_SECRET_LABEL, sharedSecret);
will(returnValue(masterKey)); will(returnValue(masterSecret));
}}); }});
// execute // execute
assertThat(masterKey, is(equalTo(protocol.perform()))); assertThat(masterSecret, is(equalTo(protocol.perform())));
} }
@Test(expected = AbortException.class) @Test(expected = AbortException.class)
@@ -373,8 +373,8 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
// Alice aborts // Alice aborts
oneOf(transport).sendAbort(false); oneOf(transport).sendAbort(false);
// Alice never derives master key // Alice never computes master secret
never(crypto).deriveKey(MASTER_KEY_LABEL, sharedSecret); never(crypto).deriveKey(MASTER_SECRET_LABEL, sharedSecret);
}}); }});
// execute // execute

View File

@@ -17,7 +17,6 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -29,8 +28,8 @@ public class ConnectionRegistryImplTest extends BrambleTestCase {
private final TransportId transportId, transportId1; private final TransportId transportId, transportId1;
public ConnectionRegistryImplTest() { public ConnectionRegistryImplTest() {
contactId = getContactId(); contactId = new ContactId(1);
contactId1 = getContactId(); contactId1 = new ContactId(2);
transportId = getTransportId(); transportId = getTransportId();
transportId1 = getTransportId(); transportId1 = getTransportId();
} }

View File

@@ -1,7 +1,7 @@
package org.briarproject.bramble.plugin; package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent; import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent;
import org.briarproject.bramble.api.plugin.ConnectionManager; import org.briarproject.bramble.api.plugin.ConnectionManager;
import org.briarproject.bramble.api.plugin.ConnectionRegistry; import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.Plugin;
@@ -36,7 +36,6 @@ import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
public class PollerTest extends BrambleMockTestCase { public class PollerTest extends BrambleMockTestCase {
@@ -57,7 +56,7 @@ public class PollerTest extends BrambleMockTestCase {
private final Executor ioExecutor = new ImmediateExecutor(); private final Executor ioExecutor = new ImmediateExecutor();
private final TransportId transportId = getTransportId(); private final TransportId transportId = getTransportId();
private final ContactId contactId = getContactId(); private final ContactId contactId = new ContactId(234);
private final TransportProperties properties = new TransportProperties(); private final TransportProperties properties = new TransportProperties();
private final int pollingInterval = 60 * 1000; private final int pollingInterval = 60 * 1000;
private final long now = System.currentTimeMillis(); private final long now = System.currentTimeMillis();
@@ -68,7 +67,7 @@ public class PollerTest extends BrambleMockTestCase {
} }
@Test @Test
public void testConnectOnContactAdded() throws Exception { public void testConnectOnContactStatusChanged() throws Exception {
// Two simplex plugins: one supports polling, the other doesn't // Two simplex plugins: one supports polling, the other doesn't
SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class); SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
SimplexPlugin simplexPlugin1 = SimplexPlugin simplexPlugin1 =
@@ -144,7 +143,7 @@ public class PollerTest extends BrambleMockTestCase {
connectionRegistry, pluginManager, transportPropertyManager, connectionRegistry, pluginManager, transportPropertyManager,
random, clock); random, clock);
p.eventOccurred(new ContactAddedEvent(contactId)); p.eventOccurred(new ContactStatusChangedEvent(contactId, true));
} }
@Test @Test

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