Merge branch '2181-implement-sync-client-for-mailbox-props' into 'master'

Implement sync client for mailbox properties

Closes #2181

See merge request briar/briar!1591
This commit is contained in:
akwizgran
2022-03-30 12:29:15 +00:00
28 changed files with 1546 additions and 46 deletions

View File

@@ -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();
}

View File

@@ -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<TransportId, TransportProperties> 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.

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -23,6 +23,11 @@ public class MailboxProperties {
return baseUrl;
}
public String getOnion() {
return baseUrl.replaceFirst("^http://", "")
.replaceFirst("\\.onion$", "");
}
public MailboxAuthToken getAuthToken() {
return authToken;
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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<MessageId, BdfDictionary> metadata =
clientHelper.getMessageMetadataAsDictionary(txn, g);
// We should have at most 1 local and 1 remote
if (metadata.size() > 2) {
throw new IllegalStateException();
}
for (Entry<MessageId, BdfDictionary> 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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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<MailboxHook> 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

View File

@@ -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 {

View File

@@ -4,7 +4,7 @@ interface TorRendezvousCrypto {
static final int SEED_BYTES = 32;
String getOnionAddress(byte[] seed);
String getOnion(byte[] seed);
String getPrivateKeyBlob(byte[] seed);
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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<? extends MailboxPairingState> 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));

View File

@@ -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<MessageId, BdfDictionary> 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<MessageId, BdfDictionary> 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<MessageId, BdfDictionary> 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<MessageId, BdfDictionary> 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<MessageId, BdfDictionary> 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<Contact> contacts = singletonList(contact);
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
Transaction txn = new Transaction(null, false);
Map<MessageId, BdfDictionary> 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<Contact> contacts = singletonList(contact);
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
Transaction txn = new Transaction(null, false);
Map<MessageId, BdfDictionary> 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<MessageId, BdfDictionary> 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<MessageId, BdfDictionary> 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<MessageId, BdfDictionary> 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<MessageId, BdfDictionary> 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<MessageId, BdfDictionary> 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<MessageId, BdfDictionary> 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);
}});
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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 {