diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/UniqueId.java b/bramble-api/src/main/java/org/briarproject/bramble/api/UniqueId.java index efa69358f..352f4ac8a 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/UniqueId.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/UniqueId.java @@ -6,14 +6,14 @@ import javax.annotation.concurrent.ThreadSafe; @ThreadSafe @NotNullByDefault -public abstract class UniqueId extends Bytes { +public class UniqueId extends Bytes { /** * The length of a unique identifier in bytes. */ public static final int LENGTH = 32; - protected UniqueId(byte[] id) { + public UniqueId(byte[] id) { super(id); if (id.length != LENGTH) throw new IllegalArgumentException(); } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/client/ClientHelper.java b/bramble-api/src/main/java/org/briarproject/bramble/api/client/ClientHelper.java index aed6d37f8..262d82c9b 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/client/ClientHelper.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/client/ClientHelper.java @@ -9,6 +9,7 @@ import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.properties.TransportProperties; @@ -20,6 +21,8 @@ import java.security.GeneralSecurityException; import java.util.Collection; import java.util.Map; +import javax.annotation.Nullable; + @NotNullByDefault public interface ClientHelper { @@ -123,6 +126,18 @@ public interface ClientHelper { Map parseAndValidateTransportPropertiesMap( BdfDictionary properties) throws FormatException; + /** + * Parse and validate the property dictionary of a Mailbox property update + * message. + * + * @return the properties for using the Mailbox, or null if there is no + * Mailbox available + * @throws FormatException if the properties are not valid + */ + @Nullable + MailboxPropertiesUpdate parseAndValidateMailboxPropertiesUpdate( + BdfDictionary properties) throws FormatException; + /** * Retrieves the contact ID from the group metadata of the given contact * group. diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/PendingContactId.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/PendingContactId.java index 535f8f531..6a601fbc7 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/PendingContactId.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/PendingContactId.java @@ -3,7 +3,6 @@ package org.briarproject.bramble.api.contact; import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; /** @@ -17,9 +16,4 @@ public class PendingContactId extends UniqueId { public PendingContactId(byte[] id) { super(id); } - - @Override - public boolean equals(@Nullable Object o) { - return o instanceof PendingContactId && super.equals(o); - } } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java index feae36bbf..7dba85835 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java @@ -1,5 +1,6 @@ package org.briarproject.bramble.api.crypto; +import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import java.security.GeneralSecurityException; @@ -10,6 +11,8 @@ import javax.annotation.Nullable; @NotNullByDefault public interface CryptoComponent { + UniqueId generateUniqueId(); + SecretKey generateSecretKey(); SecureRandom getSecureRandom(); @@ -172,9 +175,11 @@ public interface CryptoComponent { String asciiArmour(byte[] b, int lineLength); /** - * Encode the onion/hidden service address given its public key. As - * specified here: https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt?id=29245fd5#n2135 + * Encode the Onion given its public key. Specified here: + * https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt?id=29245fd5#n2135 + * + * @return the encoded onion, base32 chars */ - String encodeOnionAddress(byte[] publicKey); + String encodeOnion(byte[] publicKey); } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/AuthorId.java b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/AuthorId.java index b9793b477..6a9d5681d 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/AuthorId.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/AuthorId.java @@ -21,9 +21,4 @@ public class AuthorId extends UniqueId { public AuthorId(byte[] id) { super(id); } - - @Override - public boolean equals(Object o) { - return o instanceof AuthorId && super.equals(o); - } } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxProperties.java b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxProperties.java index 7d8d3c5c4..56b8fe05d 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxProperties.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxProperties.java @@ -23,6 +23,11 @@ public class MailboxProperties { return baseUrl; } + public String getOnion() { + return baseUrl.replaceFirst("^http://", "") + .replaceFirst("\\.onion$", ""); + } + public MailboxAuthToken getAuthToken() { return authToken; } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxPropertiesUpdate.java b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxPropertiesUpdate.java new file mode 100644 index 000000000..fb37448dd --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxPropertiesUpdate.java @@ -0,0 +1,41 @@ +package org.briarproject.bramble.api.mailbox; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public class MailboxPropertiesUpdate { + + private final String onion; + private final MailboxAuthToken authToken; + private final MailboxFolderId inboxId; + private final MailboxFolderId outboxId; + + public MailboxPropertiesUpdate(String onion, + MailboxAuthToken authToken, MailboxFolderId inboxId, + MailboxFolderId outboxId) { + this.onion = onion; + this.authToken = authToken; + this.inboxId = inboxId; + this.outboxId = outboxId; + } + + public String getOnion() { + return onion; + } + + public MailboxAuthToken getAuthToken() { + return authToken; + } + + public MailboxFolderId getInboxId() { + return inboxId; + } + + public MailboxFolderId getOutboxId() { + return outboxId; + } + +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxPropertyManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxPropertyManager.java new file mode 100644 index 000000000..b0ecf8fdd --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxPropertyManager.java @@ -0,0 +1,67 @@ +package org.briarproject.bramble.api.mailbox; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.ClientId; + +import javax.annotation.Nullable; + +@NotNullByDefault +public interface MailboxPropertyManager { + + /** + * The unique ID of the mailbox property client. + */ + ClientId CLIENT_ID = + new ClientId("org.briarproject.bramble.mailbox.properties"); + + /** + * The current major version of the mailbox property client. + */ + int MAJOR_VERSION = 0; + + /** + * The current minor version of the mailbox property client. + */ + int MINOR_VERSION = 0; + + /** + * The number of properties required for a (non-empty) update message. + */ + int PROP_COUNT = 4; + + /** + * The required properties of a non-empty update message. + */ + String PROP_KEY_ONION = "onion"; + String PROP_KEY_AUTHTOKEN = "authToken"; + String PROP_KEY_INBOXID = "inboxId"; + String PROP_KEY_OUTBOXID = "outboxId"; + + /** + * Length of the Onion property. + */ + int PROP_ONION_LENGTH = 56; + + /** + * Message metadata key for the version number of a local or remote update, + * as a BDF long. + */ + String MSG_KEY_VERSION = "version"; + + /** + * Message metadata key for whether an update is local or remote, as a BDF + * boolean. + */ + String MSG_KEY_LOCAL = "local"; + + @Nullable + MailboxPropertiesUpdate getLocalProperties(Transaction txn, ContactId c) + throws DbException; + + @Nullable + MailboxPropertiesUpdate getRemoteProperties(Transaction txn, ContactId c) + throws DbException; +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxSettingsManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxSettingsManager.java index 58e1df738..bad4ef71c 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxSettingsManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxSettingsManager.java @@ -1,8 +1,10 @@ package org.briarproject.bramble.api.mailbox; import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import javax.annotation.Nullable; @@ -10,6 +12,13 @@ import javax.annotation.Nullable; @NotNullByDefault public interface MailboxSettingsManager { + /** + * Registers a hook to be called when a mailbox has been paired or unpaired. + * This method should be called before + * {@link LifecycleManager#startServices(SecretKey)}. + */ + void registerMailboxHook(MailboxHook hook); + @Nullable MailboxProperties getOwnMailboxProperties(Transaction txn) throws DbException; @@ -30,4 +39,22 @@ public interface MailboxSettingsManager { @Nullable String getPendingUpload(Transaction txn, ContactId id) throws DbException; + + interface MailboxHook { + /** + * Called when Briar is paired with a mailbox + * + * @param txn A read-write transaction + * @param ownOnion Our new mailbox's onion (56 base32 chars) + */ + void mailboxPaired(Transaction txn, String ownOnion) + throws DbException; + + /** + * Called when the mailbox is unpaired + * + * @param txn A read-write transaction + */ + void mailboxUnpaired(Transaction txn) throws DbException; + } } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/GroupId.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/GroupId.java index d118ed9c0..6cfef9076 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/GroupId.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/GroupId.java @@ -20,9 +20,4 @@ public class GroupId extends UniqueId { public GroupId(byte[] id) { super(id); } - - @Override - public boolean equals(Object o) { - return o instanceof GroupId && super.equals(o); - } } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageId.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageId.java index 907b6e2d0..d25584eba 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageId.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageId.java @@ -27,9 +27,4 @@ public class MessageId extends UniqueId { public MessageId(byte[] id) { super(id); } - - @Override - public boolean equals(Object o) { - return o instanceof MessageId && super.equals(o); - } } diff --git a/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java b/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java index f5d1b9ed1..d5a2b7c65 100644 --- a/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java +++ b/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java @@ -16,6 +16,7 @@ import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.Identity; import org.briarproject.bramble.api.identity.LocalAuthor; +import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.sync.ClientId; @@ -39,6 +40,8 @@ import java.util.Map; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nullable; + import static java.util.Arrays.asList; import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_PUBLIC_KEY_BYTES; @@ -271,4 +274,17 @@ public class TestUtils { return optionalTests != null && asList(optionalTests.split(",")).contains(testClass.getName()); } + + public static boolean mailboxPropertiesUpdateEqual( + @Nullable MailboxPropertiesUpdate a, + @Nullable MailboxPropertiesUpdate b) { + if (a == null || b == null) { + return a == b; + } + return a.getOnion().equals(b.getOnion()) && + a.getAuthToken().equals(b.getAuthToken()) && + a.getInboxId().equals(b.getInboxId()) && + a.getOutboxId().equals(b.getOutboxId()); + } + } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/client/ClientHelperImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/client/ClientHelperImpl.java index f483acdc8..bab9a18e6 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/client/ClientHelperImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/client/ClientHelperImpl.java @@ -1,6 +1,7 @@ package org.briarproject.bramble.client; import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.crypto.CryptoComponent; @@ -22,6 +23,9 @@ import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.AuthorFactory; +import org.briarproject.bramble.api.mailbox.MailboxAuthToken; +import org.briarproject.bramble.api.mailbox.MailboxFolderId; +import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.properties.TransportProperties; @@ -29,6 +33,7 @@ import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageFactory; import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.util.Base32; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -39,6 +44,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; @@ -46,6 +52,12 @@ import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KE import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_COUNT; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_AUTHTOKEN; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_INBOXID; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_ONION; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_OUTBOXID; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_ONION_LENGTH; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH; import static org.briarproject.bramble.util.ValidationUtils.checkLength; @@ -399,6 +411,35 @@ class ClientHelperImpl implements ClientHelper { return tpMap; } + @Override + @Nullable + public MailboxPropertiesUpdate parseAndValidateMailboxPropertiesUpdate( + BdfDictionary properties) throws FormatException { + if (properties.isEmpty()) { + return null; + } + // Accepting more props than we need, for forward compatibility + if (properties.size() < PROP_COUNT) { + throw new FormatException(); + } + String onion = properties.getString(PROP_KEY_ONION); + checkLength(onion, PROP_ONION_LENGTH); + try { + Base32.decode(onion, true); + } catch (IllegalArgumentException e) { + throw new FormatException(); + } + byte[] authToken = properties.getRaw(PROP_KEY_AUTHTOKEN); + checkLength(authToken, UniqueId.LENGTH); + byte[] inboxId = properties.getRaw(PROP_KEY_INBOXID); + checkLength(inboxId, UniqueId.LENGTH); + byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID); + checkLength(outboxId, UniqueId.LENGTH); + return new MailboxPropertiesUpdate(onion, + new MailboxAuthToken(authToken), new MailboxFolderId(inboxId), + new MailboxFolderId(outboxId)); + } + @Override public ContactId getContactId(Transaction txn, GroupId contactGroupId) throws DbException { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java index 39ecef7a3..0d1577798 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java @@ -8,6 +8,7 @@ import org.bouncycastle.crypto.CryptoException; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.digests.Blake2bDigest; import org.bouncycastle.crypto.digests.SHA3Digest; +import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.crypto.AgreementPrivateKey; import org.briarproject.bramble.api.crypto.AgreementPublicKey; import org.briarproject.bramble.api.crypto.CryptoComponent; @@ -41,6 +42,7 @@ import javax.inject.Inject; import static java.lang.System.arraycopy; import static java.util.logging.Level.INFO; +import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT; import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE; import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT; @@ -54,7 +56,7 @@ import static org.briarproject.bramble.util.LogUtils.now; class CryptoComponentImpl implements CryptoComponent { private static final Logger LOG = - Logger.getLogger(CryptoComponentImpl.class.getName()); + getLogger(CryptoComponentImpl.class.getName()); private static final int SIGNATURE_KEY_PAIR_BITS = 256; private static final int STORAGE_IV_BYTES = 24; // 196 bits @@ -128,6 +130,13 @@ class CryptoComponentImpl implements CryptoComponent { } } + @Override + public UniqueId generateUniqueId() { + byte[] b = new byte[UniqueId.LENGTH]; + secureRandom.nextBytes(b); + return new UniqueId(b); + } + @Override public SecretKey generateSecretKey() { byte[] b = new byte[SecretKey.LENGTH]; @@ -449,7 +458,7 @@ class CryptoComponentImpl implements CryptoComponent { } @Override - public String encodeOnionAddress(byte[] publicKey) { + public String encodeOnion(byte[] publicKey) { Digest digest = new SHA3Digest(256); byte[] label = ".onion checksum".getBytes(Charset.forName("US-ASCII")); digest.update(label, 0, label.length); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxModule.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxModule.java index 85e925202..a89065011 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxModule.java @@ -1,16 +1,36 @@ package org.briarproject.bramble.mailbox; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.data.MetadataEncoder; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.mailbox.MailboxManager; +import org.briarproject.bramble.api.mailbox.MailboxPropertyManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; +import org.briarproject.bramble.api.sync.validation.ValidationManager; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; +import javax.inject.Inject; import javax.inject.Singleton; import dagger.Module; import dagger.Provides; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.CLIENT_ID; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MAJOR_VERSION; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MINOR_VERSION; + @Module public class MailboxModule { + public static class EagerSingletons { + @Inject + MailboxPropertyValidator mailboxPropertyValidator; + @Inject + MailboxPropertyManager mailboxPropertyManager; + } + @Provides @Singleton MailboxManager providesMailboxManager(MailboxManagerImpl mailboxManager) { @@ -33,4 +53,34 @@ public class MailboxModule { MailboxApi providesMailboxApi(MailboxApiImpl mailboxApi) { return mailboxApi; } + + @Provides + @Singleton + MailboxPropertyValidator provideMailboxPropertyValidator( + ValidationManager validationManager, ClientHelper clientHelper, + MetadataEncoder metadataEncoder, Clock clock) { + MailboxPropertyValidator validator = new MailboxPropertyValidator( + clientHelper, metadataEncoder, clock); + validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION, + validator); + return validator; + } + + @Provides + @Singleton + MailboxPropertyManager provideMailboxPropertyManager( + LifecycleManager lifecycleManager, + ValidationManager validationManager, ContactManager contactManager, + ClientVersioningManager clientVersioningManager, + MailboxSettingsManager mailboxSettingsManager, + MailboxPropertyManagerImpl mailboxPropertyManager) { + lifecycleManager.registerOpenDatabaseHook(mailboxPropertyManager); + validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION, + mailboxPropertyManager); + contactManager.registerContactHook(mailboxPropertyManager); + clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION, + MINOR_VERSION, mailboxPropertyManager); + mailboxSettingsManager.registerMailboxHook(mailboxPropertyManager); + return mailboxPropertyManager; + } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImpl.java index 32ad7d727..3873e2372 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImpl.java @@ -162,8 +162,8 @@ class MailboxPairingTaskImpl implements MailboxPairingTask { } LOG.info("QR code is valid"); byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33); - String onionAddress = crypto.encodeOnionAddress(onionPubKey); - String baseUrl = "http://" + onionAddress + ".onion"; + String onion = crypto.encodeOnion(onionPubKey); + String baseUrl = "http://" + onion + ".onion"; byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65); MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes); return new MailboxProperties(baseUrl, setupToken, true); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPropertyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPropertyManagerImpl.java new file mode 100644 index 000000000..18d0d0f40 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPropertyManagerImpl.java @@ -0,0 +1,296 @@ +package org.briarproject.bramble.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.client.ContactGroupFactory; +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.contact.ContactManager.ContactHook; +import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.data.MetadataParser; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Metadata; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook; +import org.briarproject.bramble.api.mailbox.MailboxAuthToken; +import org.briarproject.bramble.api.mailbox.MailboxFolderId; +import org.briarproject.bramble.api.mailbox.MailboxProperties; +import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate; +import org.briarproject.bramble.api.mailbox.MailboxPropertyManager; +import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; +import org.briarproject.bramble.api.mailbox.MailboxSettingsManager.MailboxHook; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.sync.Group.Visibility; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.InvalidMessageException; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.sync.validation.IncomingMessageHook; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; +import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; + +import java.util.Map; +import java.util.Map.Entry; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; + +@NotNullByDefault +class MailboxPropertyManagerImpl implements MailboxPropertyManager, + OpenDatabaseHook, ContactHook, ClientVersioningHook, + IncomingMessageHook, MailboxHook { + + private final DatabaseComponent db; + private final ClientHelper clientHelper; + private final ClientVersioningManager clientVersioningManager; + private final MetadataParser metadataParser; + private final ContactGroupFactory contactGroupFactory; + private final Clock clock; + private final MailboxSettingsManager mailboxSettingsManager; + private final ContactManager contactManager; + private final CryptoComponent crypto; + private final Group localGroup; + + @Inject + MailboxPropertyManagerImpl(DatabaseComponent db, ClientHelper clientHelper, + ClientVersioningManager clientVersioningManager, + MetadataParser metadataParser, + ContactGroupFactory contactGroupFactory, Clock clock, + MailboxSettingsManager mailboxSettingsManager, + ContactManager contactManager, + CryptoComponent crypto) { + this.db = db; + this.clientHelper = clientHelper; + this.clientVersioningManager = clientVersioningManager; + this.metadataParser = metadataParser; + this.contactGroupFactory = contactGroupFactory; + this.clock = clock; + this.mailboxSettingsManager = mailboxSettingsManager; + this.contactManager = contactManager; + this.crypto = crypto; + localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID, + MAJOR_VERSION); + } + + @Override + public void onDatabaseOpened(Transaction txn) throws DbException { + if (db.containsGroup(txn, localGroup.getId())) { + return; + } + db.addGroup(txn, localGroup); + // Set things up for any pre-existing contacts + for (Contact c : db.getContacts(txn)) { + addingContact(txn, c); + } + } + + @Override + public void addingContact(Transaction txn, Contact c) throws DbException { + // Create a group to share with the contact + Group g = getContactGroup(c); + db.addGroup(txn, g); + // Apply the client's visibility to the contact group + Visibility client = clientVersioningManager + .getClientVisibility(txn, c.getId(), CLIENT_ID, MAJOR_VERSION); + db.setGroupVisibility(txn, c.getId(), g.getId(), client); + // If we are paired, create and send props to the newly added contact + MailboxProperties ownProps = + mailboxSettingsManager.getOwnMailboxProperties(txn); + if (ownProps != null) { + createAndSendProperties(txn, c, ownProps.getOnion()); + } + } + + @Override + public void removingContact(Transaction txn, Contact c) throws DbException { + db.removeGroup(txn, getContactGroup(c)); + } + + @Override + public void mailboxPaired(Transaction txn, String ownOnion) + throws DbException { + for (Contact c : contactManager.getContacts()) { + createAndSendProperties(txn, c, ownOnion); + } + } + + @Override + public void mailboxUnpaired(Transaction txn) throws DbException { + for (Contact c : contactManager.getContacts()) { + sendEmptyProperties(txn, c); + } + } + + @Override + public void onClientVisibilityChanging(Transaction txn, Contact c, + Visibility v) throws DbException { + // Apply the client's visibility to the contact group + Group g = getContactGroup(c); + db.setGroupVisibility(txn, c.getId(), g.getId(), v); + } + + @Override + public DeliveryAction incomingMessage(Transaction txn, Message m, + Metadata meta) throws DbException, InvalidMessageException { + try { + BdfDictionary d = metadataParser.parse(meta); + // Get latest non-local update in the same group (from same contact) + LatestUpdate latest = findLatest(txn, m.getGroupId(), false); + if (latest != null) { + if (d.getLong(MSG_KEY_VERSION) > latest.version) { + db.deleteMessage(txn, latest.messageId); + db.deleteMessageMetadata(txn, latest.messageId); + } else { + // Delete this update, we already have a newer one + db.deleteMessage(txn, m.getId()); + db.deleteMessageMetadata(txn, m.getId()); + return ACCEPT_DO_NOT_SHARE; + } + } + // TODO should probably broadcast an event that a contact's mailbox + // properties were updated + } catch (FormatException e) { + throw new InvalidMessageException(e); + } + return ACCEPT_DO_NOT_SHARE; + } + + @Override + @Nullable + public MailboxPropertiesUpdate getLocalProperties(Transaction txn, + ContactId c) throws DbException { + return getProperties(txn, db.getContact(txn, c), true); + } + + @Override + @Nullable + public MailboxPropertiesUpdate getRemoteProperties(Transaction txn, + ContactId c) throws DbException { + return getProperties(txn, db.getContact(txn, c), false); + } + + /** + * Creates and sends an update message to the given contact. The message + * holds our own mailbox's onion, and generated unique properties. All of + * which the contact needs to communicate with our Mailbox. + */ + private void createAndSendProperties(Transaction txn, + Contact c, String ownOnion) throws DbException { + MailboxPropertiesUpdate p = new MailboxPropertiesUpdate(ownOnion, + new MailboxAuthToken(crypto.generateUniqueId().getBytes()), + new MailboxFolderId(crypto.generateUniqueId().getBytes()), + new MailboxFolderId(crypto.generateUniqueId().getBytes())); + Group g = getContactGroup(c); + storeMessageReplaceLatest(txn, g.getId(), p); + } + + /** + * Sends an empty update message to the given contact. The empty update + * indicates for the receiving contact that we no longer have a Mailbox that + * they can use. + */ + private void sendEmptyProperties(Transaction txn, Contact c) + throws DbException { + Group g = getContactGroup(c); + storeMessageReplaceLatest(txn, g.getId(), null); + } + + @Nullable + private MailboxPropertiesUpdate getProperties(Transaction txn, + Contact c, boolean local) throws DbException { + MailboxPropertiesUpdate p = null; + Group g = getContactGroup(c); + try { + LatestUpdate latest = findLatest(txn, g.getId(), local); + if (latest != null) { + BdfList body = + clientHelper.getMessageAsList(txn, latest.messageId); + p = parseProperties(body); + } + } catch (FormatException e) { + throw new DbException(e); + } + return p; + } + + private void storeMessageReplaceLatest(Transaction txn, GroupId g, + @Nullable MailboxPropertiesUpdate p) throws DbException { + try { + LatestUpdate latest = findLatest(txn, g, true); + long version = latest == null ? 1 : latest.version + 1; + Message m = clientHelper.createMessage(g, clock.currentTimeMillis(), + encodeProperties(version, p)); + BdfDictionary meta = new BdfDictionary(); + meta.put(MSG_KEY_VERSION, version); + meta.put(MSG_KEY_LOCAL, true); + clientHelper.addLocalMessage(txn, m, meta, true, false); + if (latest != null) { + db.removeMessage(txn, latest.messageId); + } + } catch (FormatException e) { + throw new DbException(e); + } + } + + @Nullable + private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local) + throws DbException, FormatException { + Map metadata = + clientHelper.getMessageMetadataAsDictionary(txn, g); + // We should have at most 1 local and 1 remote + if (metadata.size() > 2) { + throw new IllegalStateException(); + } + for (Entry e : metadata.entrySet()) { + BdfDictionary meta = e.getValue(); + if (meta.getBoolean(MSG_KEY_LOCAL) == local) { + return new LatestUpdate(e.getKey(), + meta.getLong(MSG_KEY_VERSION)); + } + } + return null; + } + + private BdfList encodeProperties(long version, + @Nullable MailboxPropertiesUpdate p) { + BdfDictionary dict = new BdfDictionary(); + if (p != null) { + dict.put(PROP_KEY_ONION, p.getOnion()); + dict.put(PROP_KEY_AUTHTOKEN, p.getAuthToken().getBytes()); + dict.put(PROP_KEY_INBOXID, p.getInboxId().getBytes()); + dict.put(PROP_KEY_OUTBOXID, p.getOutboxId().getBytes()); + } + return BdfList.of(version, dict); + } + + @Nullable + private MailboxPropertiesUpdate parseProperties(BdfList body) + throws FormatException { + BdfDictionary dict = body.getDictionary(1); + return clientHelper.parseAndValidateMailboxPropertiesUpdate(dict); + } + + private Group getContactGroup(Contact c) { + return contactGroupFactory.createContactGroup(CLIENT_ID, MAJOR_VERSION, + c); + } + + private static class LatestUpdate { + + private final MessageId messageId; + private final long version; + + private LatestUpdate(MessageId messageId, long version) { + this.messageId = messageId; + this.version = version; + } + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPropertyValidator.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPropertyValidator.java new file mode 100644 index 000000000..32b581fc0 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPropertyValidator.java @@ -0,0 +1,49 @@ +package org.briarproject.bramble.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.BdfMessageContext; +import org.briarproject.bramble.api.client.BdfMessageValidator; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.data.MetadataEncoder; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.sync.InvalidMessageException; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.system.Clock; + +import javax.annotation.concurrent.Immutable; + +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_LOCAL; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_VERSION; +import static org.briarproject.bramble.util.ValidationUtils.checkSize; + +@Immutable +@NotNullByDefault +class MailboxPropertyValidator extends BdfMessageValidator { + + MailboxPropertyValidator(ClientHelper clientHelper, + MetadataEncoder metadataEncoder, Clock clock) { + super(clientHelper, metadataEncoder, clock); + } + + @Override + protected BdfMessageContext validateMessage(Message m, Group g, + BdfList body) throws InvalidMessageException, FormatException { + // Version, properties + checkSize(body, 2); + // Version + long version = body.getLong(0); + if (version < 0) throw new FormatException(); + // Properties + BdfDictionary dictionary = body.getDictionary(1); + clientHelper.parseAndValidateMailboxPropertiesUpdate(dictionary); + // Return the metadata + BdfDictionary meta = new BdfDictionary(); + meta.put(MSG_KEY_VERSION, version); + meta.put(MSG_KEY_LOCAL, false); + return new BdfMessageContext(meta); + } + +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImpl.java index 4e0065037..fe74bc6b6 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImpl.java @@ -12,6 +12,9 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.SettingsManager; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; @@ -32,12 +35,18 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager { static final String SETTINGS_UPLOADS_NAMESPACE = "mailbox-uploads"; private final SettingsManager settingsManager; + private final List hooks = new CopyOnWriteArrayList<>(); @Inject MailboxSettingsManagerImpl(SettingsManager settingsManager) { this.settingsManager = settingsManager; } + @Override + public void registerMailboxHook(MailboxHook hook) { + hooks.add(hook); + } + @Override public MailboxProperties getOwnMailboxProperties(Transaction txn) throws DbException { @@ -60,6 +69,9 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager { s.put(SETTINGS_KEY_ONION, p.getBaseUrl()); s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString()); settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE); + for (MailboxHook hook : hooks) { + hook.mailboxPaired(txn, p.getOnion()); + } } @Override diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java index f443b01bd..3919c50af 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java @@ -682,8 +682,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { byte[] localSeed = alice ? aliceSeed : bobSeed; byte[] remoteSeed = alice ? bobSeed : aliceSeed; String blob = torRendezvousCrypto.getPrivateKeyBlob(localSeed); - String localOnion = torRendezvousCrypto.getOnionAddress(localSeed); - String remoteOnion = torRendezvousCrypto.getOnionAddress(remoteSeed); + String localOnion = torRendezvousCrypto.getOnion(localSeed); + String remoteOnion = torRendezvousCrypto.getOnion(remoteSeed); TransportProperties remoteProperties = new TransportProperties(); remoteProperties.put(PROP_ONION_V3, remoteOnion); try { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCrypto.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCrypto.java index a5b32630e..87f34dc7a 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCrypto.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCrypto.java @@ -4,7 +4,7 @@ interface TorRendezvousCrypto { static final int SEED_BYTES = 32; - String getOnionAddress(byte[] seed); + String getOnion(byte[] seed); String getPrivateKeyBlob(byte[] seed); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCryptoImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCryptoImpl.java index cf16c3fe9..d2b75322a 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCryptoImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCryptoImpl.java @@ -21,9 +21,9 @@ class TorRendezvousCryptoImpl implements TorRendezvousCrypto { } @Override - public String getOnionAddress(byte[] seed) { + public String getOnion(byte[] seed) { EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC); - return crypto.encodeOnionAddress(spec.getA().toByteArray()); + return crypto.encodeOnion(spec.getA().toByteArray()); } @Override diff --git a/bramble-core/src/test/java/org/briarproject/bramble/client/ClientHelperImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/client/ClientHelperImplTest.java index 489e1b8e5..6214d67c5 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/client/ClientHelperImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/client/ClientHelperImplTest.java @@ -1,6 +1,7 @@ package org.briarproject.bramble.client; import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.KeyParser; @@ -20,13 +21,15 @@ import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.AuthorFactory; +import org.briarproject.bramble.api.mailbox.MailboxAuthToken; +import org.briarproject.bramble.api.mailbox.MailboxFolderId; +import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageFactory; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.DbExpectations; -import org.briarproject.bramble.util.StringUtils; import org.jmock.Expectations; import org.junit.Test; @@ -41,15 +44,22 @@ import java.util.Random; 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_SIGNATURE_LENGTH; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_AUTHTOKEN; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_INBOXID; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_ONION; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_OUTBOXID; import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getSignaturePrivateKey; import static org.briarproject.bramble.test.TestUtils.getSignaturePublicKey; +import static org.briarproject.bramble.test.TestUtils.mailboxPropertiesUpdateEqual; import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class ClientHelperImplTest extends BrambleMockTestCase { @@ -78,13 +88,35 @@ public class ClientHelperImplTest extends BrambleMockTestCase { private final long timestamp = message.getTimestamp(); private final Metadata metadata = new Metadata(); private final BdfList list = BdfList.of("Sign this!", getRandomBytes(42)); - private final String label = StringUtils.getRandomString(5); + private final String label = getRandomString(5); private final Author author = getAuthor(); private final ClientHelper clientHelper = new ClientHelperImpl(db, messageFactory, bdfReaderFactory, bdfWriterFactory, metadataParser, metadataEncoder, cryptoComponent, authorFactory); + private final MailboxPropertiesUpdate validMailboxPropsUpdate; + + public ClientHelperImplTest() { + validMailboxPropsUpdate = new MailboxPropertiesUpdate( + "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd", + new MailboxAuthToken(getRandomId()), + new MailboxFolderId(getRandomId()), + new MailboxFolderId(getRandomId())); + } + + private BdfDictionary getValidMailboxPropsUpdateDict() { + BdfDictionary dict = new BdfDictionary(); + dict.put(PROP_KEY_ONION, validMailboxPropsUpdate.getOnion()); + dict.put(PROP_KEY_AUTHTOKEN, validMailboxPropsUpdate.getAuthToken() + .getBytes()); + dict.put(PROP_KEY_INBOXID, validMailboxPropsUpdate.getInboxId() + .getBytes()); + dict.put(PROP_KEY_OUTBOXID, validMailboxPropsUpdate.getOutboxId() + .getBytes()); + return dict; + } + @Test public void testAddLocalMessage() throws Exception { boolean shared = new Random().nextBoolean(); @@ -513,4 +545,95 @@ public class ClientHelperImplTest extends BrambleMockTestCase { will(returnValue(eof)); }}); } + + @Test + public void testParseEmptyMailboxPropsUpdate() throws Exception { + BdfDictionary emptyPropsDict = new BdfDictionary(); + MailboxPropertiesUpdate parsedProps = clientHelper + .parseAndValidateMailboxPropertiesUpdate(emptyPropsDict); + assertNull(parsedProps); + } + + @Test + public void testParseValidMailboxPropsUpdate() throws Exception { + MailboxPropertiesUpdate parsedProps = clientHelper + .parseAndValidateMailboxPropertiesUpdate( + getValidMailboxPropsUpdateDict()); + assertTrue(mailboxPropertiesUpdateEqual(validMailboxPropsUpdate, + parsedProps)); + } + + @Test(expected = FormatException.class) + public void testRejectsMailboxPropsUpdateOnionNotDecodable() + throws Exception { + BdfDictionary propsDict = getValidMailboxPropsUpdateDict(); + String badOnion = "!" + propsDict.getString(PROP_KEY_ONION) + .substring(1); + propsDict.put(PROP_KEY_ONION, badOnion); + clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict); + } + + @Test(expected = FormatException.class) + public void testRejectsMailboxPropsUpdateOnionWrongLength() + throws Exception { + BdfDictionary propsDict = getValidMailboxPropsUpdateDict(); + String tooLongOnion = propsDict.getString(PROP_KEY_ONION) + "!"; + propsDict.put(PROP_KEY_ONION, tooLongOnion); + clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict); + } + + @Test(expected = FormatException.class) + public void testRejectsMailboxPropsUpdateInboxIdWrongLength() + throws Exception { + BdfDictionary propsDict = getValidMailboxPropsUpdateDict(); + propsDict.put(PROP_KEY_INBOXID, getRandomBytes(UniqueId.LENGTH + 1)); + clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict); + } + + @Test(expected = FormatException.class) + public void testRejectsMailboxPropsUpdateOutboxIdWrongLength() + throws Exception { + BdfDictionary propsDict = getValidMailboxPropsUpdateDict(); + propsDict.put(PROP_KEY_OUTBOXID, getRandomBytes(UniqueId.LENGTH + 1)); + clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict); + } + + @Test(expected = FormatException.class) + public void testRejectsMailboxPropsUpdateAuthTokenWrongLength() + throws Exception { + BdfDictionary propsDict = getValidMailboxPropsUpdateDict(); + propsDict.put(PROP_KEY_AUTHTOKEN, getRandomBytes(UniqueId.LENGTH + 1)); + clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict); + } + + @Test(expected = FormatException.class) + public void testRejectsMailboxPropsUpdateMissingOnion() throws Exception { + BdfDictionary propsDict = getValidMailboxPropsUpdateDict(); + propsDict.remove(PROP_KEY_ONION); + clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict); + } + + @Test(expected = FormatException.class) + public void testRejectsMailboxPropsUpdateMissingAuthToken() + throws Exception { + BdfDictionary propsDict = getValidMailboxPropsUpdateDict(); + propsDict.remove(PROP_KEY_AUTHTOKEN); + clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict); + } + + @Test(expected = FormatException.class) + public void testRejectsMailboxPropsUpdateMissingInboxId() throws Exception { + BdfDictionary propsDict = getValidMailboxPropsUpdateDict(); + propsDict.remove(PROP_KEY_INBOXID); + clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict); + } + + @Test(expected = FormatException.class) + public void testRejectsMailboxPropsUpdateMissingOutboxId() + throws Exception { + BdfDictionary propsDict = getValidMailboxPropsUpdateDict(); + propsDict.remove(PROP_KEY_OUTBOXID); + clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict); + } + } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImplTest.java index 7b8d70779..f960e5b24 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImplTest.java @@ -87,7 +87,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase { @Test public void testSuccessfulPairing() throws Exception { context.checking(new Expectations() {{ - oneOf(crypto).encodeOnionAddress(onionBytes); + oneOf(crypto).encodeOnion(onionBytes); will(returnValue(onion)); oneOf(api).setup(with(matches(setupProperties))); will(returnValue(ownerToken)); @@ -141,7 +141,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase { private void testApiException(Exception e, Class s) throws Exception { context.checking(new Expectations() {{ - oneOf(crypto).encodeOnionAddress(onionBytes); + oneOf(crypto).encodeOnion(onionBytes); will(returnValue(onion)); oneOf(api).setup(with(matches(setupProperties))); will(throwException(e)); @@ -155,7 +155,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase { @Test public void testDbException() throws Exception { context.checking(new Expectations() {{ - oneOf(crypto).encodeOnionAddress(onionBytes); + oneOf(crypto).encodeOnion(onionBytes); will(returnValue(onion)); oneOf(api).setup(with(matches(setupProperties))); will(returnValue(ownerToken)); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPropertyManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPropertyManagerImplTest.java new file mode 100644 index 000000000..68f77c624 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPropertyManagerImplTest.java @@ -0,0 +1,671 @@ +package org.briarproject.bramble.mailbox; + +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.client.ContactGroupFactory; +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfEntry; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.data.MetadataParser; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.Metadata; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.mailbox.MailboxAuthToken; +import org.briarproject.bramble.api.mailbox.MailboxFolderId; +import org.briarproject.bramble.api.mailbox.MailboxProperties; +import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate; +import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.jmock.Expectations; +import org.junit.Test; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.singletonList; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.CLIENT_ID; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MAJOR_VERSION; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_LOCAL; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_VERSION; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_AUTHTOKEN; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_INBOXID; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_ONION; +import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_OUTBOXID; +import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; +import static org.briarproject.bramble.test.TestUtils.getContact; +import static org.briarproject.bramble.test.TestUtils.getGroup; +import static org.briarproject.bramble.test.TestUtils.getMessage; +import static org.briarproject.bramble.test.TestUtils.getRandomId; +import static org.briarproject.bramble.test.TestUtils.mailboxPropertiesUpdateEqual; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class MailboxPropertyManagerImplTest extends BrambleMockTestCase { + + private final DatabaseComponent db = context.mock(DatabaseComponent.class); + private final ClientHelper clientHelper = context.mock(ClientHelper.class); + private final ClientVersioningManager clientVersioningManager = + context.mock(ClientVersioningManager.class); + private final MetadataParser metadataParser = + context.mock(MetadataParser.class); + private final ContactGroupFactory contactGroupFactory = + context.mock(ContactGroupFactory.class); + private final Clock clock = context.mock(Clock.class); + private final CryptoComponent crypto = context.mock(CryptoComponent.class); + private final MailboxSettingsManager mailboxSettingsManager = + context.mock(MailboxSettingsManager.class); + private final ContactManager contactManager = + context.mock(ContactManager.class); + + private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + private final BdfDictionary propsDict; + private final BdfDictionary emptyPropsDict = new BdfDictionary(); + private final MailboxPropertiesUpdate props; + private final MailboxProperties ownProps; + + public MailboxPropertyManagerImplTest() { + ownProps = new MailboxProperties("http://bar.onion", + new MailboxAuthToken(getRandomId()), true); + props = new MailboxPropertiesUpdate(ownProps.getOnion(), + new MailboxAuthToken(getRandomId()), + new MailboxFolderId(getRandomId()), + new MailboxFolderId(getRandomId())); + propsDict = new BdfDictionary(); + propsDict.put(PROP_KEY_ONION, props.getOnion()); + propsDict.put(PROP_KEY_AUTHTOKEN, props.getAuthToken().getBytes()); + propsDict.put(PROP_KEY_INBOXID, props.getInboxId().getBytes()); + propsDict.put(PROP_KEY_OUTBOXID, props.getOutboxId().getBytes()); + } + + private MailboxPropertyManagerImpl createInstance() { + context.checking(new Expectations() {{ + oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID, + MAJOR_VERSION); + will(returnValue(localGroup)); + }}); + return new MailboxPropertyManagerImpl(db, clientHelper, + clientVersioningManager, metadataParser, contactGroupFactory, + clock, mailboxSettingsManager, contactManager, crypto); + } + + @Test + public void testCreatesGroupsAtUnpairedStartup() throws Exception { + Transaction txn = new Transaction(null, false); + Contact contact = getContact(); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + + context.checking(new Expectations() {{ + oneOf(db).containsGroup(txn, localGroup.getId()); + will(returnValue(false)); + oneOf(db).addGroup(txn, localGroup); + oneOf(db).getContacts(txn); + will(returnValue(singletonList(contact))); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(db).addGroup(txn, contactGroup); + oneOf(clientVersioningManager).getClientVisibility(txn, + contact.getId(), CLIENT_ID, MAJOR_VERSION); + will(returnValue(SHARED)); + oneOf(db).setGroupVisibility(txn, contact.getId(), + contactGroup.getId(), SHARED); + oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn); + will(returnValue(null)); + }}); + + MailboxPropertyManagerImpl t = createInstance(); + t.onDatabaseOpened(txn); + } + + @Test + public void testCreatesGroupsAndCreatesAndSendsAtPairedStartup() + throws Exception { + Transaction txn = new Transaction(null, false); + Contact contact = getContact(); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + Map messageMetadata = new LinkedHashMap<>(); + + context.checking(new Expectations() {{ + oneOf(db).containsGroup(txn, localGroup.getId()); + will(returnValue(false)); + oneOf(db).addGroup(txn, localGroup); + oneOf(db).getContacts(txn); + will(returnValue(singletonList(contact))); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(db).addGroup(txn, contactGroup); + oneOf(clientVersioningManager).getClientVisibility(txn, + contact.getId(), CLIENT_ID, MAJOR_VERSION); + will(returnValue(SHARED)); + oneOf(db).setGroupVisibility(txn, contact.getId(), + contactGroup.getId(), SHARED); + oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn); + will(returnValue(ownProps)); + oneOf(crypto).generateUniqueId(); + will(returnValue(props.getAuthToken())); + oneOf(crypto).generateUniqueId(); + will(returnValue(props.getInboxId())); + oneOf(crypto).generateUniqueId(); + will(returnValue(props.getOutboxId())); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + expectStoreMessage(txn, contactGroup.getId(), propsDict, 1, true); + }}); + + MailboxPropertyManagerImpl t = createInstance(); + t.onDatabaseOpened(txn); + } + + @Test + public void testDoesNotCreateGroupsAtStartupIfAlreadyCreated() + throws Exception { + Transaction txn = new Transaction(null, false); + + context.checking(new Expectations() {{ + oneOf(db).containsGroup(txn, localGroup.getId()); + will(returnValue(true)); + }}); + + MailboxPropertyManagerImpl t = createInstance(); + t.onDatabaseOpened(txn); + } + + @Test + public void testCreatesContactGroupWhenAddingContactUnpaired() + throws Exception { + Transaction txn = new Transaction(null, false); + Contact contact = getContact(); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + + context.checking(new Expectations() {{ + // Create the group and share it with the contact + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(db).addGroup(txn, contactGroup); + oneOf(clientVersioningManager).getClientVisibility(txn, + contact.getId(), CLIENT_ID, MAJOR_VERSION); + will(returnValue(SHARED)); + oneOf(db).setGroupVisibility(txn, contact.getId(), + contactGroup.getId(), SHARED); + oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn); + will(returnValue(null)); + }}); + + MailboxPropertyManagerImpl t = createInstance(); + t.addingContact(txn, contact); + } + + @Test + public void testCreatesContactGroupAndCreatesAndSendsWhenAddingContactPaired() + throws Exception { + Transaction txn = new Transaction(null, false); + Contact contact = getContact(); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + Map messageMetadata = new LinkedHashMap<>(); + + context.checking(new Expectations() {{ + // Create the group and share it with the contact + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(db).addGroup(txn, contactGroup); + oneOf(clientVersioningManager).getClientVisibility(txn, + contact.getId(), CLIENT_ID, MAJOR_VERSION); + will(returnValue(SHARED)); + oneOf(db).setGroupVisibility(txn, contact.getId(), + contactGroup.getId(), SHARED); + oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn); + will(returnValue(ownProps)); + oneOf(crypto).generateUniqueId(); + will(returnValue(props.getAuthToken())); + oneOf(crypto).generateUniqueId(); + will(returnValue(props.getInboxId())); + oneOf(crypto).generateUniqueId(); + will(returnValue(props.getOutboxId())); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + expectStoreMessage(txn, contactGroup.getId(), propsDict, 1, true); + }}); + + MailboxPropertyManagerImpl t = createInstance(); + t.addingContact(txn, contact); + } + + @Test + public void testRemovesGroupWhenRemovingContact() throws Exception { + Transaction txn = new Transaction(null, false); + Contact contact = getContact(); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + + context.checking(new Expectations() {{ + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(db).removeGroup(txn, contactGroup); + }}); + + MailboxPropertyManagerImpl t = createInstance(); + t.removingContact(txn, contact); + } + + @Test + public void testDoesNotDeleteAnythingWhenFirstUpdateIsDelivered() + throws Exception { + Transaction txn = new Transaction(null, false); + GroupId contactGroupId = new GroupId(getRandomId()); + Message message = getMessage(contactGroupId); + Metadata meta = new Metadata(); + BdfDictionary metaDictionary = BdfDictionary.of( + new BdfEntry(MSG_KEY_VERSION, 1), + new BdfEntry(MSG_KEY_LOCAL, false) + ); + Map messageMetadata = new LinkedHashMap<>(); + // A local update should be ignored + MessageId localUpdateId = new MessageId(getRandomId()); + messageMetadata.put(localUpdateId, BdfDictionary.of( + new BdfEntry(MSG_KEY_VERSION, 1), + new BdfEntry(MSG_KEY_LOCAL, true) + )); + + context.checking(new Expectations() {{ + oneOf(metadataParser).parse(meta); + will(returnValue(metaDictionary)); + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroupId); + will(returnValue(messageMetadata)); + }}); + + MailboxPropertyManagerImpl t = createInstance(); + assertEquals(ACCEPT_DO_NOT_SHARE, + t.incomingMessage(txn, message, meta)); + } + + @Test + public void testDeletesOlderUpdateWhenUpdateIsDelivered() + throws Exception { + Transaction txn = new Transaction(null, false); + GroupId contactGroupId = new GroupId(getRandomId()); + Message message = getMessage(contactGroupId); + Metadata meta = new Metadata(); + BdfDictionary metaDictionary = BdfDictionary.of( + new BdfEntry(MSG_KEY_VERSION, 2), + new BdfEntry(MSG_KEY_LOCAL, false) + ); + Map messageMetadata = new LinkedHashMap<>(); + // This older version should be deleted + MessageId updateId = new MessageId(getRandomId()); + messageMetadata.put(updateId, BdfDictionary.of( + new BdfEntry(MSG_KEY_VERSION, 1), + new BdfEntry(MSG_KEY_LOCAL, false) + )); + // A local update should be ignored + MessageId localUpdateId = new MessageId(getRandomId()); + messageMetadata.put(localUpdateId, BdfDictionary.of( + new BdfEntry(MSG_KEY_VERSION, 1), + new BdfEntry(MSG_KEY_LOCAL, true) + )); + + context.checking(new Expectations() {{ + oneOf(metadataParser).parse(meta); + will(returnValue(metaDictionary)); + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroupId); + will(returnValue(messageMetadata)); + oneOf(db).deleteMessage(txn, updateId); + oneOf(db).deleteMessageMetadata(txn, updateId); + }}); + + MailboxPropertyManagerImpl t = createInstance(); + assertEquals(ACCEPT_DO_NOT_SHARE, + t.incomingMessage(txn, message, meta)); + } + + @Test + public void testDeletesObsoleteUpdateWhenDelivered() throws Exception { + Transaction txn = new Transaction(null, false); + GroupId contactGroupId = new GroupId(getRandomId()); + Message message = getMessage(contactGroupId); + Metadata meta = new Metadata(); + BdfDictionary metaDictionary = BdfDictionary.of( + new BdfEntry(MSG_KEY_VERSION, 3), + new BdfEntry(MSG_KEY_LOCAL, false) + ); + Map messageMetadata = new LinkedHashMap<>(); + // This newer version should not be deleted + MessageId updateId = new MessageId(getRandomId()); + messageMetadata.put(updateId, BdfDictionary.of( + new BdfEntry(MSG_KEY_VERSION, 4), + new BdfEntry(MSG_KEY_LOCAL, false) + )); + + context.checking(new Expectations() {{ + oneOf(metadataParser).parse(meta); + will(returnValue(metaDictionary)); + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroupId); + will(returnValue(messageMetadata)); + oneOf(db).deleteMessage(txn, message.getId()); + oneOf(db).deleteMessageMetadata(txn, message.getId()); + }}); + + MailboxPropertyManagerImpl t = createInstance(); + assertEquals(ACCEPT_DO_NOT_SHARE, + t.incomingMessage(txn, message, meta)); + } + + @Test + public void testCreatesAndStoresLocalPropertiesWithNewVersionOnPairing() + throws Exception { + Contact contact = getContact(); + List contacts = singletonList(contact); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + + Transaction txn = new Transaction(null, false); + Map messageMetadata = new LinkedHashMap<>(); + MessageId latestId = new MessageId(getRandomId()); + messageMetadata.put(latestId, BdfDictionary.of( + new BdfEntry(MSG_KEY_VERSION, 1), + new BdfEntry(MSG_KEY_LOCAL, true) + )); + // Some remote props, ignored + messageMetadata.put(new MessageId(getRandomId()), BdfDictionary.of( + new BdfEntry(MSG_KEY_VERSION, 3), + new BdfEntry(MSG_KEY_LOCAL, false) + )); + + context.checking(new Expectations() {{ + oneOf(contactManager).getContacts(); + will(returnValue(contacts)); + oneOf(crypto).generateUniqueId(); + will(returnValue(props.getAuthToken())); + oneOf(crypto).generateUniqueId(); + will(returnValue(props.getInboxId())); + oneOf(crypto).generateUniqueId(); + will(returnValue(props.getOutboxId())); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + expectStoreMessage(txn, contactGroup.getId(), propsDict, 2, true); + oneOf(db).removeMessage(txn, latestId); + }}); + + MailboxPropertyManagerImpl t = createInstance(); + t.mailboxPaired(txn, ownProps.getOnion()); + } + + @Test + public void testStoresEmptyLocalPropertiesWithNewVersionOnUnpairing() + throws Exception { + Contact contact = getContact(); + List contacts = singletonList(contact); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + + Transaction txn = new Transaction(null, false); + Map messageMetadata = new LinkedHashMap<>(); + MessageId latestId = new MessageId(getRandomId()); + messageMetadata.put(latestId, BdfDictionary.of( + new BdfEntry(MSG_KEY_VERSION, 1), + new BdfEntry(MSG_KEY_LOCAL, true) + )); + // Some remote props, ignored + messageMetadata.put(new MessageId(getRandomId()), BdfDictionary.of( + new BdfEntry(MSG_KEY_VERSION, 3), + new BdfEntry(MSG_KEY_LOCAL, false) + )); + + context.checking(new Expectations() {{ + oneOf(contactManager).getContacts(); + will(returnValue(contacts)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + expectStoreMessage(txn, contactGroup.getId(), emptyPropsDict, + 2, true); + oneOf(db).removeMessage(txn, latestId); + }}); + + MailboxPropertyManagerImpl t = createInstance(); + t.mailboxUnpaired(txn); + } + + @Test + public void testGetRemoteProperties() + throws Exception { + Transaction txn = new Transaction(null, false); + Contact contact = getContact(); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + BdfDictionary metaDictionary = BdfDictionary.of( + new BdfEntry(MSG_KEY_VERSION, 1), + new BdfEntry(MSG_KEY_LOCAL, false) + ); + Map messageMetadata = new LinkedHashMap<>(); + MessageId fooUpdateId = new MessageId(getRandomId()); + messageMetadata.put(fooUpdateId, metaDictionary); + BdfList fooUpdate = BdfList.of(1, propsDict); + + context.checking(new Expectations() {{ + oneOf(db).getContact(txn, contact.getId()); + will(returnValue(contact)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + oneOf(clientHelper).getMessageAsList(txn, fooUpdateId); + will(returnValue(fooUpdate)); + oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate( + propsDict); + will(returnValue(props)); + }}); + + MailboxPropertyManagerImpl t = createInstance(); + MailboxPropertiesUpdate remote = + t.getRemoteProperties(txn, contact.getId()); + assertTrue(mailboxPropertiesUpdateEqual(remote, props)); + } + + @Test + public void testGetRemotePropertiesReturnsNullBecauseNoUpdate() + throws Exception { + Transaction txn = new Transaction(null, false); + Contact contact = getContact(); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + Map emptyMessageMetadata = + new LinkedHashMap<>(); + + context.checking(new Expectations() {{ + oneOf(db).getContact(txn, contact.getId()); + will(returnValue(contact)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(emptyMessageMetadata)); + }}); + + MailboxPropertyManagerImpl t = createInstance(); + assertNull(t.getRemoteProperties(txn, contact.getId())); + } + + @Test + public void testGetRemotePropertiesReturnsNullBecauseEmptyUpdate() + throws Exception { + Transaction txn = new Transaction(null, false); + Contact contact = getContact(); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + BdfDictionary metaDictionary = BdfDictionary.of( + new BdfEntry(MSG_KEY_VERSION, 1), + new BdfEntry(MSG_KEY_LOCAL, false) + ); + Map messageMetadata = new LinkedHashMap<>(); + MessageId fooUpdateId = new MessageId(getRandomId()); + messageMetadata.put(fooUpdateId, metaDictionary); + BdfList fooUpdate = BdfList.of(1, emptyPropsDict); + + context.checking(new Expectations() {{ + oneOf(db).getContact(txn, contact.getId()); + will(returnValue(contact)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + oneOf(clientHelper).getMessageAsList(txn, fooUpdateId); + will(returnValue(fooUpdate)); + oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate( + emptyPropsDict); + will(returnValue(null)); + }}); + + MailboxPropertyManagerImpl t = createInstance(); + assertNull(t.getRemoteProperties(txn, contact.getId())); + } + + @Test + public void testGetLocalProperties() + throws Exception { + Transaction txn = new Transaction(null, false); + Contact contact = getContact(); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + BdfDictionary metaDictionary = BdfDictionary.of( + new BdfEntry(MSG_KEY_VERSION, 1), + new BdfEntry(MSG_KEY_LOCAL, true) + ); + Map messageMetadata = new LinkedHashMap<>(); + MessageId fooUpdateId = new MessageId(getRandomId()); + messageMetadata.put(fooUpdateId, metaDictionary); + BdfList fooUpdate = BdfList.of(1, propsDict); + + context.checking(new Expectations() {{ + oneOf(db).getContact(txn, contact.getId()); + will(returnValue(contact)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + oneOf(clientHelper).getMessageAsList(txn, fooUpdateId); + will(returnValue(fooUpdate)); + oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate( + propsDict); + will(returnValue(props)); + }}); + + MailboxPropertyManagerImpl t = createInstance(); + MailboxPropertiesUpdate local = + t.getLocalProperties(txn, contact.getId()); + assertTrue(mailboxPropertiesUpdateEqual(local, props)); + } + + @Test + public void testGetLocalPropertiesReturnsNullBecauseNoUpdate() + throws Exception { + Transaction txn = new Transaction(null, false); + Contact contact = getContact(); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + Map emptyMessageMetadata = + new LinkedHashMap<>(); + + context.checking(new Expectations() {{ + oneOf(db).getContact(txn, contact.getId()); + will(returnValue(contact)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(emptyMessageMetadata)); + }}); + + MailboxPropertyManagerImpl t = createInstance(); + assertNull(t.getLocalProperties(txn, contact.getId())); + } + + @Test + public void testGetLocalPropertiesReturnsNullBecauseEmptyUpdate() + throws Exception { + Transaction txn = new Transaction(null, false); + Contact contact = getContact(); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + BdfDictionary metaDictionary = BdfDictionary.of( + new BdfEntry(MSG_KEY_VERSION, 1), + new BdfEntry(MSG_KEY_LOCAL, true) + ); + Map messageMetadata = new LinkedHashMap<>(); + MessageId fooUpdateId = new MessageId(getRandomId()); + messageMetadata.put(fooUpdateId, metaDictionary); + BdfList fooUpdate = BdfList.of(1, emptyPropsDict); + + context.checking(new Expectations() {{ + oneOf(db).getContact(txn, contact.getId()); + will(returnValue(contact)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + oneOf(clientHelper).getMessageAsList(txn, fooUpdateId); + will(returnValue(fooUpdate)); + oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate( + emptyPropsDict); + will(returnValue(null)); + }}); + + MailboxPropertyManagerImpl t = createInstance(); + assertNull(t.getLocalProperties(txn, contact.getId())); + } + + private void expectStoreMessage(Transaction txn, GroupId g, + BdfDictionary properties, long version, boolean local) + throws Exception { + BdfList body = BdfList.of(version, properties); + Message message = getMessage(g); + long timestamp = message.getTimestamp(); + BdfDictionary meta = BdfDictionary.of( + new BdfEntry(MSG_KEY_VERSION, version), + new BdfEntry(MSG_KEY_LOCAL, local) + ); + + context.checking(new Expectations() {{ + oneOf(clock).currentTimeMillis(); + will(returnValue(timestamp)); + oneOf(clientHelper).createMessage(g, timestamp, body); + will(returnValue(message)); + oneOf(clientHelper).addLocalMessage(txn, message, meta, true, + false); + }}); + } + +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPropertyValidatorTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPropertyValidatorTest.java new file mode 100644 index 000000000..0ee1747da --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPropertyValidatorTest.java @@ -0,0 +1,99 @@ +package org.briarproject.bramble.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfEntry; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.data.MetadataEncoder; +import org.briarproject.bramble.api.mailbox.MailboxAuthToken; +import org.briarproject.bramble.api.mailbox.MailboxFolderId; +import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate; +import org.briarproject.bramble.api.mailbox.MailboxPropertyManager; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.jmock.Expectations; +import org.junit.Test; + +import java.io.IOException; + +import static org.briarproject.bramble.test.TestUtils.getGroup; +import static org.briarproject.bramble.test.TestUtils.getMessage; +import static org.briarproject.bramble.test.TestUtils.getRandomId; +import static org.junit.Assert.assertEquals; + +public class MailboxPropertyValidatorTest extends BrambleMockTestCase { + + private final ClientHelper clientHelper = context.mock(ClientHelper.class); + + private final BdfDictionary bdfDict; + private final MailboxPropertiesUpdate mailboxProps; + private final Group group; + private final Message message; + private final MailboxPropertyValidator mpv; + + public MailboxPropertyValidatorTest() { + // Just dummies, clientHelper is mocked so our test is a bit shallow; + // not testing + // {@link ClientHelper#parseAndValidateMailboxPropertiesUpdate(BdfDictionary)} + bdfDict = BdfDictionary.of(new BdfEntry("foo", "bar")); + mailboxProps = new MailboxPropertiesUpdate("baz", + new MailboxAuthToken(getRandomId()), + new MailboxFolderId(getRandomId()), + new MailboxFolderId(getRandomId())); + + group = getGroup(MailboxPropertyManager.CLIENT_ID, + MailboxPropertyManager.MAJOR_VERSION); + message = getMessage(group.getId()); + + MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class); + Clock clock = context.mock(Clock.class); + mpv = new MailboxPropertyValidator(clientHelper, metadataEncoder, + clock); + } + + @Test + public void testValidateMessageBody() throws IOException { + BdfList body = BdfList.of(4, bdfDict); + + context.checking(new Expectations() {{ + oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate( + bdfDict); + will(returnValue(mailboxProps)); + }}); + + BdfDictionary result = + mpv.validateMessage(message, group, body).getDictionary(); + assertEquals(4, result.getLong("version").longValue()); + } + + @Test(expected = FormatException.class) + public void testValidateWrongVersionValue() throws IOException { + BdfList body = BdfList.of(-1, bdfDict); + mpv.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testValidateWrongVersionType() throws IOException { + BdfList body = BdfList.of(bdfDict, bdfDict); + mpv.validateMessage(message, group, body); + } + + @Test + public void testEmptyPropertiesReturnsNull() throws IOException { + BdfDictionary emptyBdfDict = new BdfDictionary(); + BdfList body = BdfList.of(42, emptyBdfDict); + + context.checking(new Expectations() {{ + oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate( + emptyBdfDict); + will(returnValue(null)); + }}); + + BdfDictionary result = + mpv.validateMessage(message, group, body).getDictionary(); + assertEquals(42, result.getLong("version").longValue()); + } +} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/client/SessionId.java b/briar-api/src/main/java/org/briarproject/briar/api/client/SessionId.java index d569bdfec..07494d0f3 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/client/SessionId.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/client/SessionId.java @@ -16,9 +16,4 @@ public class SessionId extends UniqueId { public SessionId(byte[] id) { super(id); } - - @Override - public boolean equals(Object o) { - return o instanceof SessionId && super.equals(o); - } } diff --git a/briar-core/src/main/java/org/briarproject/briar/test/TestDataCreatorImpl.java b/briar-core/src/main/java/org/briarproject/briar/test/TestDataCreatorImpl.java index 2af9e215a..474237ada 100644 --- a/briar-core/src/main/java/org/briarproject/briar/test/TestDataCreatorImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/test/TestDataCreatorImpl.java @@ -299,7 +299,7 @@ public class TestDataCreatorImpl implements TestDataCreator { private String getRandomTorAddress() { byte[] pubkeyBytes = crypto.generateSignatureKeyPair().getPublic().getEncoded(); - return crypto.encodeOnionAddress(pubkeyBytes); + return crypto.encodeOnion(pubkeyBytes); } private void addAvatar(Contact c) throws DbException {