From b6bb3928e7f294ecd323a2822fb7cd541158b7fe Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 13 Nov 2024 11:51:31 -0500 Subject: [PATCH] Convert SignalStorageManifest to kotlin. --- .../storage/StorageSyncValidations.java | 16 +-- .../api/SignalServiceAccountManager.java | 14 +-- .../ManifestRecordIdentifierExtensions.kt | 44 +++++++ .../api/storage/SignalStorageManifest.java | 114 ------------------ .../api/storage/SignalStorageManifest.kt | 51 ++++++++ .../api/storage/SignalStorageModels.kt | 13 +- 6 files changed, 112 insertions(+), 140 deletions(-) create mode 100644 libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/ManifestRecordIdentifierExtensions.kt delete mode 100644 libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageManifest.java create mode 100644 libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageManifest.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncValidations.java b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncValidations.java index 581aeb1baf..65fcd668fe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncValidations.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncValidations.java @@ -36,7 +36,7 @@ public final class StorageSyncValidations { validateManifestAndInserts(result.manifest, result.inserts, self); if (result.deletes.size() > 0) { - Set allSetEncoded = Stream.of(result.manifest.getStorageIds()).map(StorageId::getRaw).map(Base64::encodeWithPadding).collect(Collectors.toSet()); + Set allSetEncoded = Stream.of(result.manifest.storageIds).map(StorageId::getRaw).map(Base64::encodeWithPadding).collect(Collectors.toSet()); for (byte[] delete : result.deletes) { String encoded = Base64.encodeWithPadding(delete); @@ -46,12 +46,12 @@ public final class StorageSyncValidations { } } - if (previousManifest.getVersion() == 0) { + if (previousManifest.version == 0) { Log.i(TAG, "Previous manifest is empty, not bothering with additional validations around the diffs between the two manifests."); return; } - if (result.manifest.getVersion() != previousManifest.getVersion() + 1) { + if (result.manifest.version != previousManifest.version + 1) { throw new IncorrectManifestVersionError(); } @@ -60,8 +60,8 @@ public final class StorageSyncValidations { return; } - Set previousIds = Stream.of(previousManifest.getStorageIds()).map(id -> ByteBuffer.wrap(id.getRaw())).collect(Collectors.toSet()); - Set newIds = Stream.of(result.manifest.getStorageIds()).map(id -> ByteBuffer.wrap(id.getRaw())).collect(Collectors.toSet()); + Set previousIds = Stream.of(previousManifest.storageIds).map(id -> ByteBuffer.wrap(id.getRaw())).collect(Collectors.toSet()); + Set newIds = Stream.of(result.manifest.storageIds).map(id -> ByteBuffer.wrap(id.getRaw())).collect(Collectors.toSet()); Set manifestInserts = SetUtil.difference(newIds, previousIds); Set manifestDeletes = SetUtil.difference(previousIds, newIds); @@ -105,7 +105,7 @@ public final class StorageSyncValidations { private static void validateManifestAndInserts(@NonNull SignalStorageManifest manifest, @NonNull List inserts, @NonNull Recipient self) { int accountCount = 0; - for (StorageId id : manifest.getStorageIds()) { + for (StorageId id : manifest.storageIds) { accountCount += id.getType() == ManifestRecord.Identifier.Type.ACCOUNT.getValue() ? 1 : 0; } @@ -117,11 +117,11 @@ public final class StorageSyncValidations { throw new MissingAccountError(); } - Set allSet = new HashSet<>(manifest.getStorageIds()); + Set allSet = new HashSet<>(manifest.storageIds); Set insertSet = new HashSet<>(Stream.of(inserts).map(SignalStorageRecord::getId).toList()); Set rawIdSet = Stream.of(allSet).map(id -> ByteBuffer.wrap(id.getRaw())).collect(Collectors.toSet()); - if (allSet.size() != manifest.getStorageIds().size()) { + if (allSet.size() != manifest.storageIds.size()) { throw new DuplicateStorageIdError(); } diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java index b442fc0868..e5aa08c6a6 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java @@ -428,13 +428,13 @@ public class SignalServiceAccountManager { throws IOException, InvalidKeyException { ManifestRecord.Builder manifestRecordBuilder = new ManifestRecord.Builder() - .sourceDevice(manifest.getSourceDeviceId()) - .version(manifest.getVersion()); + .sourceDevice(manifest.sourceDeviceId) + .version(manifest.version); manifestRecordBuilder.identifiers( - manifest.getStorageIds().stream() - .map(id -> { + manifest.storageIds.stream() + .map(id -> { ManifestRecord.Identifier.Builder builder = new ManifestRecord.Identifier.Builder() .raw(ByteString.of(id.getRaw())); if (!id.isUnknown()) { @@ -445,14 +445,14 @@ public class SignalServiceAccountManager { } return builder.build(); }) - .collect(Collectors.toList()) + .collect(Collectors.toList()) ); String authToken = this.pushServiceSocket.getStorageAuth(); - StorageManifestKey manifestKey = storageKey.deriveManifestKey(manifest.getVersion()); + StorageManifestKey manifestKey = storageKey.deriveManifestKey(manifest.version); byte[] encryptedRecord = SignalStorageCipher.encrypt(manifestKey, manifestRecordBuilder.build().encode()); StorageManifest storageManifest = new StorageManifest.Builder() - .version(manifest.getVersion()) + .version(manifest.version) .value_(ByteString.of(encryptedRecord)) .build(); diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/ManifestRecordIdentifierExtensions.kt b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/ManifestRecordIdentifierExtensions.kt new file mode 100644 index 0000000000..d64d3cabfb --- /dev/null +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/ManifestRecordIdentifierExtensions.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.signalservice.api.storage + +import com.squareup.wire.FieldEncoding +import okio.ByteString.Companion.toByteString +import org.signal.core.util.getUnknownEnumValue +import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord + +/** + * Wire makes it harder to write specific values to proto enums, since they use actual enums under the hood. + * This method handles creating an identifier from a possibly-unknown enum type, writing an unknown field if + * necessary to preserve the specific value. + */ +fun ManifestRecord.Identifier.Companion.fromPossiblyUnknownType(typeInt: Int, rawId: ByteArray): ManifestRecord.Identifier { + val builder = ManifestRecord.Identifier.Builder() + builder.raw = rawId.toByteString() + + val type = ManifestRecord.Identifier.Type.fromValue(typeInt) + if (type != null) { + builder.type = type + } else { + builder.type = ManifestRecord.Identifier.Type.UNKNOWN + builder.addUnknownField(StorageRecordProtoUtil.STORAGE_ID_TYPE_TAG, FieldEncoding.VARINT, typeInt) + } + + return builder.build() +} + +/** + * Wire makes it harder to read the underlying int value of an unknown enum. + * This value represents the _true_ int value of the enum, even if it is not one of the known values. + */ +val ManifestRecord.Identifier.typeValue: Int + get() { + return if (this.type != ManifestRecord.Identifier.Type.UNKNOWN) { + this.type.value + } else { + this.getUnknownEnumValue(StorageRecordProtoUtil.STORAGE_ID_TYPE_TAG) + } + } diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageManifest.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageManifest.java deleted file mode 100644 index bf50e00c83..0000000000 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageManifest.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.whispersystems.signalservice.api.storage; - -import org.signal.core.util.ProtoUtil; -import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord; -import org.whispersystems.signalservice.internal.storage.protos.StorageManifest; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import okio.ByteString; - -public class SignalStorageManifest { - public static final SignalStorageManifest EMPTY = new SignalStorageManifest(0, 1, Collections.emptyList()); - - private final long version; - private final int sourceDeviceId; - private final List storageIds; - private final Map> storageIdsByType; - - public SignalStorageManifest(long version, int sourceDeviceId, List storageIds) { - this.version = version; - this.sourceDeviceId = sourceDeviceId; - this.storageIds = storageIds; - this.storageIdsByType = new HashMap<>(); - - for (StorageId id : storageIds) { - List list = storageIdsByType.get(id.getType()); - if (list == null) { - list = new ArrayList<>(); - } - list.add(id); - storageIdsByType.put(id.getType(), list); - } - } - - public static SignalStorageManifest deserialize(byte[] serialized) { - try { - StorageManifest manifest = StorageManifest.ADAPTER.decode(serialized); - ManifestRecord manifestRecord = ManifestRecord.ADAPTER.decode(manifest.value_); - List ids = new ArrayList<>(manifestRecord.identifiers.size()); - - for (ManifestRecord.Identifier id : manifestRecord.identifiers) { - ids.add(StorageId.forType(id.raw.toByteArray(), id.type.getValue())); - } - - return new SignalStorageManifest(manifest.version, manifestRecord.sourceDevice, ids); - } catch (IOException e) { - throw new AssertionError(e); - } - } - - public long getVersion() { - return version; - } - - public int getSourceDeviceId() { - return sourceDeviceId; - } - - public String getVersionString() { - return version + "." + sourceDeviceId; - } - - public List getStorageIds() { - return storageIds; - } - - public Optional getAccountStorageId() { - List list = storageIdsByType.get(ManifestRecord.Identifier.Type.ACCOUNT.getValue()); - - if (list != null && list.size() > 0) { - return Optional.of(list.get(0)); - } else { - return Optional.empty(); - } - } - - public Map> getStorageIdsByType() { - return storageIdsByType; - } - - public byte[] serialize() { - List ids = new ArrayList<>(storageIds.size()); - - for (StorageId id : storageIds) { - ManifestRecord.Identifier.Type type = ManifestRecord.Identifier.Type.Companion.fromValue(id.getType()); - if (type != null) { - ids.add(new ManifestRecord.Identifier.Builder() - .type(type) - .raw(ByteString.of(id.getRaw())) - .build()); - } else { - ByteString unknownEnum = ProtoUtil.writeUnknownEnumValue(StorageRecordProtoUtil.STORAGE_ID_TYPE_TAG, id.getType()); - ids.add(new ManifestRecord.Identifier(ByteString.of(id.getRaw()), ManifestRecord.Identifier.Type.UNKNOWN, unknownEnum)); - } - } - - ManifestRecord manifestRecord = new ManifestRecord.Builder() - .identifiers(ids) - .sourceDevice(sourceDeviceId) - .build(); - - return new StorageManifest.Builder() - .version(version) - .value_(manifestRecord.encodeByteString()) - .build() - .encode(); - } -} diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageManifest.kt b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageManifest.kt new file mode 100644 index 0000000000..aea268928f --- /dev/null +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageManifest.kt @@ -0,0 +1,51 @@ +package org.whispersystems.signalservice.api.storage + +import org.signal.core.util.toOptional +import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord +import org.whispersystems.signalservice.internal.storage.protos.StorageManifest +import java.util.Optional + +class SignalStorageManifest( + @JvmField val version: Long, + @JvmField val sourceDeviceId: Int, + @JvmField val storageIds: List +) { + + companion object { + val EMPTY: SignalStorageManifest = SignalStorageManifest(0, 1, emptyList()) + + fun deserialize(serialized: ByteArray): SignalStorageManifest { + val manifest = StorageManifest.ADAPTER.decode(serialized) + val manifestRecord = ManifestRecord.ADAPTER.decode(manifest.value_) + val ids: List = manifestRecord.identifiers.map { id -> + StorageId.forType(id.raw.toByteArray(), id.typeValue) + } + + return SignalStorageManifest(manifest.version, manifestRecord.sourceDevice, ids) + } + } + + val storageIdsByType: Map> = storageIds.groupBy { it.type } + + val versionString: String + get() = "$version.$sourceDeviceId" + + val accountStorageId: Optional + get() = storageIdsByType[ManifestRecord.Identifier.Type.ACCOUNT.value]?.takeIf { it.isNotEmpty() }?.get(0).toOptional() + + fun serialize(): ByteArray { + val ids: List = storageIds.map { id -> + ManifestRecord.Identifier.fromPossiblyUnknownType(id.type, id.raw) + } + + val manifestRecord = ManifestRecord( + identifiers = ids, + sourceDevice = sourceDeviceId + ) + + return StorageManifest( + version = version, + value_ = manifestRecord.encodeByteString() + ).encode() + } +} diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageModels.kt b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageModels.kt index d4b3cb693e..1b9e37a8dd 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageModels.kt +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageModels.kt @@ -1,7 +1,6 @@ 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 @@ -19,16 +18,8 @@ object SignalStorageModels { 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 = 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)) + val ids: List = manifestRecord.identifiers.map { id -> + StorageId.forType(id.raw.toByteArray(), id.typeValue) } return SignalStorageManifest(manifestRecord.version, manifestRecord.sourceDevice, ids)