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 index bfbae4731a..12d6844e0a 100644 --- 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 @@ -1,5 +1,7 @@ package org.whispersystems.signalservice.api.storage; +import org.whispersystems.signalservice.api.messages.multidevice.MessageRequestResponseMessage; +import org.whispersystems.signalservice.api.util.ProtoUtil; import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord; import org.whispersystems.signalservice.internal.storage.protos.StorageManifest; @@ -87,10 +89,16 @@ public class SignalStorageManifest { List ids = new ArrayList<>(storageIds.size()); for (StorageId id : storageIds) { - ids.add(new ManifestRecord.Identifier.Builder() - .type(ManifestRecord.Identifier.Type.Companion.fromValue(id.getType())) - .raw(ByteString.of(id.getRaw())) - .build()); + 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() diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageModels.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageModels.java index 80dceb5a93..d5ab0246f4 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageModels.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageModels.java @@ -3,6 +3,7 @@ package org.whispersystems.signalservice.api.storage; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.logging.Log; import org.signal.libsignal.zkgroup.groups.GroupMasterKey; +import org.whispersystems.signalservice.api.util.ProtoUtil; import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord; import org.whispersystems.signalservice.internal.storage.protos.StorageItem; import org.whispersystems.signalservice.internal.storage.protos.StorageManifest; @@ -24,7 +25,10 @@ public final class SignalStorageModels { List ids = new ArrayList<>(manifestRecord.identifiers.size()); for (ManifestRecord.Identifier id : manifestRecord.identifiers) { - ids.add(StorageId.forType(id.raw.toByteArray(), id.type.getValue())); + 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); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/StorageId.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/StorageId.java index af8bae489a..bc15097abf 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/StorageId.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/StorageId.java @@ -58,23 +58,13 @@ public class StorageId { public static boolean isKnownType(int val) { for (ManifestRecord.Identifier.Type type : ManifestRecord.Identifier.Type.values()) { - if (type.getValue() == val) { + if (type != ManifestRecord.Identifier.Type.UNKNOWN && type.getValue() == val) { return true; } } return false; } - public static int largestKnownType() { - int max = 0; - - for (ManifestRecord.Identifier.Type type : ManifestRecord.Identifier.Type.values()) { - max = Math.max(type.getValue(), max); - } - - return max; - } - @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/StorageRecordProtoUtil.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/StorageRecordProtoUtil.kt index ca5cec3778..213809b24f 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/StorageRecordProtoUtil.kt +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/StorageRecordProtoUtil.kt @@ -13,6 +13,10 @@ import org.whispersystems.signalservice.internal.storage.protos.AccountRecord * Provide helpers for various Storage Service protos. */ object StorageRecordProtoUtil { + + /** Must match tag value specified for ManifestRecord.Identifier#type in StorageService.proto */ + const val STORAGE_ID_TYPE_TAG = 2 + @JvmStatic val defaultAccountRecord by lazy { AccountRecord() } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/ProtoUtil.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/ProtoUtil.java deleted file mode 100644 index df5825de56..0000000000 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/ProtoUtil.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.whispersystems.signalservice.api.util; - -import com.squareup.wire.Message; - -import org.signal.libsignal.protocol.logging.Log; - -import java.lang.reflect.Field; -import java.util.LinkedList; -import java.util.List; - -import okio.ByteString; - -public final class ProtoUtil { - - private static final String TAG = ProtoUtil.class.getSimpleName(); - - private static final String DEFAULT_INSTANCE = "DEFAULT_INSTANCE"; - - private ProtoUtil() {} - - /** - * True if there are unknown fields anywhere inside the proto or its nested protos. - */ - @SuppressWarnings("rawtypes") - public static boolean hasUnknownFields(Message rootProto) { - List allProtos = getInnerProtos(rootProto); - allProtos.add(rootProto); - - for (Message proto : allProtos) { - ByteString unknownFields = proto.unknownFields(); - - if (unknownFields.size() > 0) { - return true; - } - } - - return false; - } - - /** - * Recursively retrieves all inner complex proto types inside a given proto. - */ - @SuppressWarnings("rawtypes") - private static List getInnerProtos(Message proto) { - List innerProtos = new LinkedList<>(); - - try { - Field[] fields = proto.getClass().getDeclaredFields(); - - for (Field field : fields) { - if (!field.getName().equals(DEFAULT_INSTANCE) && Message.class.isAssignableFrom(field.getType())) { - field.setAccessible(true); - - Message inner = (Message) field.get(proto); - if (inner != null) { - innerProtos.add(inner); - innerProtos.addAll(getInnerProtos(inner)); - } - } - } - - } catch (IllegalAccessException e) { - Log.w(TAG, "Failed to get inner protos!", e); - } - - return innerProtos; - } -} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/ProtoUtil.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/ProtoUtil.kt new file mode 100644 index 0000000000..52078c0e37 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/ProtoUtil.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +@file:JvmName("ProtoUtil") + +package org.whispersystems.signalservice.api.util + +import com.squareup.wire.FieldEncoding +import com.squareup.wire.Message +import com.squareup.wire.ProtoAdapter +import com.squareup.wire.ProtoReader +import com.squareup.wire.ProtoWriter +import okio.Buffer +import okio.ByteString +import org.signal.libsignal.protocol.logging.Log +import java.util.LinkedList + +object ProtoUtil { + private val TAG = ProtoUtil::class.java.simpleName + + /** + * True if there are unknown fields anywhere inside the proto or its nested protos. + */ + @JvmStatic + fun hasUnknownFields(rootProto: Message<*, *>): Boolean { + val allProtos = getInnerProtos(rootProto) + allProtos.add(rootProto) + for (proto in allProtos) { + val unknownFields = proto.unknownFields + if (unknownFields.size > 0) { + return true + } + } + return false + } + + @JvmStatic + fun getUnknownEnumValue(proto: Message<*, *>, tag: Int): Int { + val reader = ProtoReader(Buffer().write(proto.unknownFields)) + reader.forEachTag { unknownTag -> + if (unknownTag == tag) { + return ProtoAdapter.INT32.decode(reader) + } + } + + throw AssertionError("Tag $tag not found in unknown fields") + } + + @JvmStatic + fun writeUnknownEnumValue(tag: Int, enumValue: Int): ByteString { + val buffer = Buffer() + val writer = ProtoWriter(buffer) + + @Suppress("UNCHECKED_CAST") + (FieldEncoding.VARINT.rawProtoAdapter() as ProtoAdapter).encodeWithTag(writer, tag, enumValue.toLong()) + + return buffer.readByteString() + } + + /** + * Recursively retrieves all inner complex proto types inside a given proto. + */ + private fun getInnerProtos(proto: Message<*, *>): MutableList> { + val innerProtos: MutableList> = LinkedList() + try { + val fields = proto.javaClass.declaredFields + for (field in fields) { + if (Message::class.java.isAssignableFrom(field.type)) { + field.isAccessible = true + val inner = field[proto] as? Message<*, *> + if (inner != null) { + innerProtos.add(inner) + innerProtos.addAll(getInnerProtos(inner)) + } + } + } + } catch (e: IllegalAccessException) { + Log.w(TAG, "Failed to get inner protos!", e) + } + return innerProtos + } +}