mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-18 21:59:54 +01:00
WIP: Update our backup when contacts are added or removed.
This commit is contained in:
@@ -1,20 +1,14 @@
|
|||||||
package org.briarproject.briar.socialbackup;
|
package org.briarproject.briar.socialbackup;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.identity.Identity;
|
import org.briarproject.bramble.api.identity.Identity;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
interface BackupPayloadEncoder {
|
interface BackupPayloadEncoder {
|
||||||
|
|
||||||
BackupPayload encodeBackupPayload(SecretKey secret, Identity identity,
|
BackupPayload encodeBackupPayload(SecretKey secret, Identity identity,
|
||||||
List<Contact> contacts,
|
List<ContactData> contactData, int version);
|
||||||
List<Map<TransportId, TransportProperties>> properties,
|
|
||||||
int version);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,19 +10,16 @@ import org.briarproject.bramble.api.data.BdfList;
|
|||||||
import org.briarproject.bramble.api.identity.Identity;
|
import org.briarproject.bramble.api.identity.Identity;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.briar.api.socialbackup.Shard;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
|
||||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.AUTH_TAG_BYTES;
|
import static org.briarproject.briar.socialbackup.SocialBackupConstants.AUTH_TAG_BYTES;
|
||||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.NONCE_BYTES;
|
import static org.briarproject.briar.socialbackup.SocialBackupConstants.NONCE_BYTES;
|
||||||
|
|
||||||
@@ -33,49 +30,49 @@ class BackupPayloadEncoderImpl implements BackupPayloadEncoder {
|
|||||||
private final ClientHelper clientHelper;
|
private final ClientHelper clientHelper;
|
||||||
private final Provider<AuthenticatedCipher> cipherProvider;
|
private final Provider<AuthenticatedCipher> cipherProvider;
|
||||||
private final SecureRandom secureRandom;
|
private final SecureRandom secureRandom;
|
||||||
|
private final MessageEncoder messageEncoder;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
BackupPayloadEncoderImpl(ClientHelper clientHelper,
|
BackupPayloadEncoderImpl(ClientHelper clientHelper,
|
||||||
Provider<AuthenticatedCipher> cipherProvider,
|
Provider<AuthenticatedCipher> cipherProvider,
|
||||||
SecureRandom secureRandom) {
|
SecureRandom secureRandom,
|
||||||
|
MessageEncoder messageEncoder) {
|
||||||
this.clientHelper = clientHelper;
|
this.clientHelper = clientHelper;
|
||||||
this.cipherProvider = cipherProvider;
|
this.cipherProvider = cipherProvider;
|
||||||
this.secureRandom = secureRandom;
|
this.secureRandom = secureRandom;
|
||||||
|
this.messageEncoder = messageEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BackupPayload encodeBackupPayload(SecretKey secret,
|
public BackupPayload encodeBackupPayload(SecretKey secret,
|
||||||
Identity identity, List<Contact> contacts,
|
Identity identity, List<ContactData> contactData, int version) {
|
||||||
List<Map<TransportId, TransportProperties>> properties,
|
|
||||||
int version) {
|
|
||||||
if (contacts.size() != properties.size()) {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
// Encode the local identity
|
// Encode the local identity
|
||||||
BdfList identityData = new BdfList();
|
BdfList bdfIdentity = new BdfList();
|
||||||
LocalAuthor localAuthor = identity.getLocalAuthor();
|
LocalAuthor localAuthor = identity.getLocalAuthor();
|
||||||
identityData.add(clientHelper.toList(localAuthor));
|
bdfIdentity.add(clientHelper.toList(localAuthor));
|
||||||
identityData.add(localAuthor.getPrivateKey().getEncoded());
|
bdfIdentity.add(localAuthor.getPrivateKey().getEncoded());
|
||||||
identityData.add(identity.getHandshakePublicKey().getEncoded());
|
bdfIdentity.add(identity.getHandshakePublicKey().getEncoded());
|
||||||
identityData.add(identity.getHandshakePrivateKey().getEncoded());
|
bdfIdentity.add(identity.getHandshakePrivateKey().getEncoded());
|
||||||
// Encode the contacts
|
// Encode the contact data
|
||||||
BdfList contactData = new BdfList();
|
BdfList bdfContactData = new BdfList();
|
||||||
for (int i = 0; i < contacts.size(); i++) {
|
for (ContactData cd : contactData) {
|
||||||
Contact contact = contacts.get(i);
|
BdfList bdfData = new BdfList();
|
||||||
Map<TransportId, TransportProperties> props = properties.get(i);
|
Contact contact = cd.getContact();
|
||||||
BdfList data = new BdfList();
|
bdfData.add(clientHelper.toList(contact.getAuthor()));
|
||||||
data.add(clientHelper.toList(contact.getAuthor()));
|
bdfData.add(contact.getAlias());
|
||||||
data.add(contact.getAlias());
|
PublicKey pub = contact.getHandshakePublicKey();
|
||||||
PublicKey pub = requireNonNull(contact.getHandshakePublicKey());
|
bdfData.add(pub == null ? null : pub.getEncoded());
|
||||||
data.add(pub.getEncoded());
|
bdfData.add(clientHelper.toDictionary(cd.getProperties()));
|
||||||
data.add(clientHelper.toDictionary(props));
|
Shard shard = cd.getShard();
|
||||||
contactData.add(data);
|
if (shard == null) bdfData.add(null);
|
||||||
|
else bdfData.add(messageEncoder.encodeShardMessage(shard));
|
||||||
|
bdfContactData.add(bdfData);
|
||||||
}
|
}
|
||||||
// Encode and encrypt the payload
|
// Encode and encrypt the payload
|
||||||
BdfList backup = new BdfList();
|
BdfList backup = new BdfList();
|
||||||
backup.add(version);
|
backup.add(version);
|
||||||
backup.add(identityData);
|
backup.add(bdfIdentity);
|
||||||
backup.add(contactData);
|
backup.add(bdfContactData);
|
||||||
try {
|
try {
|
||||||
byte[] plaintext = clientHelper.toByteArray(backup);
|
byte[] plaintext = clientHelper.toByteArray(backup);
|
||||||
byte[] ciphertext = new byte[plaintext.length + AUTH_TAG_BYTES];
|
byte[] ciphertext = new byte[plaintext.length + AUTH_TAG_BYTES];
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.briarproject.briar.socialbackup;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
|
import org.briarproject.briar.api.socialbackup.Shard;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class ContactData {
|
||||||
|
|
||||||
|
private final Contact contact;
|
||||||
|
private final Map<TransportId, TransportProperties> properties;
|
||||||
|
@Nullable
|
||||||
|
private final Shard shard;
|
||||||
|
|
||||||
|
ContactData(Contact contact,
|
||||||
|
Map<TransportId, TransportProperties> properties,
|
||||||
|
@Nullable Shard shard) {
|
||||||
|
this.contact = contact;
|
||||||
|
this.properties = properties;
|
||||||
|
this.shard = shard;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Contact getContact() {
|
||||||
|
return contact;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<TransportId, TransportProperties> getProperties() {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Shard getShard() {
|
||||||
|
return shard;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
package org.briarproject.briar.socialbackup;
|
package org.briarproject.briar.socialbackup;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.api.socialbackup.Shard;
|
import org.briarproject.briar.api.socialbackup.Shard;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
interface MessageParser {
|
interface MessageParser {
|
||||||
|
|
||||||
Shard parseShardMessage(byte[] body) throws FormatException;
|
Shard parseShardMessage(BdfList body) throws FormatException;
|
||||||
|
|
||||||
BackupPayload parseBackupMessage(byte[] body) throws FormatException;
|
BackupPayload parseBackupMessage(BdfList body) throws FormatException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.socialbackup;
|
package org.briarproject.briar.socialbackup;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.api.socialbackup.Shard;
|
import org.briarproject.briar.api.socialbackup.Shard;
|
||||||
@@ -13,29 +12,24 @@ import javax.inject.Inject;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class MessageParserImpl implements MessageParser {
|
class MessageParserImpl implements MessageParser {
|
||||||
|
|
||||||
private final ClientHelper clientHelper;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MessageParserImpl(ClientHelper clientHelper) {
|
MessageParserImpl() {
|
||||||
this.clientHelper = clientHelper;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Shard parseShardMessage(byte[] body) throws FormatException {
|
public Shard parseShardMessage(BdfList body) throws FormatException {
|
||||||
BdfList list = clientHelper.toList(body);
|
|
||||||
// Message type, secret ID, num shards, threshold, shard
|
// Message type, secret ID, num shards, threshold, shard
|
||||||
byte[] secretId = list.getRaw(1);
|
byte[] secretId = body.getRaw(1);
|
||||||
int numShards = list.getLong(2).intValue();
|
int numShards = body.getLong(2).intValue();
|
||||||
int threshold = list.getLong(3).intValue();
|
int threshold = body.getLong(3).intValue();
|
||||||
byte[] shard = list.getRaw(4);
|
byte[] shard = body.getRaw(4);
|
||||||
return new Shard(secretId, numShards, threshold, shard);
|
return new Shard(secretId, numShards, threshold, shard);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BackupPayload parseBackupMessage(byte[] body)
|
public BackupPayload parseBackupMessage(BdfList body)
|
||||||
throws FormatException {
|
throws FormatException {
|
||||||
BdfList list = clientHelper.toList(body);
|
|
||||||
// Message type, backup payload
|
// Message type, backup payload
|
||||||
return new BackupPayload(list.getRaw(1));
|
return new BackupPayload(body.getRaw(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.socialbackup;
|
|||||||
interface SocialBackupConstants {
|
interface SocialBackupConstants {
|
||||||
|
|
||||||
// Group metadata keys
|
// Group metadata keys
|
||||||
|
String GROUP_KEY_CONTACT_ID = "contactId";
|
||||||
String GROUP_KEY_SECRET = "secret";
|
String GROUP_KEY_SECRET = "secret";
|
||||||
String GROUP_KEY_CUSTODIANS = "custodians";
|
String GROUP_KEY_CUSTODIANS = "custodians";
|
||||||
String GROUP_KEY_THRESHOLD = "threshold";
|
String GROUP_KEY_THRESHOLD = "threshold";
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.briar.socialbackup;
|
package org.briarproject.briar.socialbackup;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.Pair;
|
||||||
import org.briarproject.bramble.api.client.BdfIncomingMessageHook;
|
import org.briarproject.bramble.api.client.BdfIncomingMessageHook;
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||||
@@ -16,10 +17,12 @@ import org.briarproject.bramble.api.data.BdfList;
|
|||||||
import org.briarproject.bramble.api.data.MetadataParser;
|
import org.briarproject.bramble.api.data.MetadataParser;
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.Identity;
|
import org.briarproject.bramble.api.identity.Identity;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TorConstants;
|
import org.briarproject.bramble.api.plugin.TorConstants;
|
||||||
@@ -42,6 +45,7 @@ import org.briarproject.briar.api.socialbackup.SocialBackupManager;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
@@ -49,8 +53,11 @@ import javax.annotation.Nullable;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
import static org.briarproject.briar.socialbackup.MessageType.BACKUP;
|
import static org.briarproject.briar.socialbackup.MessageType.BACKUP;
|
||||||
import static org.briarproject.briar.socialbackup.MessageType.SHARD;
|
import static org.briarproject.briar.socialbackup.MessageType.SHARD;
|
||||||
|
import static org.briarproject.briar.socialbackup.SocialBackupConstants.GROUP_KEY_CONTACT_ID;
|
||||||
|
import static org.briarproject.briar.socialbackup.SocialBackupConstants.GROUP_KEY_VERSION;
|
||||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.MSG_KEY_LOCAL;
|
import static org.briarproject.briar.socialbackup.SocialBackupConstants.MSG_KEY_LOCAL;
|
||||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.MSG_KEY_MESSAGE_TYPE;
|
import static org.briarproject.briar.socialbackup.SocialBackupConstants.MSG_KEY_MESSAGE_TYPE;
|
||||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.MSG_KEY_VERSION;
|
import static org.briarproject.briar.socialbackup.SocialBackupConstants.MSG_KEY_VERSION;
|
||||||
@@ -66,6 +73,7 @@ class SocialBackupManagerImpl extends BdfIncomingMessageHook
|
|||||||
private final BackupMetadataParser backupMetadataParser;
|
private final BackupMetadataParser backupMetadataParser;
|
||||||
private final BackupMetadataEncoder backupMetadataEncoder;
|
private final BackupMetadataEncoder backupMetadataEncoder;
|
||||||
private final BackupPayloadEncoder backupPayloadEncoder;
|
private final BackupPayloadEncoder backupPayloadEncoder;
|
||||||
|
private final MessageParser messageParser;
|
||||||
private final MessageEncoder messageEncoder;
|
private final MessageEncoder messageEncoder;
|
||||||
private final IdentityManager identityManager;
|
private final IdentityManager identityManager;
|
||||||
private final ContactManager contactManager;
|
private final ContactManager contactManager;
|
||||||
@@ -85,6 +93,7 @@ class SocialBackupManagerImpl extends BdfIncomingMessageHook
|
|||||||
BackupMetadataParser backupMetadataParser,
|
BackupMetadataParser backupMetadataParser,
|
||||||
BackupMetadataEncoder backupMetadataEncoder,
|
BackupMetadataEncoder backupMetadataEncoder,
|
||||||
BackupPayloadEncoder backupPayloadEncoder,
|
BackupPayloadEncoder backupPayloadEncoder,
|
||||||
|
MessageParser messageParser,
|
||||||
MessageEncoder messageEncoder,
|
MessageEncoder messageEncoder,
|
||||||
IdentityManager identityManager,
|
IdentityManager identityManager,
|
||||||
ContactManager contactManager,
|
ContactManager contactManager,
|
||||||
@@ -98,6 +107,7 @@ class SocialBackupManagerImpl extends BdfIncomingMessageHook
|
|||||||
this.backupMetadataParser = backupMetadataParser;
|
this.backupMetadataParser = backupMetadataParser;
|
||||||
this.backupMetadataEncoder = backupMetadataEncoder;
|
this.backupMetadataEncoder = backupMetadataEncoder;
|
||||||
this.backupPayloadEncoder = backupPayloadEncoder;
|
this.backupPayloadEncoder = backupPayloadEncoder;
|
||||||
|
this.messageParser = messageParser;
|
||||||
this.messageEncoder = messageEncoder;
|
this.messageEncoder = messageEncoder;
|
||||||
this.identityManager = identityManager;
|
this.identityManager = identityManager;
|
||||||
this.contactManager = contactManager;
|
this.contactManager = contactManager;
|
||||||
@@ -125,13 +135,21 @@ class SocialBackupManagerImpl extends BdfIncomingMessageHook
|
|||||||
Visibility client = clientVersioningManager.getClientVisibility(txn,
|
Visibility client = clientVersioningManager.getClientVisibility(txn,
|
||||||
c.getId(), CLIENT_ID, MAJOR_VERSION);
|
c.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||||
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
||||||
// TODO: Add the contact to our backup, if any
|
// Attach the contact ID to the group
|
||||||
|
setContactId(txn, g.getId(), c.getId());
|
||||||
|
// Add the contact to our backup, if any
|
||||||
|
if (localBackupExists(txn)) {
|
||||||
|
updateBackup(txn, loadContactData(txn));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removingContact(Transaction txn, Contact c) throws DbException {
|
public void removingContact(Transaction txn, Contact c) throws DbException {
|
||||||
db.removeGroup(txn, getContactGroup(c));
|
db.removeGroup(txn, getContactGroup(c));
|
||||||
// TODO: Remove the contact from our backup, if any
|
// Remove the contact from our backup, if any
|
||||||
|
if (localBackupExists(txn)) {
|
||||||
|
updateBackup(txn, loadContactData(txn));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -147,20 +165,34 @@ class SocialBackupManagerImpl extends BdfIncomingMessageHook
|
|||||||
BdfDictionary meta) throws DbException, FormatException {
|
BdfDictionary meta) throws DbException, FormatException {
|
||||||
MessageType type = MessageType.fromValue(body.getLong(0).intValue());
|
MessageType type = MessageType.fromValue(body.getLong(0).intValue());
|
||||||
if (type == SHARD) {
|
if (type == SHARD) {
|
||||||
// TODO: Add the shard to our backup, if any
|
// Only one shard should be received from each contact
|
||||||
|
if (findMessage(txn, m.getGroupId(), SHARD, false) != null) {
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
// Add the shard to our backup, if any
|
||||||
|
if (localBackupExists(txn)) {
|
||||||
|
Shard shard = messageParser.parseShardMessage(body);
|
||||||
|
ContactId c = getContactId(txn, m.getGroupId());
|
||||||
|
List<ContactData> contactData = loadContactData(txn);
|
||||||
|
ListIterator<ContactData> it = contactData.listIterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
ContactData cd = it.next();
|
||||||
|
if (cd.getContact().getId().equals(c)) {
|
||||||
|
it.set(new ContactData(cd.getContact(),
|
||||||
|
cd.getProperties(), shard));
|
||||||
|
updateBackup(txn, contactData);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (type == BACKUP) {
|
} else if (type == BACKUP) {
|
||||||
// Keep the newest version of the backup, delete any older versions
|
// Keep the newest version of the backup, delete any older versions
|
||||||
int version = meta.getLong(MSG_KEY_VERSION).intValue();
|
int version = meta.getLong(MSG_KEY_VERSION).intValue();
|
||||||
BdfDictionary query = BdfDictionary.of(
|
Pair<MessageId, BdfDictionary> prev =
|
||||||
new BdfEntry(MSG_KEY_MESSAGE_TYPE, BACKUP.getValue()),
|
findMessage(txn, m.getGroupId(), BACKUP, false);
|
||||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
if (prev != null) {
|
||||||
Map<MessageId, BdfDictionary> results =
|
MessageId prevId = prev.getFirst();
|
||||||
clientHelper.getMessageMetadataAsDictionary(txn,
|
BdfDictionary prevMeta = prev.getSecond();
|
||||||
m.getGroupId(), query);
|
|
||||||
if (results.size() > 1) throw new DbException();
|
|
||||||
for (Entry<MessageId, BdfDictionary> e : results.entrySet()) {
|
|
||||||
MessageId prevId = e.getKey();
|
|
||||||
BdfDictionary prevMeta = e.getValue();
|
|
||||||
int prevVersion = prevMeta.getLong(MSG_KEY_VERSION).intValue();
|
int prevVersion = prevMeta.getLong(MSG_KEY_VERSION).intValue();
|
||||||
if (version > prevVersion) {
|
if (version > prevVersion) {
|
||||||
// This backup is newer - delete the previous backup
|
// This backup is newer - delete the previous backup
|
||||||
@@ -192,7 +224,7 @@ class SocialBackupManagerImpl extends BdfIncomingMessageHook
|
|||||||
@Override
|
@Override
|
||||||
public void createBackup(Transaction txn, List<ContactId> custodianIds,
|
public void createBackup(Transaction txn, List<ContactId> custodianIds,
|
||||||
int threshold) throws DbException {
|
int threshold) throws DbException {
|
||||||
if (getBackupMetadata(txn) != null) throw new BackupExistsException();
|
if (localBackupExists(txn)) throw new BackupExistsException();
|
||||||
// Load the contacts
|
// Load the contacts
|
||||||
List<Contact> custodians = new ArrayList<>(custodianIds.size());
|
List<Contact> custodians = new ArrayList<>(custodianIds.size());
|
||||||
for (ContactId custodianId : custodianIds) {
|
for (ContactId custodianId : custodianIds) {
|
||||||
@@ -200,7 +232,9 @@ class SocialBackupManagerImpl extends BdfIncomingMessageHook
|
|||||||
}
|
}
|
||||||
// Create the encrypted backup payload
|
// Create the encrypted backup payload
|
||||||
SecretKey secret = crypto.generateSecretKey();
|
SecretKey secret = crypto.generateSecretKey();
|
||||||
BackupPayload payload = createBackupPayload(txn, secret, 0);
|
List<ContactData> contactData = loadContactData(txn);
|
||||||
|
BackupPayload payload =
|
||||||
|
createBackupPayload(txn, secret, contactData, 0);
|
||||||
// Create the shards
|
// Create the shards
|
||||||
List<Shard> shards = darkCrystal.createShards(secret,
|
List<Shard> shards = darkCrystal.createShards(secret,
|
||||||
custodians.size(), threshold);
|
custodians.size(), threshold);
|
||||||
@@ -232,23 +266,50 @@ class SocialBackupManagerImpl extends BdfIncomingMessageHook
|
|||||||
MAJOR_VERSION, c);
|
MAJOR_VERSION, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BackupPayload createBackupPayload(Transaction txn,
|
private void setContactId(Transaction txn, GroupId g, ContactId c)
|
||||||
SecretKey secret, int version) throws DbException {
|
throws DbException {
|
||||||
Identity identity = identityManager.getIdentity(txn);
|
BdfDictionary d = new BdfDictionary();
|
||||||
Collection<Contact> contacts = contactManager.getContacts(txn);
|
d.put(GROUP_KEY_CONTACT_ID, c.getInt());
|
||||||
List<Contact> eligible = new ArrayList<>();
|
try {
|
||||||
List<Map<TransportId, TransportProperties>> properties =
|
clientHelper.mergeGroupMetadata(txn, g, d);
|
||||||
new ArrayList<>();
|
} catch (FormatException e) {
|
||||||
// Include all contacts whose handshake public keys we know
|
throw new AssertionError(e);
|
||||||
for (Contact c : contacts) {
|
|
||||||
if (c.getHandshakePublicKey() != null) {
|
|
||||||
eligible.add(c);
|
|
||||||
properties.add(getTransportProperties(txn, c.getId()));
|
|
||||||
// TODO: Include shard received from contact, if any
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContactId getContactId(Transaction txn, GroupId g)
|
||||||
|
throws DbException {
|
||||||
|
try {
|
||||||
|
BdfDictionary meta =
|
||||||
|
clientHelper.getGroupMetadataAsDictionary(txn, g);
|
||||||
|
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BackupPayload createBackupPayload(Transaction txn,
|
||||||
|
SecretKey secret, List<ContactData> contactData, int version)
|
||||||
|
throws DbException {
|
||||||
|
Identity identity = identityManager.getIdentity(txn);
|
||||||
return backupPayloadEncoder.encodeBackupPayload(secret, identity,
|
return backupPayloadEncoder.encodeBackupPayload(secret, identity,
|
||||||
eligible, properties, version);
|
contactData, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ContactData> loadContactData(Transaction txn)
|
||||||
|
throws DbException {
|
||||||
|
Collection<Contact> contacts = contactManager.getContacts(txn);
|
||||||
|
List<ContactData> contactData = new ArrayList<>();
|
||||||
|
for (Contact c : contacts) {
|
||||||
|
// Skip contacts that are in the process of being removed
|
||||||
|
Group contactGroup = getContactGroup(c);
|
||||||
|
if (!db.containsGroup(txn, contactGroup.getId())) continue;
|
||||||
|
Map<TransportId, TransportProperties> props =
|
||||||
|
getTransportProperties(txn, c.getId());
|
||||||
|
Shard shard = getRemoteShard(txn, contactGroup.getId());
|
||||||
|
contactData.add(new ContactData(c, props, shard));
|
||||||
|
}
|
||||||
|
return contactData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<TransportId, TransportProperties> getTransportProperties(
|
private Map<TransportId, TransportProperties> getTransportProperties(
|
||||||
@@ -284,4 +345,76 @@ class SocialBackupManagerImpl extends BdfIncomingMessageHook
|
|||||||
new BdfEntry(MSG_KEY_VERSION, version));
|
new BdfEntry(MSG_KEY_VERSION, version));
|
||||||
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean localBackupExists(Transaction txn) throws DbException {
|
||||||
|
return !db.getGroupMetadata(txn, localGroup.getId()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Shard getRemoteShard(Transaction txn, GroupId g)
|
||||||
|
throws DbException {
|
||||||
|
try {
|
||||||
|
Pair<MessageId, BdfDictionary> prev =
|
||||||
|
findMessage(txn, g, SHARD, false);
|
||||||
|
if (prev == null) return null;
|
||||||
|
BdfList body = clientHelper.getMessageAsList(txn, prev.getFirst());
|
||||||
|
return messageParser.parseShardMessage(body);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBackup(Transaction txn, List<ContactData> contactData)
|
||||||
|
throws DbException {
|
||||||
|
BackupMetadata backupMetadata = requireNonNull(getBackupMetadata(txn));
|
||||||
|
int newVersion = backupMetadata.getVersion() + 1;
|
||||||
|
BackupPayload payload = createBackupPayload(txn,
|
||||||
|
backupMetadata.getSecret(), contactData, newVersion);
|
||||||
|
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
|
||||||
|
try {
|
||||||
|
for (Author author : backupMetadata.getCustodians()) {
|
||||||
|
try {
|
||||||
|
Contact custodian = contactManager.getContact(txn,
|
||||||
|
author.getId(), localAuthor.getId());
|
||||||
|
Group contactGroup = getContactGroup(custodian);
|
||||||
|
Pair<MessageId, BdfDictionary> prev = findMessage(txn,
|
||||||
|
contactGroup.getId(), BACKUP, true);
|
||||||
|
if (prev != null) {
|
||||||
|
// Delete the previous backup message
|
||||||
|
MessageId prevId = prev.getFirst();
|
||||||
|
db.deleteMessage(txn, prevId);
|
||||||
|
db.deleteMessageMetadata(txn, prevId);
|
||||||
|
}
|
||||||
|
sendBackupMessage(txn, custodian, newVersion, payload);
|
||||||
|
} catch (NoSuchContactException e) {
|
||||||
|
// The custodian is no longer a contact - continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
BdfDictionary meta =
|
||||||
|
BdfDictionary.of(new BdfEntry(GROUP_KEY_VERSION, newVersion));
|
||||||
|
try {
|
||||||
|
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Pair<MessageId, BdfDictionary> findMessage(Transaction txn,
|
||||||
|
GroupId g, MessageType type, boolean local)
|
||||||
|
throws DbException, FormatException {
|
||||||
|
BdfDictionary query = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_MESSAGE_TYPE, type.getValue()),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, local));
|
||||||
|
Map<MessageId, BdfDictionary> results =
|
||||||
|
clientHelper.getMessageMetadataAsDictionary(txn, g, query);
|
||||||
|
if (results.size() > 1) throw new DbException();
|
||||||
|
if (results.isEmpty()) return null;
|
||||||
|
Entry<MessageId, BdfDictionary> e =
|
||||||
|
results.entrySet().iterator().next();
|
||||||
|
return new Pair<>(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user