Add call link support to storage service.

This commit is contained in:
Nicholas Tinsley
2024-09-09 13:15:32 -04:00
committed by Cody Henthorne
parent 1f2b5e90a3
commit e247d311d8
29 changed files with 645 additions and 83 deletions

View File

@@ -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()
}
}
}
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;
}