Simplify SignalStorageRecord.

This commit is contained in:
Greyson Parrelli 2024-11-11 11:38:41 -05:00
parent ae37c4019f
commit dcdfe0b762
27 changed files with 249 additions and 341 deletions

View file

@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.MessageTableTestUtils
import org.whispersystems.signalservice.api.storage.SignalContactRecord
import org.whispersystems.signalservice.api.storage.toSignalContactRecord
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord
@Suppress("ClassName")
@ -34,7 +35,7 @@ class RecipientTableTest_applyStorageSyncContactUpdate {
MmsHelper.insert(recipient = other)
identities.setVerified(other.id, harness.othersKeys[0].publicKey, IdentityTable.VerifiedStatus.VERIFIED)
val oldRecord: SignalContactRecord = StorageSyncModels.localToRemoteRecord(SignalDatabase.recipients.getRecordForSync(harness.others[0])!!).contact.get()
val oldRecord: SignalContactRecord = StorageSyncModels.localToRemoteRecord(SignalDatabase.recipients.getRecordForSync(harness.others[0])!!).let { it.proto.contact!!.toSignalContactRecord(it.id) }
val newProto = oldRecord
.proto

View file

@ -116,7 +116,7 @@ public class UnknownStorageIdTable extends DatabaseTable {
for (SignalStorageRecord insert : inserts) {
ContentValues values = new ContentValues();
values.put(TYPE, insert.getType());
values.put(TYPE, insert.getId().getType());
values.put(STORAGE_ID, Base64.encodeWithPadding(insert.getId().getRaw()));
db.insert(TABLE_NAME, null, values);

View file

@ -96,12 +96,12 @@ public class StorageAccountRestoreJob extends BaseJob {
return;
}
SignalAccountRecord accountRecord = record.getAccount().orElse(null);
if (accountRecord == null) {
if (record.getProto().account == null) {
Log.w(TAG, "The storage record didn't actually have an account on it! Not restoring.");
return;
}
SignalAccountRecord accountRecord = new SignalAccountRecord(record.getId(), record.getProto().account);
Log.i(TAG, "Applying changes locally...");
SignalDatabase.getRawDatabase().beginTransaction();

View file

@ -37,6 +37,12 @@ import org.whispersystems.signalservice.api.storage.SignalStorageManifest
import org.whispersystems.signalservice.api.storage.SignalStorageRecord
import org.whispersystems.signalservice.api.storage.SignalStoryDistributionListRecord
import org.whispersystems.signalservice.api.storage.StorageId
import org.whispersystems.signalservice.api.storage.toSignalAccountRecord
import org.whispersystems.signalservice.api.storage.toSignalCallLinkRecord
import org.whispersystems.signalservice.api.storage.toSignalContactRecord
import org.whispersystems.signalservice.api.storage.toSignalGroupV1Record
import org.whispersystems.signalservice.api.storage.toSignalGroupV2Record
import org.whispersystems.signalservice.api.storage.toSignalStoryDistributionListRecord
import org.whispersystems.signalservice.internal.push.SyncMessage
import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord
import java.io.IOException
@ -475,18 +481,18 @@ class StorageSyncJob private constructor(parameters: Parameters) : BaseJob(param
init {
for (record in records) {
if (record.contact.isPresent) {
contacts += record.contact.get()
} else if (record.groupV1.isPresent) {
gv1 += record.groupV1.get()
} else if (record.groupV2.isPresent) {
gv2 += record.groupV2.get()
} else if (record.account.isPresent) {
account += record.account.get()
} else if (record.storyDistributionList.isPresent) {
storyDistributionLists += record.storyDistributionList.get()
} else if (record.callLink.isPresent) {
callLinkRecords += record.callLink.get()
if (record.proto.contact != null) {
contacts += record.proto.contact!!.toSignalContactRecord(record.id)
} else if (record.proto.groupV1 != null) {
gv1 += record.proto.groupV1!!.toSignalGroupV1Record(record.id)
} else if (record.proto.groupV2 != null) {
gv2 += record.proto.groupV2!!.toSignalGroupV2Record(record.id)
} else if (record.proto.account != null) {
account += record.proto.account!!.toSignalAccountRecord(record.id)
} else if (record.proto.storyDistributionList != null) {
storyDistributionLists += record.proto.storyDistributionList!!.toSignalStoryDistributionListRecord(record.id)
} else if (record.proto.callLink != null) {
callLinkRecords += record.proto.callLink!!.toSignalCallLinkRecord(record.id)
} else if (record.id.isUnknown) {
unknown += record
} else {

View file

@ -8,7 +8,6 @@ import org.signal.core.util.nullIfEmpty
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.storage.StorageSyncHelper.applyAccountStorageSyncUpdates
import org.thoughtcrime.securesms.storage.StorageSyncHelper.buildAccountRecord
import org.whispersystems.signalservice.api.storage.SignalAccountRecord
import org.whispersystems.signalservice.api.storage.StorageId
import org.whispersystems.signalservice.api.storage.safeSetBackupsSubscriber
@ -35,7 +34,11 @@ class AccountRecordProcessor(
private var foundAccountRecord = false
constructor(context: Context, self: Recipient) : this(context, self, buildAccountRecord(context, self).account.get())
constructor(context: Context, self: Recipient) : this(
context = context,
self = self,
localAccountRecord = StorageSyncHelper.buildAccountRecord(context, self).let { it.proto.account!!.toSignalAccountRecord(it.id) }
)
/**
* We want to catch:

View file

@ -138,7 +138,7 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
return StorageSyncModels.localToRemoteRecord(updatedSettings);
}
})
.map(r -> r.getContact().get());
.map(r -> new SignalContactRecord(r.getId(), r.getProto().contact));
}
@Override

View file

@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.groups.BadGroupIdException
import org.thoughtcrime.securesms.groups.GroupId
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record
import org.whispersystems.signalservice.api.storage.SignalStorageRecord
import org.whispersystems.signalservice.api.storage.toSignalGroupV1Record
import java.util.Optional
/**
@ -53,7 +54,7 @@ class GroupV1RecordProcessor(private val groupDatabase: GroupTable, private val
return recipientId
.map { recipientTable.getRecordForSync(it)!! }
.map { settings: RecipientRecord -> StorageSyncModels.localToRemoteRecord(settings) }
.map { record: SignalStorageRecord -> record.groupV1.get() }
.map { record: SignalStorageRecord -> record.proto.groupV1!!.toSignalGroupV1Record(record.id) }
}
override fun merge(remote: SignalGroupV1Record, local: SignalGroupV1Record, keyGenerator: StorageKeyGenerator): SignalGroupV1Record {

View file

@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.database.model.RecipientRecord
import org.thoughtcrime.securesms.groups.GroupId
import org.whispersystems.signalservice.api.storage.SignalGroupV2Record
import org.whispersystems.signalservice.api.storage.SignalStorageRecord
import org.whispersystems.signalservice.api.storage.toSignalGroupV2Record
import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record
import java.util.Optional
@ -39,7 +40,7 @@ class GroupV2RecordProcessor(private val recipientTable: RecipientTable, private
StorageSyncModels.localToRemoteRecord(settings, remote.masterKeyOrThrow)
}
}
.map { record: SignalStorageRecord -> record.groupV2.get() }
.map { record: SignalStorageRecord -> record.proto.groupV2!!.toSignalGroupV2Record(record.id) }
}
override fun merge(remote: SignalGroupV2Record, local: SignalGroupV2Record, keyGenerator: StorageKeyGenerator): SignalGroupV2Record {

View file

@ -33,6 +33,7 @@ import org.whispersystems.signalservice.api.storage.safeSetBackupsSubscriber
import org.whispersystems.signalservice.api.storage.safeSetPayments
import org.whispersystems.signalservice.api.storage.safeSetSubscriber
import org.whispersystems.signalservice.api.storage.toSignalAccountRecord
import org.whispersystems.signalservice.api.storage.toSignalStorageRecord
import org.whispersystems.signalservice.api.util.OptionalUtil.byteArrayEquals
import org.whispersystems.signalservice.api.util.UuidUtil
import org.whispersystems.signalservice.api.util.toByteArray
@ -182,12 +183,12 @@ object StorageSyncHelper {
safeSetPayments(SignalStore.payments.mobileCoinPaymentsEnabled(), Optional.ofNullable(SignalStore.payments.paymentsEntropy).map { obj: Entropy -> obj.bytes }.orElse(null))
}
return SignalStorageRecord.forAccount(accountRecord.toSignalAccountRecord(StorageId.forAccount(storageId)))
return accountRecord.toSignalAccountRecord(StorageId.forAccount(storageId)).toSignalStorageRecord()
}
@JvmStatic
fun applyAccountStorageSyncUpdates(context: Context, self: Recipient, updatedRecord: SignalAccountRecord, fetchProfile: Boolean) {
val localRecord = buildAccountRecord(context, self).account.get()
val localRecord = buildAccountRecord(context, self).let { it.proto.account!!.toSignalAccountRecord(it.id) }
applyAccountStorageSyncUpdates(context, self, StorageRecordUpdate(localRecord, updatedRecord), fetchProfile)
}

View file

@ -25,6 +25,7 @@ import org.whispersystems.signalservice.api.storage.SignalGroupV1Record
import org.whispersystems.signalservice.api.storage.SignalGroupV2Record
import org.whispersystems.signalservice.api.storage.SignalStorageRecord
import org.whispersystems.signalservice.api.storage.SignalStoryDistributionListRecord
import org.whispersystems.signalservice.api.storage.toSignalStorageRecord
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import org.whispersystems.signalservice.api.util.UuidUtil
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord
@ -50,17 +51,17 @@ object StorageSyncModels {
throw AssertionError("Must have a storage key!")
}
return SignalStorageRecord.forGroupV2(localToRemoteGroupV2(settings, settings.storageId, groupMasterKey))
return localToRemoteGroupV2(settings, settings.storageId, groupMasterKey).toSignalStorageRecord()
}
@JvmStatic
fun localToRemoteRecord(settings: RecipientRecord, rawStorageId: ByteArray): SignalStorageRecord {
return when (settings.recipientType) {
RecipientType.INDIVIDUAL -> SignalStorageRecord.forContact(localToRemoteContact(settings, rawStorageId))
RecipientType.GV1 -> SignalStorageRecord.forGroupV1(localToRemoteGroupV1(settings, rawStorageId))
RecipientType.GV2 -> SignalStorageRecord.forGroupV2(localToRemoteGroupV2(settings, rawStorageId, settings.syncExtras.groupMasterKey!!))
RecipientType.DISTRIBUTION_LIST -> SignalStorageRecord.forStoryDistributionList(localToRemoteStoryDistributionList(settings, rawStorageId))
RecipientType.CALL_LINK -> SignalStorageRecord.forCallLink(localToRemoteCallLink(settings, rawStorageId))
RecipientType.INDIVIDUAL -> localToRemoteContact(settings, rawStorageId).toSignalStorageRecord()
RecipientType.GV1 -> localToRemoteGroupV1(settings, rawStorageId).toSignalStorageRecord()
RecipientType.GV2 -> localToRemoteGroupV2(settings, rawStorageId, settings.syncExtras.groupMasterKey!!).toSignalStorageRecord()
RecipientType.DISTRIBUTION_LIST -> localToRemoteStoryDistributionList(settings, rawStorageId).toSignalStorageRecord()
RecipientType.CALL_LINK -> localToRemoteCallLink(settings, rawStorageId).toSignalStorageRecord()
else -> throw AssertionError("Unsupported type!")
}
}

View file

@ -9,10 +9,12 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.signal.core.util.Base64;
import org.signal.core.util.SetUtil;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.storage.SignalContactRecord;
import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
import org.whispersystems.signalservice.api.storage.StorageId;
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord;
import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord;
import java.nio.ByteBuffer;
@ -166,18 +168,18 @@ public final class StorageSyncValidations {
throw new UnknownInsertError();
}
if (insert.getContact().isPresent()) {
SignalContactRecord contact = insert.getContact().get();
if (insert.getProto().contact != null) {
ContactRecord contact = insert.getProto().contact;
if (self.requireAci().equals(contact.getAci().orElse(null)) ||
self.requirePni().equals(contact.getPni().orElse(null)) ||
self.requireE164().equals(contact.getNumber().orElse("")))
if (self.requireAci().equals(ServiceId.ACI.parseOrNull(contact.aci)) ||
self.requirePni().equals(ServiceId.PNI.parseOrNull(contact.pni)) ||
self.requireE164().equals(contact.e164))
{
throw new SelfAddedAsContactError();
}
}
if (insert.getAccount().isPresent() && insert.getAccount().get().getProto().profileKey.size() == 0) {
if (insert.getProto().account != null && insert.getProto().account.profileKey.size() == 0) {
Log.w(TAG, "Uploading a null profile key in our AccountRecord!");
}
}

View file

@ -7,6 +7,8 @@ import org.thoughtcrime.securesms.database.SignalDatabase
import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.api.storage.SignalStoryDistributionListRecord
import org.whispersystems.signalservice.api.storage.toSignalStoryDistributionListRecord
import org.whispersystems.signalservice.api.util.OptionalUtil.asOptional
import org.whispersystems.signalservice.api.util.UuidUtil
import java.io.IOException
import java.util.Optional
@ -78,14 +80,7 @@ class StoryDistributionListRecordProcessor : DefaultStorageRecordProcessor<Signa
throw InvalidGroupTypeException()
}
val record = StorageSyncModels.localToRemoteRecord(recordForSync).storyDistributionList
if (record.isPresent) {
Log.d(TAG, "Found a matching record.")
return record
} else {
Log.e(TAG, "Could not resolve the record")
throw UnexpectedEmptyOptionalException()
}
return StorageSyncModels.localToRemoteRecord(recordForSync).let { it.proto.storyDistributionList!!.toSignalStoryDistributionListRecord(it.id) }.asOptional()
} else {
Log.d(TAG, "Could not find a matching record. Returning an empty.")
return Optional.empty()

View file

@ -23,24 +23,24 @@ class StorageServicePlugin : Plugin {
for (record in signalStorageRecords) {
val row = mutableListOf<String>()
if (record.account.isPresent) {
if (record.proto.account != null) {
row += "Account"
row += record.account.get().toProto().toString()
} else if (record.contact.isPresent) {
row += record.proto.account.toString()
} else if (record.proto.contact != null) {
row += "Contact"
row += record.contact.get().toProto().toString()
} else if (record.groupV1.isPresent) {
row += record.proto.toString()
} else if (record.proto.groupV1 != null) {
row += "GV1"
row += record.groupV1.get().toProto().toString()
} else if (record.groupV2.isPresent) {
row += record.proto.toString()
} else if (record.proto.groupV2 != null) {
row += "GV2"
row += record.groupV2.get().toProto().toString()
} else if (record.storyDistributionList.isPresent) {
row += record.proto.toString()
} else if (record.proto.storyDistributionList != null) {
row += "Distribution List"
row += record.storyDistributionList.get().toProto().toString()
} else if (record.callLink.isPresent) {
row += record.proto.toString()
} else if (record.proto.callLink != null) {
row += "Call Link"
row += record.callLink.get().toProto().toString()
row += record.proto.callLink.toString()
} else {
row += "Unknown"
row += ""

View file

@ -156,20 +156,6 @@ public final class StorageSyncHelperTest {
assertTrue(StorageSyncHelper.profileKeyChanged(update(a, b)));
}
private static SignalStorageRecord record(SignalRecord record) {
if (record instanceof SignalContactRecord) {
return SignalStorageRecord.forContact(record.getId(), (SignalContactRecord) record);
} else if (record instanceof SignalGroupV1Record) {
return SignalStorageRecord.forGroupV1(record.getId(), (SignalGroupV1Record) record);
} else if (record instanceof SignalGroupV2Record) {
return SignalStorageRecord.forGroupV2(record.getId(), (SignalGroupV2Record) record);
} else if (record instanceof SignalAccountRecord) {
return SignalStorageRecord.forAccount(record.getId(), (SignalAccountRecord) record);
} else {
return SignalStorageRecord.forUnknown(record.getId());
}
}
private static SignalContactRecord.Builder contactBuilder(int key,
ACI aci,
String e164,

View file

@ -53,10 +53,6 @@ fun AccountRecord.Builder.safeSetBackupsSubscriber(subscriberId: ByteString, sub
return this
}
fun AccountRecord.Builder.toSignalAccountRecord(storageId: StorageId): SignalAccountRecord {
return SignalAccountRecord(storageId, this.build())
}
fun AccountRecord.PinnedConversation.Contact.toSignalServiceAddress(): SignalServiceAddress {
val serviceId = ServiceId.parseOrNull(this.serviceId)
return SignalServiceAddress(serviceId, this.e164)

View file

@ -31,10 +31,6 @@ class SignalAccountRecord(
}
}
override fun asStorageRecord(): SignalStorageRecord {
return SignalStorageRecord.forAccount(this)
}
fun serializeUnknownFields(): ByteArray? {
return if (proto.hasUnknownFields()) proto.encode() else null
}

View file

@ -21,10 +21,6 @@ class SignalCallLinkRecord(
val adminPassKey: ByteArray = proto.adminPasskey.toByteArray()
val deletionTimestamp: Long = proto.deletedAtTimestampMs
override fun asStorageRecord(): SignalStorageRecord {
return SignalStorageRecord.forCallLink(this)
}
fun isDeleted(): Boolean {
return deletionTimestamp > 0
}

View file

@ -74,11 +74,6 @@ public final class SignalContactRecord implements SignalRecord<ContactRecord> {
return proto;
}
@Override
public SignalStorageRecord asStorageRecord() {
return SignalStorageRecord.forContact(this);
}
public boolean hasUnknownFields() {
return hasUnknownFields;
}

View file

@ -37,11 +37,6 @@ public final class SignalGroupV1Record implements SignalRecord<GroupV1Record> {
return proto;
}
@Override
public SignalStorageRecord asStorageRecord() {
return SignalStorageRecord.forGroupV1(this);
}
public boolean hasUnknownFields() {
return hasUnknownFields;
}

View file

@ -40,11 +40,6 @@ public final class SignalGroupV2Record implements SignalRecord<GroupV2Record> {
return proto;
}
@Override
public SignalStorageRecord asStorageRecord() {
return SignalStorageRecord.forGroupV2(this);
}
public boolean hasUnknownFields() {
return hasUnknownFields;
}

View file

@ -6,7 +6,6 @@ import kotlin.reflect.full.memberProperties
interface SignalRecord<E> {
val id: StorageId
val proto: E
fun asStorageRecord(): SignalStorageRecord
fun describeDiff(other: SignalRecord<*>): String {
if (this::class != other::class) {

View file

@ -1,94 +0,0 @@
package org.whispersystems.signalservice.api.storage;
import org.signal.core.util.ProtoUtil;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.logging.Log;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord;
import org.whispersystems.signalservice.internal.storage.protos.StorageItem;
import org.whispersystems.signalservice.internal.storage.protos.StorageManifest;
import org.whispersystems.signalservice.internal.storage.protos.StorageRecord;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import okio.ByteString;
public final class SignalStorageModels {
private static final String TAG = SignalStorageModels.class.getSimpleName();
public static SignalStorageManifest remoteToLocalStorageManifest(StorageManifest manifest, StorageKey storageKey) throws IOException, InvalidKeyException {
byte[] rawRecord = SignalStorageCipher.decrypt(storageKey.deriveManifestKey(manifest.version), manifest.value_.toByteArray());
ManifestRecord manifestRecord = ManifestRecord.ADAPTER.decode(rawRecord);
List<StorageId> ids = new ArrayList<>(manifestRecord.identifiers.size());
for (ManifestRecord.Identifier id : manifestRecord.identifiers) {
int typeValue = (id.type != ManifestRecord.Identifier.Type.UNKNOWN) ? id.type.getValue()
: ProtoUtil.getUnknownEnumValue(id, StorageRecordProtoUtil.STORAGE_ID_TYPE_TAG);
ids.add(StorageId.forType(id.raw.toByteArray(), typeValue));
}
return new SignalStorageManifest(manifestRecord.version, manifestRecord.sourceDevice, ids);
}
public static SignalStorageRecord remoteToLocalStorageRecord(StorageItem item, int type, StorageKey storageKey) throws IOException, InvalidKeyException {
byte[] key = item.key.toByteArray();
byte[] rawRecord = SignalStorageCipher.decrypt(storageKey.deriveItemKey(key), item.value_.toByteArray());
StorageRecord record = StorageRecord.ADAPTER.decode(rawRecord);
StorageId id = StorageId.forType(key, type);
if (record.contact != null && type == ManifestRecord.Identifier.Type.CONTACT.getValue()) {
return SignalStorageRecord.forContact(id, new SignalContactRecord(id, record.contact));
} else if (record.groupV1 != null && type == ManifestRecord.Identifier.Type.GROUPV1.getValue()) {
return SignalStorageRecord.forGroupV1(id, new SignalGroupV1Record(id, record.groupV1));
} else if (record.groupV2 != null && type == ManifestRecord.Identifier.Type.GROUPV2.getValue() && record.groupV2.masterKey.size() == GroupMasterKey.SIZE) {
return SignalStorageRecord.forGroupV2(id, new SignalGroupV2Record(id, record.groupV2));
} else if (record.account != null && type == ManifestRecord.Identifier.Type.ACCOUNT.getValue()) {
return SignalStorageRecord.forAccount(id, new SignalAccountRecord(id, record.account));
} else if (record.storyDistributionList != null && type == ManifestRecord.Identifier.Type.STORY_DISTRIBUTION_LIST.getValue()) {
return SignalStorageRecord.forStoryDistributionList(id, new SignalStoryDistributionListRecord(id, record.storyDistributionList));
} else if (record.callLink != null && type == ManifestRecord.Identifier.Type.CALL_LINK.getValue()) {
return SignalStorageRecord.forCallLink(id, new SignalCallLinkRecord(id, record.callLink));
}else {
if (StorageId.isKnownType(type)) {
Log.w(TAG, "StorageId is of known type (" + type + "), but the data is bad! Falling back to unknown.");
}
return SignalStorageRecord.forUnknown(StorageId.forType(key, type));
}
}
public static StorageItem localToRemoteStorageRecord(SignalStorageRecord record, StorageKey storageKey) {
StorageRecord.Builder builder = new StorageRecord.Builder();
if (record.getContact().isPresent()) {
builder.contact(record.getContact().get().getProto());
} else if (record.getGroupV1().isPresent()) {
builder.groupV1(record.getGroupV1().get().getProto());
} else if (record.getGroupV2().isPresent()) {
builder.groupV2(record.getGroupV2().get().getProto());
} else if (record.getAccount().isPresent()) {
builder.account(record.getAccount().get().getProto());
} else if (record.getStoryDistributionList().isPresent()) {
builder.storyDistributionList(record.getStoryDistributionList().get().getProto());
} else if (record.getCallLink().isPresent()) {
builder.callLink(record.getCallLink().get().getProto());
} else {
throw new InvalidStorageWriteError();
}
StorageRecord remoteRecord = builder.build();
StorageItemKey itemKey = storageKey.deriveItemKey(record.getId().getRaw());
byte[] encryptedRecord = SignalStorageCipher.encrypt(itemKey, remoteRecord.encode());
return new StorageItem.Builder()
.key(ByteString.of(record.getId().getRaw()))
.value_(ByteString.of(encryptedRecord))
.build();
}
private static class InvalidStorageWriteError extends Error {
}
}

View file

@ -0,0 +1,96 @@
package org.whispersystems.signalservice.api.storage
import okio.ByteString.Companion.toByteString
import org.signal.core.util.getUnknownEnumValue
import org.signal.libsignal.protocol.InvalidKeyException
import org.signal.libsignal.protocol.logging.Log
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord
import org.whispersystems.signalservice.internal.storage.protos.StorageItem
import org.whispersystems.signalservice.internal.storage.protos.StorageManifest
import org.whispersystems.signalservice.internal.storage.protos.StorageRecord
import java.io.IOException
object SignalStorageModels {
private val TAG: String = SignalStorageModels::class.java.simpleName
@JvmStatic
@Throws(IOException::class, InvalidKeyException::class)
fun remoteToLocalStorageManifest(manifest: StorageManifest, storageKey: StorageKey): SignalStorageManifest {
val rawRecord = SignalStorageCipher.decrypt(storageKey.deriveManifestKey(manifest.version), manifest.value_.toByteArray())
val manifestRecord = ManifestRecord.ADAPTER.decode(rawRecord)
val ids: MutableList<StorageId> = ArrayList(manifestRecord.identifiers.size)
for (id in manifestRecord.identifiers) {
val typeValue = if ((id.type != ManifestRecord.Identifier.Type.UNKNOWN)) {
id.type.value
} else {
id.getUnknownEnumValue(StorageRecordProtoUtil.STORAGE_ID_TYPE_TAG)
}
ids.add(StorageId.forType(id.raw.toByteArray(), typeValue))
}
return SignalStorageManifest(manifestRecord.version, manifestRecord.sourceDevice, ids)
}
@JvmStatic
@Throws(IOException::class, InvalidKeyException::class)
fun remoteToLocalStorageRecord(item: StorageItem, type: Int, storageKey: StorageKey): SignalStorageRecord {
val key = item.key.toByteArray()
val rawRecord = SignalStorageCipher.decrypt(storageKey.deriveItemKey(key), item.value_.toByteArray())
val record = StorageRecord.ADAPTER.decode(rawRecord)
val id = StorageId.forType(key, type)
if (record.contact != null && type == ManifestRecord.Identifier.Type.CONTACT.value) {
return SignalContactRecord(id, record.contact).toSignalStorageRecord()
} else if (record.groupV1 != null && type == ManifestRecord.Identifier.Type.GROUPV1.value) {
return SignalGroupV1Record(id, record.groupV1).toSignalStorageRecord()
} else if (record.groupV2 != null && type == ManifestRecord.Identifier.Type.GROUPV2.value && record.groupV2.masterKey.size == GroupMasterKey.SIZE) {
return SignalGroupV2Record(id, record.groupV2).toSignalStorageRecord()
} else if (record.account != null && type == ManifestRecord.Identifier.Type.ACCOUNT.value) {
return SignalAccountRecord(id, record.account).toSignalStorageRecord()
} else if (record.storyDistributionList != null && type == ManifestRecord.Identifier.Type.STORY_DISTRIBUTION_LIST.value) {
return SignalStoryDistributionListRecord(id, record.storyDistributionList).toSignalStorageRecord()
} else if (record.callLink != null && type == ManifestRecord.Identifier.Type.CALL_LINK.value) {
return SignalCallLinkRecord(id, record.callLink).toSignalStorageRecord()
} else {
if (StorageId.isKnownType(type)) {
Log.w(TAG, "StorageId is of known type ($type), but the data is bad! Falling back to unknown.")
}
return SignalStorageRecord.forUnknown(StorageId.forType(key, type))
}
}
@JvmStatic
fun localToRemoteStorageRecord(record: SignalStorageRecord, storageKey: StorageKey): StorageItem {
val builder = StorageRecord.Builder()
if (record.proto.contact != null) {
builder.contact(record.proto.contact)
} else if (record.proto.groupV1 != null) {
builder.groupV1(record.proto.groupV1)
} else if (record.proto.groupV2 != null) {
builder.groupV2(record.proto.groupV2)
} else if (record.proto.account != null) {
builder.account(record.proto.account)
} else if (record.proto.storyDistributionList != null) {
builder.storyDistributionList(record.proto.storyDistributionList)
} else if (record.proto.callLink != null) {
builder.callLink(record.proto.callLink)
} else {
throw InvalidStorageWriteError()
}
val remoteRecord = builder.build()
val itemKey = storageKey.deriveItemKey(record.id.raw)
val encryptedRecord = SignalStorageCipher.encrypt(itemKey, remoteRecord.encode())
return StorageItem.Builder()
.key(record.id.raw.toByteString())
.value_(encryptedRecord.toByteString())
.build()
}
private class InvalidStorageWriteError : Error()
}

View file

@ -1,145 +0,0 @@
package org.whispersystems.signalservice.api.storage;
import org.jetbrains.annotations.NotNull;
import org.whispersystems.signalservice.internal.storage.protos.CallLinkRecord;
import java.util.Objects;
import java.util.Optional;
public class SignalStorageRecord {
private final StorageId id;
private final Optional<SignalStoryDistributionListRecord> storyDistributionList;
private final Optional<SignalContactRecord> contact;
private final Optional<SignalGroupV1Record> groupV1;
private final Optional<SignalGroupV2Record> groupV2;
private final Optional<SignalAccountRecord> account;
private final Optional<SignalCallLinkRecord> callLink;
public static SignalStorageRecord forStoryDistributionList(SignalStoryDistributionListRecord storyDistributionList) {
return forStoryDistributionList(storyDistributionList.getId(), storyDistributionList);
}
public static SignalStorageRecord forStoryDistributionList(StorageId key, SignalStoryDistributionListRecord storyDistributionList) {
return new SignalStorageRecord(key, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(storyDistributionList), Optional.empty());
}
public static SignalStorageRecord forContact(SignalContactRecord contact) {
return forContact(contact.getId(), contact);
}
public static SignalStorageRecord forContact(StorageId key, SignalContactRecord contact) {
return new SignalStorageRecord(key, Optional.of(contact), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty());
}
public static SignalStorageRecord forGroupV1(SignalGroupV1Record groupV1) {
return forGroupV1(groupV1.getId(), groupV1);
}
public static SignalStorageRecord forGroupV1(StorageId key, SignalGroupV1Record groupV1) {
return new SignalStorageRecord(key, Optional.empty(), Optional.of(groupV1), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty());
}
public static SignalStorageRecord forGroupV2(SignalGroupV2Record groupV2) {
return forGroupV2(groupV2.getId(), groupV2);
}
public static SignalStorageRecord forGroupV2(StorageId key, SignalGroupV2Record groupV2) {
return new SignalStorageRecord(key, Optional.empty(), Optional.empty(), Optional.of(groupV2), Optional.empty(), Optional.empty(), Optional.empty());
}
public static SignalStorageRecord forAccount(SignalAccountRecord account) {
return forAccount(account.getId(), account);
}
public static SignalStorageRecord forAccount(StorageId key, SignalAccountRecord account) {
return new SignalStorageRecord(key, Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(account), Optional.empty(), Optional.empty());
}
@NotNull
public static SignalStorageRecord forCallLink(@NotNull SignalCallLinkRecord callLink) {
return forCallLink(callLink.getId(), callLink);
}
@NotNull
public static SignalStorageRecord forCallLink(StorageId key, @NotNull SignalCallLinkRecord callLink) {
return new SignalStorageRecord(key, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(callLink));
}
public static SignalStorageRecord forUnknown(StorageId key) {
return new SignalStorageRecord(key, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty());
}
private SignalStorageRecord(StorageId id,
Optional<SignalContactRecord> contact,
Optional<SignalGroupV1Record> groupV1,
Optional<SignalGroupV2Record> groupV2,
Optional<SignalAccountRecord> account,
Optional<SignalStoryDistributionListRecord> storyDistributionList,
Optional<SignalCallLinkRecord> callLink)
{
this.id = id;
this.contact = contact;
this.groupV1 = groupV1;
this.groupV2 = groupV2;
this.account = account;
this.storyDistributionList = storyDistributionList;
this.callLink = callLink;
}
public StorageId getId() {
return id;
}
public int getType() {
return id.getType();
}
public Optional<SignalContactRecord> getContact() {
return contact;
}
public Optional<SignalGroupV1Record> getGroupV1() {
return groupV1;
}
public Optional<SignalGroupV2Record> getGroupV2() {
return groupV2;
}
public Optional<SignalAccountRecord> getAccount() {
return account;
}
public Optional<SignalStoryDistributionListRecord> getStoryDistributionList() {
return storyDistributionList;
}
public Optional<SignalCallLinkRecord> getCallLink() {
return callLink;
}
public boolean isUnknown() {
return !contact.isPresent() && !groupV1.isPresent() && !groupV2.isPresent() && !account.isPresent() && !storyDistributionList.isPresent() && !callLink.isPresent();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SignalStorageRecord that = (SignalStorageRecord) o;
return Objects.equals(id, that.id) &&
Objects.equals(contact, that.contact) &&
Objects.equals(groupV1, that.groupV1) &&
Objects.equals(groupV2, that.groupV2) &&
Objects.equals(storyDistributionList, that.storyDistributionList) &&
Objects.equals(callLink, that.callLink);
}
@Override
public int hashCode() {
return Objects.hash(id, contact, groupV1, groupV2, storyDistributionList, callLink);
}
}

View file

@ -0,0 +1,21 @@
package org.whispersystems.signalservice.api.storage
import org.whispersystems.signalservice.internal.storage.protos.StorageRecord
/**
* A wrapper around [StorageRecord] to pair it with a [StorageId].
*/
data class SignalStorageRecord(
val id: StorageId,
val proto: StorageRecord
) {
val isUnknown: Boolean
get() = proto.contact == null && proto.groupV1 == null && proto.groupV2 == null && proto.account == null && proto.storyDistributionList == null && proto.callLink == null
companion object {
@JvmStatic
fun forUnknown(key: StorageId): SignalStorageRecord {
return SignalStorageRecord(key, proto = StorageRecord())
}
}
}

View file

@ -47,11 +47,6 @@ public class SignalStoryDistributionListRecord implements SignalRecord<StoryDist
return proto;
}
@Override
public SignalStorageRecord asStorageRecord() {
return SignalStorageRecord.forStoryDistributionList(this);
}
public byte[] serializeUnknownFields() {
return hasUnknownFields ? proto.encode() : null;
}

View file

@ -0,0 +1,66 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.storage
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord
import org.whispersystems.signalservice.internal.storage.protos.CallLinkRecord
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord
import org.whispersystems.signalservice.internal.storage.protos.GroupV1Record
import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record
import org.whispersystems.signalservice.internal.storage.protos.StorageRecord
import org.whispersystems.signalservice.internal.storage.protos.StoryDistributionListRecord
fun ContactRecord.toSignalContactRecord(storageId: StorageId): SignalContactRecord {
return SignalContactRecord(storageId, this)
}
fun AccountRecord.toSignalAccountRecord(storageId: StorageId): SignalAccountRecord {
return SignalAccountRecord(storageId, this)
}
fun AccountRecord.Builder.toSignalAccountRecord(storageId: StorageId): SignalAccountRecord {
return SignalAccountRecord(storageId, this.build())
}
fun GroupV1Record.toSignalGroupV1Record(storageId: StorageId): SignalGroupV1Record {
return SignalGroupV1Record(storageId, this)
}
fun GroupV2Record.toSignalGroupV2Record(storageId: StorageId): SignalGroupV2Record {
return SignalGroupV2Record(storageId, this)
}
fun StoryDistributionListRecord.toSignalStoryDistributionListRecord(storageId: StorageId): SignalStoryDistributionListRecord {
return SignalStoryDistributionListRecord(storageId, this)
}
fun CallLinkRecord.toSignalCallLinkRecord(storageId: StorageId): SignalCallLinkRecord {
return SignalCallLinkRecord(storageId, this)
}
fun SignalContactRecord.toSignalStorageRecord(): SignalStorageRecord {
return SignalStorageRecord(id, StorageRecord(contact = this.proto))
}
fun SignalGroupV1Record.toSignalStorageRecord(): SignalStorageRecord {
return SignalStorageRecord(id, StorageRecord(groupV1 = this.proto))
}
fun SignalGroupV2Record.toSignalStorageRecord(): SignalStorageRecord {
return SignalStorageRecord(id, StorageRecord(groupV2 = this.proto))
}
fun SignalAccountRecord.toSignalStorageRecord(): SignalStorageRecord {
return SignalStorageRecord(id, StorageRecord(account = this.proto))
}
fun SignalStoryDistributionListRecord.toSignalStorageRecord(): SignalStorageRecord {
return SignalStorageRecord(id, StorageRecord(storyDistributionList = this.proto))
}
fun SignalCallLinkRecord.toSignalStorageRecord(): SignalStorageRecord {
return SignalStorageRecord(id, StorageRecord(callLink = this.proto))
}