mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-27 04:04:43 +01:00
Add call link support to storage service.
This commit is contained in:
committed by
Cody Henthorne
parent
1f2b5e90a3
commit
e247d311d8
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.storage
|
||||
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.whispersystems.signalservice.internal.storage.protos.CallLinkRecord
|
||||
import java.io.IOException
|
||||
import java.util.LinkedList
|
||||
|
||||
/**
|
||||
* A record in storage service that represents a call link that was already created.
|
||||
*/
|
||||
class SignalCallLinkRecord(private val id: StorageId, private val proto: CallLinkRecord) : SignalRecord {
|
||||
|
||||
val rootKey: ByteArray = proto.rootKey.toByteArray()
|
||||
val adminPassKey: ByteArray = proto.adminPasskey.toByteArray()
|
||||
val deletionTimestamp: Long = proto.deletedAtTimestampMs
|
||||
|
||||
init {
|
||||
if (deletionTimestamp != 0L && adminPassKey.isNotEmpty()) {
|
||||
throw IllegalStateException("Cannot have nonzero deletion timestamp ($deletionTimestamp) and admin passkey!")
|
||||
}
|
||||
}
|
||||
|
||||
fun toProto(): CallLinkRecord {
|
||||
return proto
|
||||
}
|
||||
|
||||
override fun getId(): StorageId {
|
||||
return id
|
||||
}
|
||||
|
||||
override fun asStorageRecord(): SignalStorageRecord {
|
||||
return SignalStorageRecord.forCallLink(this)
|
||||
}
|
||||
|
||||
override fun describeDiff(other: SignalRecord?): String {
|
||||
return when (other) {
|
||||
is SignalCallLinkRecord -> {
|
||||
val diff = LinkedList<String>()
|
||||
if (!rootKey.contentEquals(other.rootKey)) {
|
||||
diff.add("RootKey")
|
||||
}
|
||||
|
||||
if (!adminPassKey.contentEquals(other.adminPassKey)) {
|
||||
diff.add("AdminPassKey")
|
||||
}
|
||||
|
||||
if (deletionTimestamp != other.deletionTimestamp) {
|
||||
diff.add("DeletionTimestamp")
|
||||
}
|
||||
|
||||
diff.toString()
|
||||
}
|
||||
|
||||
null -> {
|
||||
"Other was null!"
|
||||
}
|
||||
|
||||
else -> {
|
||||
"Different class. ${this::class.java.getSimpleName()} | ${other::class.java.getSimpleName()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isDeleted(): Boolean {
|
||||
return deletionTimestamp > 0
|
||||
}
|
||||
|
||||
class Builder(rawId: ByteArray, serializedUnknowns: ByteArray?) {
|
||||
private var id: StorageId = StorageId.forCallLink(rawId)
|
||||
private var builder: CallLinkRecord.Builder
|
||||
|
||||
init {
|
||||
if (serializedUnknowns != null) {
|
||||
this.builder = parseUnknowns(serializedUnknowns)
|
||||
} else {
|
||||
this.builder = CallLinkRecord.Builder()
|
||||
}
|
||||
}
|
||||
|
||||
fun setRootKey(rootKey: ByteArray): Builder {
|
||||
builder.rootKey = rootKey.toByteString()
|
||||
return this
|
||||
}
|
||||
|
||||
fun setAdminPassKey(adminPasskey: ByteArray): Builder {
|
||||
builder.adminPasskey = adminPasskey.toByteString()
|
||||
return this
|
||||
}
|
||||
|
||||
fun setDeletedTimestamp(deletedTimestamp: Long): Builder {
|
||||
builder.deletedAtTimestampMs = deletedTimestamp
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): SignalCallLinkRecord {
|
||||
return SignalCallLinkRecord(id, builder.build())
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun parseUnknowns(serializedUnknowns: ByteArray): CallLinkRecord.Builder {
|
||||
return try {
|
||||
CallLinkRecord.ADAPTER.decode(serializedUnknowns).newBuilder()
|
||||
} catch (e: IOException) {
|
||||
CallLinkRecord.Builder()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,9 @@ public final class SignalStorageModels {
|
||||
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 {
|
||||
} 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.");
|
||||
}
|
||||
@@ -71,6 +73,8 @@ public final class SignalStorageModels {
|
||||
builder.account(record.getAccount().get().toProto());
|
||||
} else if (record.getStoryDistributionList().isPresent()) {
|
||||
builder.storyDistributionList(record.getStoryDistributionList().get().toProto());
|
||||
} else if (record.getCallLink().isPresent()) {
|
||||
builder.callLink(record.getCallLink().get().toProto());
|
||||
} else {
|
||||
throw new InvalidStorageWriteError();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
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;
|
||||
|
||||
@@ -12,13 +15,14 @@ public class SignalStorageRecord implements SignalRecord {
|
||||
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));
|
||||
return new SignalStorageRecord(key, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(storyDistributionList), Optional.empty());
|
||||
}
|
||||
|
||||
public static SignalStorageRecord forContact(SignalContactRecord contact) {
|
||||
@@ -26,7 +30,7 @@ public class SignalStorageRecord implements SignalRecord {
|
||||
}
|
||||
|
||||
public static SignalStorageRecord forContact(StorageId key, SignalContactRecord contact) {
|
||||
return new SignalStorageRecord(key, Optional.of(contact), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty());
|
||||
return new SignalStorageRecord(key, Optional.of(contact), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
public static SignalStorageRecord forGroupV1(SignalGroupV1Record groupV1) {
|
||||
@@ -34,7 +38,7 @@ public class SignalStorageRecord implements SignalRecord {
|
||||
}
|
||||
|
||||
public static SignalStorageRecord forGroupV1(StorageId key, SignalGroupV1Record groupV1) {
|
||||
return new SignalStorageRecord(key, Optional.empty(), Optional.of(groupV1), Optional.empty(), Optional.empty(), Optional.empty());
|
||||
return new SignalStorageRecord(key, Optional.empty(), Optional.of(groupV1), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
public static SignalStorageRecord forGroupV2(SignalGroupV2Record groupV2) {
|
||||
@@ -42,7 +46,7 @@ public class SignalStorageRecord implements SignalRecord {
|
||||
}
|
||||
|
||||
public static SignalStorageRecord forGroupV2(StorageId key, SignalGroupV2Record groupV2) {
|
||||
return new SignalStorageRecord(key, Optional.empty(), Optional.empty(), Optional.of(groupV2), Optional.empty(), Optional.empty());
|
||||
return new SignalStorageRecord(key, Optional.empty(), Optional.empty(), Optional.of(groupV2), Optional.empty(), Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
public static SignalStorageRecord forAccount(SignalAccountRecord account) {
|
||||
@@ -50,19 +54,31 @@ public class SignalStorageRecord implements SignalRecord {
|
||||
}
|
||||
|
||||
public static SignalStorageRecord forAccount(StorageId key, SignalAccountRecord account) {
|
||||
return new SignalStorageRecord(key, Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(account), Optional.empty());
|
||||
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());
|
||||
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<SignalStoryDistributionListRecord> storyDistributionList,
|
||||
Optional<SignalCallLinkRecord> callLink)
|
||||
{
|
||||
this.id = id;
|
||||
this.contact = contact;
|
||||
@@ -70,6 +86,7 @@ public class SignalStorageRecord implements SignalRecord {
|
||||
this.groupV2 = groupV2;
|
||||
this.account = account;
|
||||
this.storyDistributionList = storyDistributionList;
|
||||
this.callLink = callLink;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -111,8 +128,12 @@ public class SignalStorageRecord implements SignalRecord {
|
||||
return storyDistributionList;
|
||||
}
|
||||
|
||||
public Optional<SignalCallLinkRecord> getCallLink() {
|
||||
return callLink;
|
||||
}
|
||||
|
||||
public boolean isUnknown() {
|
||||
return !contact.isPresent() && !groupV1.isPresent() && !groupV2.isPresent() && !account.isPresent() && !storyDistributionList.isPresent();
|
||||
return !contact.isPresent() && !groupV1.isPresent() && !groupV2.isPresent() && !account.isPresent() && !storyDistributionList.isPresent() && !callLink.isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -124,11 +145,12 @@ public class SignalStorageRecord implements SignalRecord {
|
||||
Objects.equals(contact, that.contact) &&
|
||||
Objects.equals(groupV1, that.groupV1) &&
|
||||
Objects.equals(groupV2, that.groupV2) &&
|
||||
Objects.equals(storyDistributionList, that.storyDistributionList);
|
||||
Objects.equals(storyDistributionList, that.storyDistributionList) &&
|
||||
Objects.equals(callLink, that.callLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, contact, groupV1, groupV2, storyDistributionList);
|
||||
return Objects.hash(id, contact, groupV1, groupV2, storyDistributionList, callLink);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@ public class StorageId {
|
||||
return new StorageId(ManifestRecord.Identifier.Type.ACCOUNT.getValue(), Preconditions.checkNotNull(raw));
|
||||
}
|
||||
|
||||
public static StorageId forCallLink(byte[] raw) {
|
||||
return new StorageId(ManifestRecord.Identifier.Type.CALL_LINK.getValue(), Preconditions.checkNotNull(raw));
|
||||
}
|
||||
|
||||
public static StorageId forType(byte[] raw, int type) {
|
||||
return new StorageId(type, raw);
|
||||
}
|
||||
|
||||
@@ -630,7 +630,7 @@ message SyncMessage {
|
||||
message CallLinkUpdate {
|
||||
enum Type {
|
||||
UPDATE = 0;
|
||||
DELETE = 1;
|
||||
reserved 1; // was DELETE, superseded by storage service
|
||||
}
|
||||
|
||||
optional bytes rootKey = 1;
|
||||
|
||||
@@ -50,6 +50,7 @@ message ManifestRecord {
|
||||
GROUPV2 = 3;
|
||||
ACCOUNT = 4;
|
||||
STORY_DISTRIBUTION_LIST = 5;
|
||||
CALL_LINK = 7;
|
||||
}
|
||||
|
||||
bytes raw = 1;
|
||||
@@ -69,6 +70,7 @@ message StorageRecord {
|
||||
GroupV2Record groupV2 = 3;
|
||||
AccountRecord account = 4;
|
||||
StoryDistributionListRecord storyDistributionList = 5;
|
||||
CallLinkRecord callLink = 7;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,3 +229,9 @@ message StoryDistributionListRecord {
|
||||
bool allowsReplies = 5;
|
||||
bool isBlockList = 6;
|
||||
}
|
||||
|
||||
message CallLinkRecord {
|
||||
bytes rootKey = 1;
|
||||
bytes adminPasskey = 2;
|
||||
uint64 deletedAtTimestampMs = 3;
|
||||
}
|
||||
Reference in New Issue
Block a user