use of org.whispersystems.signalservice.api.storage.SignalStorageRecord in project Signal-Android by WhisperSystems.
the class StorageForcePushJob method onRun.
@Override
protected void onRun() throws IOException, RetryLaterException {
if (SignalStore.account().isLinkedDevice()) {
Log.i(TAG, "Only the primary device can force push");
return;
}
StorageKey storageServiceKey = SignalStore.storageService().getOrCreateStorageKey();
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
RecipientDatabase recipientDatabase = SignalDatabase.recipients();
UnknownStorageIdDatabase storageIdDatabase = SignalDatabase.unknownStorageIds();
long currentVersion = accountManager.getStorageManifestVersion();
Map<RecipientId, StorageId> oldContactStorageIds = recipientDatabase.getContactStorageSyncIdsMap();
long newVersion = currentVersion + 1;
Map<RecipientId, StorageId> newContactStorageIds = generateContactStorageIds(oldContactStorageIds);
List<SignalStorageRecord> inserts = Stream.of(oldContactStorageIds.keySet()).map(recipientDatabase::getRecordForSync).withoutNulls().map(s -> StorageSyncModels.localToRemoteRecord(s, Objects.requireNonNull(newContactStorageIds.get(s.getId())).getRaw())).toList();
SignalStorageRecord accountRecord = StorageSyncHelper.buildAccountRecord(context, Recipient.self().fresh());
List<StorageId> allNewStorageIds = new ArrayList<>(newContactStorageIds.values());
inserts.add(accountRecord);
allNewStorageIds.add(accountRecord.getId());
SignalStorageManifest manifest = new SignalStorageManifest(newVersion, allNewStorageIds);
StorageSyncValidations.validateForcePush(manifest, inserts, Recipient.self().fresh());
try {
if (newVersion > 1) {
Log.i(TAG, String.format(Locale.ENGLISH, "Force-pushing data. Inserting %d IDs.", inserts.size()));
if (accountManager.resetStorageRecords(storageServiceKey, manifest, inserts).isPresent()) {
Log.w(TAG, "Hit a conflict. Trying again.");
throw new RetryLaterException();
}
} else {
Log.i(TAG, String.format(Locale.ENGLISH, "First version, normal push. Inserting %d IDs.", inserts.size()));
if (accountManager.writeStorageRecords(storageServiceKey, manifest, inserts, Collections.emptyList()).isPresent()) {
Log.w(TAG, "Hit a conflict. Trying again.");
throw new RetryLaterException();
}
}
} catch (InvalidKeyException e) {
Log.w(TAG, "Hit an invalid key exception, which likely indicates a conflict.");
throw new RetryLaterException(e);
}
Log.i(TAG, "Force push succeeded. Updating local manifest version to: " + newVersion);
SignalStore.storageService().setManifest(manifest);
recipientDatabase.applyStorageIdUpdates(newContactStorageIds);
recipientDatabase.applyStorageIdUpdates(Collections.singletonMap(Recipient.self().getId(), accountRecord.getId()));
storageIdDatabase.deleteAll();
}
use of org.whispersystems.signalservice.api.storage.SignalStorageRecord in project Signal-Android by WhisperSystems.
the class StorageSyncValidations method validateManifestAndInserts.
private static void validateManifestAndInserts(@NonNull SignalStorageManifest manifest, @NonNull List<SignalStorageRecord> inserts, @NonNull Recipient self) {
Set<StorageId> allSet = new HashSet<>(manifest.getStorageIds());
Set<StorageId> insertSet = new HashSet<>(Stream.of(inserts).map(SignalStorageRecord::getId).toList());
Set<ByteBuffer> rawIdSet = Stream.of(allSet).map(id -> ByteBuffer.wrap(id.getRaw())).collect(Collectors.toSet());
if (allSet.size() != manifest.getStorageIds().size()) {
throw new DuplicateStorageIdError();
}
if (rawIdSet.size() != allSet.size()) {
throw new DuplicateRawIdError();
}
if (inserts.size() > insertSet.size()) {
throw new DuplicateInsertInWriteError();
}
int accountCount = 0;
for (StorageId id : manifest.getStorageIds()) {
accountCount += id.getType() == ManifestRecord.Identifier.Type.ACCOUNT_VALUE ? 1 : 0;
}
if (accountCount > 1) {
throw new MultipleAccountError();
}
if (accountCount == 0) {
throw new MissingAccountError();
}
for (SignalStorageRecord insert : inserts) {
if (!allSet.contains(insert.getId())) {
throw new InsertNotPresentInFullIdSetError();
}
if (insert.isUnknown()) {
throw new UnknownInsertError();
}
if (insert.getContact().isPresent()) {
SignalServiceAddress address = insert.getContact().get().getAddress();
if (self.requireE164().equals(address.getNumber().or("")) || self.requireServiceId().equals(address.getServiceId())) {
throw new SelfAddedAsContactError();
}
}
}
}
use of org.whispersystems.signalservice.api.storage.SignalStorageRecord in project Signal-Android by WhisperSystems.
the class UnknownStorageIdDatabase method insert.
public void insert(@NonNull Collection<SignalStorageRecord> inserts) {
SQLiteDatabase db = databaseHelper.getSignalWritableDatabase();
Preconditions.checkArgument(db.inTransaction(), "Must be in a transaction!");
for (SignalStorageRecord insert : inserts) {
ContentValues values = new ContentValues();
values.put(TYPE, insert.getType());
values.put(STORAGE_ID, Base64.encodeBytes(insert.getId().getRaw()));
db.insert(TABLE_NAME, null, values);
}
}
use of org.whispersystems.signalservice.api.storage.SignalStorageRecord in project Signal-Android by WhisperSystems.
the class SignalServiceAccountManager method writeStorageRecords.
/**
* @return If there was a conflict, the latest {@link SignalStorageManifest}. Otherwise absent.
*/
private Optional<SignalStorageManifest> writeStorageRecords(StorageKey storageKey, SignalStorageManifest manifest, List<SignalStorageRecord> inserts, List<byte[]> deletes, boolean clearAll) throws IOException, InvalidKeyException {
ManifestRecord.Builder manifestRecordBuilder = ManifestRecord.newBuilder().setVersion(manifest.getVersion());
for (StorageId id : manifest.getStorageIds()) {
ManifestRecord.Identifier idProto = ManifestRecord.Identifier.newBuilder().setRaw(ByteString.copyFrom(id.getRaw())).setType(ManifestRecord.Identifier.Type.forNumber(id.getType())).build();
manifestRecordBuilder.addIdentifiers(idProto);
}
String authToken = this.pushServiceSocket.getStorageAuth();
StorageManifestKey manifestKey = storageKey.deriveManifestKey(manifest.getVersion());
byte[] encryptedRecord = SignalStorageCipher.encrypt(manifestKey, manifestRecordBuilder.build().toByteArray());
StorageManifest storageManifest = StorageManifest.newBuilder().setVersion(manifest.getVersion()).setValue(ByteString.copyFrom(encryptedRecord)).build();
WriteOperation.Builder writeBuilder = WriteOperation.newBuilder().setManifest(storageManifest);
for (SignalStorageRecord insert : inserts) {
writeBuilder.addInsertItem(SignalStorageModels.localToRemoteStorageRecord(insert, storageKey));
}
if (clearAll) {
writeBuilder.setClearAll(true);
} else {
for (byte[] delete : deletes) {
writeBuilder.addDeleteKey(ByteString.copyFrom(delete));
}
}
Optional<StorageManifest> conflict = this.pushServiceSocket.writeStorageContacts(authToken, writeBuilder.build());
if (conflict.isPresent()) {
StorageManifestKey conflictKey = storageKey.deriveManifestKey(conflict.get().getVersion());
byte[] rawManifestRecord = SignalStorageCipher.decrypt(conflictKey, conflict.get().getValue().toByteArray());
ManifestRecord record = ManifestRecord.parseFrom(rawManifestRecord);
List<StorageId> ids = new ArrayList<>(record.getIdentifiersCount());
for (ManifestRecord.Identifier id : record.getIdentifiersList()) {
ids.add(StorageId.forType(id.getRaw().toByteArray(), id.getType().getNumber()));
}
SignalStorageManifest conflictManifest = new SignalStorageManifest(record.getVersion(), ids);
return Optional.of(conflictManifest);
} else {
return Optional.absent();
}
}
use of org.whispersystems.signalservice.api.storage.SignalStorageRecord in project Signal-Android by signalapp.
the class StorageSyncJob method buildLocalStorageRecords.
@NonNull
private static List<SignalStorageRecord> buildLocalStorageRecords(@NonNull Context context, @NonNull Recipient self, @NonNull Collection<StorageId> ids) {
if (ids.isEmpty()) {
return Collections.emptyList();
}
RecipientDatabase recipientDatabase = SignalDatabase.recipients();
UnknownStorageIdDatabase storageIdDatabase = SignalDatabase.unknownStorageIds();
List<SignalStorageRecord> records = new ArrayList<>(ids.size());
for (StorageId id : ids) {
switch(id.getType()) {
case ManifestRecord.Identifier.Type.CONTACT_VALUE:
case ManifestRecord.Identifier.Type.GROUPV1_VALUE:
case ManifestRecord.Identifier.Type.GROUPV2_VALUE:
RecipientRecord settings = recipientDatabase.getByStorageId(id.getRaw());
if (settings != null) {
if (settings.getGroupType() == RecipientDatabase.GroupType.SIGNAL_V2 && settings.getSyncExtras().getGroupMasterKey() == null) {
throw new MissingGv2MasterKeyError();
} else {
records.add(StorageSyncModels.localToRemoteRecord(settings));
}
} else {
throw new MissingRecipientModelError("Missing local recipient model! Type: " + id.getType());
}
break;
case ManifestRecord.Identifier.Type.ACCOUNT_VALUE:
if (!Arrays.equals(self.getStorageServiceId(), id.getRaw())) {
throw new AssertionError("Local storage ID doesn't match self!");
}
records.add(StorageSyncHelper.buildAccountRecord(context, self));
break;
default:
SignalStorageRecord unknown = storageIdDatabase.getById(id.getRaw());
if (unknown != null) {
records.add(unknown);
} else {
throw new MissingUnknownModelError("Missing local unknown model! Type: " + id.getType());
}
break;
}
}
return records;
}
Aggregations