mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-23 02:10:44 +01:00
The rest of the storage service unwrapping.
This commit is contained in:
@@ -37,7 +37,7 @@ sealed class ServiceId(val libSignalServiceId: LibSignalServiceId) {
|
||||
@JvmOverloads
|
||||
@JvmStatic
|
||||
fun parseOrNull(raw: String?, logFailures: Boolean = true): ServiceId? {
|
||||
if (raw == null) {
|
||||
if (raw.isNullOrBlank()) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.storage
|
||||
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord
|
||||
|
||||
val ContactRecord.signalAci: ServiceId.ACI?
|
||||
get() = ServiceId.ACI.parseOrNull(this.aci)
|
||||
|
||||
val ContactRecord.signalPni: ServiceId.PNI?
|
||||
get() = ServiceId.PNI.parseOrNull(this.pni)
|
||||
@@ -1,55 +1,27 @@
|
||||
package org.whispersystems.signalservice.api.storage
|
||||
|
||||
import org.signal.core.util.hasUnknownFields
|
||||
import org.signal.libsignal.protocol.logging.Log
|
||||
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord
|
||||
import java.io.IOException
|
||||
|
||||
class SignalAccountRecord(
|
||||
/**
|
||||
* Wrapper around a [AccountRecord] to pair it with a [StorageId].
|
||||
*/
|
||||
data class SignalAccountRecord(
|
||||
override val id: StorageId,
|
||||
override val proto: AccountRecord
|
||||
) : SignalRecord<AccountRecord> {
|
||||
|
||||
companion object {
|
||||
private val TAG: String = SignalAccountRecord::class.java.simpleName
|
||||
|
||||
fun newBuilder(serializedUnknowns: ByteArray?): AccountRecord.Builder {
|
||||
return if (serializedUnknowns != null) {
|
||||
parseUnknowns(serializedUnknowns)
|
||||
} else {
|
||||
return serializedUnknowns?.let { builderFromUnknowns(it) } ?: AccountRecord.Builder()
|
||||
}
|
||||
|
||||
private fun builderFromUnknowns(serializedUnknowns: ByteArray): AccountRecord.Builder {
|
||||
return try {
|
||||
AccountRecord.ADAPTER.decode(serializedUnknowns).newBuilder()
|
||||
} catch (e: IOException) {
|
||||
AccountRecord.Builder()
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseUnknowns(serializedUnknowns: ByteArray): AccountRecord.Builder {
|
||||
try {
|
||||
return AccountRecord.ADAPTER.decode(serializedUnknowns).newBuilder()
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Failed to combine unknown fields!", e)
|
||||
return AccountRecord.Builder()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun serializeUnknownFields(): ByteArray? {
|
||||
return if (proto.hasUnknownFields()) proto.encode() else null
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as SignalAccountRecord
|
||||
|
||||
if (id != other.id) return false
|
||||
if (proto != other.proto) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = id.hashCode()
|
||||
result = 31 * result + proto.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,64 +5,27 @@
|
||||
|
||||
package org.whispersystems.signalservice.api.storage
|
||||
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.whispersystems.signalservice.internal.storage.protos.CallLinkRecord
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* A record in storage service that represents a call link that was already created.
|
||||
* Wrapper around a [CallLinkRecord] to pair it with a [StorageId].
|
||||
*/
|
||||
class SignalCallLinkRecord(
|
||||
data class SignalCallLinkRecord(
|
||||
override val id: StorageId,
|
||||
override val proto: CallLinkRecord
|
||||
) : SignalRecord<CallLinkRecord> {
|
||||
|
||||
val rootKey: ByteArray = proto.rootKey.toByteArray()
|
||||
val adminPassKey: ByteArray = proto.adminPasskey.toByteArray()
|
||||
val deletionTimestamp: Long = proto.deletedAtTimestampMs
|
||||
|
||||
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()
|
||||
}
|
||||
companion object {
|
||||
fun newBuilder(serializedUnknowns: ByteArray?): CallLinkRecord.Builder {
|
||||
return serializedUnknowns?.let { builderFromUnknowns(it) } ?: 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()
|
||||
}
|
||||
private fun builderFromUnknowns(serializedUnknowns: ByteArray): CallLinkRecord.Builder {
|
||||
return try {
|
||||
CallLinkRecord.ADAPTER.decode(serializedUnknowns).newBuilder()
|
||||
} catch (e: IOException) {
|
||||
CallLinkRecord.Builder()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,360 +0,0 @@
|
||||
package org.whispersystems.signalservice.api.storage;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.signal.core.util.ProtoUtil;
|
||||
import org.signal.libsignal.protocol.logging.Log;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
|
||||
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord.IdentityState;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
public final class SignalContactRecord implements SignalRecord<ContactRecord> {
|
||||
|
||||
private static final String TAG = SignalContactRecord.class.getSimpleName();
|
||||
|
||||
private final StorageId id;
|
||||
private final ContactRecord proto;
|
||||
private final boolean hasUnknownFields;
|
||||
|
||||
private final Optional<ACI> aci;
|
||||
private final Optional<PNI> pni;
|
||||
private final Optional<String> e164;
|
||||
private final Optional<String> profileGivenName;
|
||||
private final Optional<String> profileFamilyName;
|
||||
private final Optional<String> systemGivenName;
|
||||
private final Optional<String> systemFamilyName;
|
||||
private final Optional<String> systemNickname;
|
||||
private final Optional<byte[]> profileKey;
|
||||
private final Optional<String> username;
|
||||
private final Optional<byte[]> identityKey;
|
||||
private final Optional<String> nicknameGivenName;
|
||||
private final Optional<String> nicknameFamilyName;
|
||||
private final Optional<String> note;
|
||||
|
||||
public SignalContactRecord(StorageId id, ContactRecord proto) {
|
||||
this.id = id;
|
||||
this.proto = proto;
|
||||
this.hasUnknownFields = ProtoUtil.hasUnknownFields(proto);
|
||||
this.aci = OptionalUtil.absentIfEmpty(proto.aci).map(ACI::parseOrNull).map(it -> it.isUnknown() ? null : it);
|
||||
this.pni = OptionalUtil.absentIfEmpty(proto.pni).map(PNI::parseOrNull).map(it -> it.isUnknown() ? null : it);
|
||||
this.e164 = OptionalUtil.absentIfEmpty(proto.e164);
|
||||
this.profileGivenName = OptionalUtil.absentIfEmpty(proto.givenName);
|
||||
this.profileFamilyName = OptionalUtil.absentIfEmpty(proto.familyName);
|
||||
this.systemGivenName = OptionalUtil.absentIfEmpty(proto.systemGivenName);
|
||||
this.systemFamilyName = OptionalUtil.absentIfEmpty(proto.systemFamilyName);
|
||||
this.systemNickname = OptionalUtil.absentIfEmpty(proto.systemNickname);
|
||||
this.profileKey = OptionalUtil.absentIfEmpty(proto.profileKey);
|
||||
this.username = OptionalUtil.absentIfEmpty(proto.username);
|
||||
this.identityKey = OptionalUtil.absentIfEmpty(proto.identityKey);
|
||||
this.nicknameGivenName = Optional.ofNullable(proto.nickname).flatMap(n -> OptionalUtil.absentIfEmpty(n.given));
|
||||
this.nicknameFamilyName = Optional.ofNullable(proto.nickname).flatMap(n -> OptionalUtil.absentIfEmpty(n.family));
|
||||
this.note = OptionalUtil.absentIfEmpty(proto.note);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactRecord getProto() {
|
||||
return proto;
|
||||
}
|
||||
|
||||
public boolean hasUnknownFields() {
|
||||
return hasUnknownFields;
|
||||
}
|
||||
|
||||
public byte[] serializeUnknownFields() {
|
||||
return hasUnknownFields ? proto.encode() : null;
|
||||
}
|
||||
|
||||
public Optional<ACI> getAci() {
|
||||
return aci;
|
||||
}
|
||||
|
||||
public Optional<PNI> getPni() {
|
||||
return pni;
|
||||
}
|
||||
|
||||
public Optional<? extends ServiceId> getServiceId() {
|
||||
if (aci.isPresent()) {
|
||||
return aci;
|
||||
} else if (pni.isPresent()) {
|
||||
return pni;
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<String> getNumber() {
|
||||
return e164;
|
||||
}
|
||||
|
||||
public Optional<String> getProfileGivenName() {
|
||||
return profileGivenName;
|
||||
}
|
||||
|
||||
public Optional<String> getProfileFamilyName() {
|
||||
return profileFamilyName;
|
||||
}
|
||||
|
||||
public Optional<String> getSystemGivenName() {
|
||||
return systemGivenName;
|
||||
}
|
||||
|
||||
public Optional<String> getSystemFamilyName() {
|
||||
return systemFamilyName;
|
||||
}
|
||||
|
||||
public Optional<String> getSystemNickname() {
|
||||
return systemNickname;
|
||||
}
|
||||
|
||||
public Optional<String> getNicknameGivenName() {
|
||||
return nicknameGivenName;
|
||||
}
|
||||
|
||||
public Optional<String> getNicknameFamilyName() {
|
||||
return nicknameFamilyName;
|
||||
}
|
||||
|
||||
public Optional<String> getNote() {
|
||||
return note;
|
||||
}
|
||||
|
||||
public Optional<byte[]> getProfileKey() {
|
||||
return profileKey;
|
||||
}
|
||||
|
||||
public Optional<String> getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public Optional<byte[]> getIdentityKey() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
public IdentityState getIdentityState() {
|
||||
return proto.identityState;
|
||||
}
|
||||
|
||||
public boolean isBlocked() {
|
||||
return proto.blocked;
|
||||
}
|
||||
|
||||
public boolean isProfileSharingEnabled() {
|
||||
return proto.whitelisted;
|
||||
}
|
||||
|
||||
public boolean isArchived() {
|
||||
return proto.archived;
|
||||
}
|
||||
|
||||
public boolean isForcedUnread() {
|
||||
return proto.markedUnread;
|
||||
}
|
||||
|
||||
public long getMuteUntil() {
|
||||
return proto.mutedUntilTimestamp;
|
||||
}
|
||||
|
||||
public boolean shouldHideStory() {
|
||||
return proto.hideStory;
|
||||
}
|
||||
|
||||
public long getUnregisteredTimestamp() {
|
||||
return proto.unregisteredAtTimestamp;
|
||||
}
|
||||
|
||||
public boolean isHidden() {
|
||||
return proto.hidden;
|
||||
}
|
||||
|
||||
public boolean isPniSignatureVerified() {
|
||||
return proto.pniSignatureVerified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the same record, but stripped of the PNI field. Only used while PNP is in development.
|
||||
*/
|
||||
public SignalContactRecord withoutPni() {
|
||||
return new SignalContactRecord(id, proto.newBuilder().pni("").build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
SignalContactRecord that = (SignalContactRecord) o;
|
||||
return id.equals(that.id) &&
|
||||
proto.equals(that.proto);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, proto);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final StorageId id;
|
||||
private final ContactRecord.Builder builder;
|
||||
|
||||
public Builder(byte[] rawId, @Nullable ACI aci, byte[] serializedUnknowns) {
|
||||
this.id = StorageId.forContact(rawId);
|
||||
|
||||
if (serializedUnknowns != null) {
|
||||
this.builder = parseUnknowns(serializedUnknowns);
|
||||
} else {
|
||||
this.builder = new ContactRecord.Builder();
|
||||
}
|
||||
|
||||
builder.aci(aci == null ? "" : aci.toString());
|
||||
}
|
||||
|
||||
public Builder setE164(String e164) {
|
||||
builder.e164(e164 == null ? "" : e164);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPni(PNI pni) {
|
||||
builder.pni(pni == null ? "" : pni.toStringWithoutPrefix());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setProfileGivenName(String givenName) {
|
||||
builder.givenName(givenName == null ? "" : givenName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setProfileFamilyName(String familyName) {
|
||||
builder.familyName(familyName == null ? "" : familyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSystemGivenName(String givenName) {
|
||||
builder.systemGivenName(givenName == null ? "" : givenName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSystemFamilyName(String familyName) {
|
||||
builder.systemFamilyName(familyName == null ? "" : familyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSystemNickname(String nickname) {
|
||||
builder.systemNickname(nickname == null ? "" : nickname);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setProfileKey(byte[] profileKey) {
|
||||
builder.profileKey(profileKey == null ? ByteString.EMPTY : ByteString.of(profileKey));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setUsername(String username) {
|
||||
builder.username(username == null ? "" : username);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setIdentityKey(byte[] identityKey) {
|
||||
builder.identityKey(identityKey == null ? ByteString.EMPTY : ByteString.of(identityKey));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setIdentityState(IdentityState identityState) {
|
||||
builder.identityState(identityState == null ? IdentityState.DEFAULT : identityState);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setBlocked(boolean blocked) {
|
||||
builder.blocked(blocked);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setProfileSharingEnabled(boolean profileSharingEnabled) {
|
||||
builder.whitelisted(profileSharingEnabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setArchived(boolean archived) {
|
||||
builder.archived(archived);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setForcedUnread(boolean forcedUnread) {
|
||||
builder.markedUnread(forcedUnread);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMuteUntil(long muteUntil) {
|
||||
builder.mutedUntilTimestamp(muteUntil);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setHideStory(boolean hideStory) {
|
||||
builder.hideStory(hideStory);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setUnregisteredTimestamp(long timestamp) {
|
||||
builder.unregisteredAtTimestamp(timestamp);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setHidden(boolean hidden) {
|
||||
builder.hidden(hidden);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPniSignatureVerified(boolean verified) {
|
||||
builder.pniSignatureVerified(verified);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNicknameGivenName(String nicknameGivenName) {
|
||||
ContactRecord.Name.Builder name = builder.nickname == null ? new ContactRecord.Name.Builder() : builder.nickname.newBuilder();
|
||||
name.given(nicknameGivenName);
|
||||
builder.nickname(name.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNicknameFamilyName(String nicknameFamilyName) {
|
||||
ContactRecord.Name.Builder name = builder.nickname == null ? new ContactRecord.Name.Builder() : builder.nickname.newBuilder();
|
||||
name.family(nicknameFamilyName);
|
||||
builder.nickname(name.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNote(String note) {
|
||||
builder.note(note == null ? "" : note);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static ContactRecord.Builder parseUnknowns(byte[] serializedUnknowns) {
|
||||
try {
|
||||
return ContactRecord.ADAPTER.decode(serializedUnknowns).newBuilder();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to combine unknown fields!", e);
|
||||
return new ContactRecord.Builder();
|
||||
}
|
||||
}
|
||||
|
||||
public SignalContactRecord build() {
|
||||
return new SignalContactRecord(id, builder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.whispersystems.signalservice.api.storage
|
||||
|
||||
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Wrapper around a [ContactRecord] to pair it with a [StorageId].
|
||||
*/
|
||||
data class SignalContactRecord(
|
||||
override val id: StorageId,
|
||||
override val proto: ContactRecord
|
||||
) : SignalRecord<ContactRecord> {
|
||||
|
||||
companion object {
|
||||
fun newBuilder(serializedUnknowns: ByteArray?): ContactRecord.Builder {
|
||||
return serializedUnknowns?.let { builderFromUnknowns(it) } ?: ContactRecord.Builder()
|
||||
}
|
||||
|
||||
private fun builderFromUnknowns(serializedUnknowns: ByteArray): ContactRecord.Builder {
|
||||
return try {
|
||||
ContactRecord.ADAPTER.decode(serializedUnknowns).newBuilder()
|
||||
} catch (e: IOException) {
|
||||
ContactRecord.Builder()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
package org.whispersystems.signalservice.api.storage;
|
||||
|
||||
import org.signal.core.util.ProtoUtil;
|
||||
import org.signal.libsignal.protocol.logging.Log;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.GroupV1Record;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
public final class SignalGroupV1Record implements SignalRecord<GroupV1Record> {
|
||||
|
||||
private static final String TAG = SignalGroupV1Record.class.getSimpleName();
|
||||
|
||||
private final StorageId id;
|
||||
private final GroupV1Record proto;
|
||||
private final byte[] groupId;
|
||||
private final boolean hasUnknownFields;
|
||||
|
||||
public SignalGroupV1Record(StorageId id, GroupV1Record proto) {
|
||||
this.id = id;
|
||||
this.proto = proto;
|
||||
this.groupId = proto.id.toByteArray();
|
||||
this.hasUnknownFields = ProtoUtil.hasUnknownFields(proto);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override public GroupV1Record getProto() {
|
||||
return proto;
|
||||
}
|
||||
|
||||
public boolean hasUnknownFields() {
|
||||
return hasUnknownFields;
|
||||
}
|
||||
|
||||
public byte[] serializeUnknownFields() {
|
||||
return hasUnknownFields ? proto.encode() : null;
|
||||
}
|
||||
|
||||
public byte[] getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public boolean isBlocked() {
|
||||
return proto.blocked;
|
||||
}
|
||||
|
||||
public boolean isProfileSharingEnabled() {
|
||||
return proto.whitelisted;
|
||||
}
|
||||
|
||||
public boolean isArchived() {
|
||||
return proto.archived;
|
||||
}
|
||||
|
||||
public boolean isForcedUnread() {
|
||||
return proto.markedUnread;
|
||||
}
|
||||
|
||||
public long getMuteUntil() {
|
||||
return proto.mutedUntilTimestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
SignalGroupV1Record that = (SignalGroupV1Record) o;
|
||||
return id.equals(that.id) &&
|
||||
proto.equals(that.proto);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, proto);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final StorageId id;
|
||||
private final GroupV1Record.Builder builder;
|
||||
|
||||
public Builder(byte[] rawId, byte[] groupId, byte[] serializedUnknowns) {
|
||||
this.id = StorageId.forGroupV1(rawId);
|
||||
|
||||
if (serializedUnknowns != null) {
|
||||
this.builder = parseUnknowns(serializedUnknowns);
|
||||
} else {
|
||||
this.builder = new GroupV1Record.Builder();
|
||||
}
|
||||
|
||||
builder.id(ByteString.of(groupId));
|
||||
}
|
||||
|
||||
public Builder setBlocked(boolean blocked) {
|
||||
builder.blocked(blocked);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setProfileSharingEnabled(boolean profileSharingEnabled) {
|
||||
builder.whitelisted(profileSharingEnabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setArchived(boolean archived) {
|
||||
builder.archived(archived);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setForcedUnread(boolean forcedUnread) {
|
||||
builder.markedUnread(forcedUnread);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMuteUntil(long muteUntil) {
|
||||
builder.mutedUntilTimestamp(muteUntil);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static GroupV1Record.Builder parseUnknowns(byte[] serializedUnknowns) {
|
||||
try {
|
||||
return GroupV1Record.ADAPTER.decode(serializedUnknowns).newBuilder();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to combine unknown fields!", e);
|
||||
return new GroupV1Record.Builder();
|
||||
}
|
||||
}
|
||||
|
||||
public SignalGroupV1Record build() {
|
||||
return new SignalGroupV1Record(id, builder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.whispersystems.signalservice.api.storage
|
||||
|
||||
import org.whispersystems.signalservice.internal.storage.protos.GroupV1Record
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Wrapper around a [GroupV1Record] to pair it with a [StorageId].
|
||||
*/
|
||||
data class SignalGroupV1Record(
|
||||
override val id: StorageId,
|
||||
override val proto: GroupV1Record
|
||||
) : SignalRecord<GroupV1Record> {
|
||||
|
||||
companion object {
|
||||
fun newBuilder(serializedUnknowns: ByteArray?): GroupV1Record.Builder {
|
||||
return serializedUnknowns?.let { builderFromUnknowns(it) } ?: GroupV1Record.Builder()
|
||||
}
|
||||
|
||||
private fun builderFromUnknowns(serializedUnknowns: ByteArray): GroupV1Record.Builder {
|
||||
return try {
|
||||
GroupV1Record.ADAPTER.decode(serializedUnknowns).newBuilder()
|
||||
} catch (e: IOException) {
|
||||
GroupV1Record.Builder()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
package org.whispersystems.signalservice.api.storage;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.signal.core.util.ProtoUtil;
|
||||
import org.signal.libsignal.protocol.logging.Log;
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
public final class SignalGroupV2Record implements SignalRecord<GroupV2Record> {
|
||||
|
||||
private static final String TAG = SignalGroupV2Record.class.getSimpleName();
|
||||
|
||||
private final StorageId id;
|
||||
private final GroupV2Record proto;
|
||||
private final byte[] masterKey;
|
||||
private final boolean hasUnknownFields;
|
||||
|
||||
public SignalGroupV2Record(StorageId id, GroupV2Record proto) {
|
||||
this.id = id;
|
||||
this.proto = proto;
|
||||
this.hasUnknownFields = ProtoUtil.hasUnknownFields(proto);
|
||||
this.masterKey = proto.masterKey.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override public GroupV2Record getProto() {
|
||||
return proto;
|
||||
}
|
||||
|
||||
public boolean hasUnknownFields() {
|
||||
return hasUnknownFields;
|
||||
}
|
||||
|
||||
public byte[] serializeUnknownFields() {
|
||||
return hasUnknownFields ? proto.encode() : null;
|
||||
}
|
||||
|
||||
public byte[] getMasterKeyBytes() {
|
||||
return masterKey;
|
||||
}
|
||||
|
||||
public GroupMasterKey getMasterKeyOrThrow() {
|
||||
try {
|
||||
return new GroupMasterKey(masterKey);
|
||||
} catch (InvalidInputException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isBlocked() {
|
||||
return proto.blocked;
|
||||
}
|
||||
|
||||
public boolean isProfileSharingEnabled() {
|
||||
return proto.whitelisted;
|
||||
}
|
||||
|
||||
public boolean isArchived() {
|
||||
return proto.archived;
|
||||
}
|
||||
|
||||
public boolean isForcedUnread() {
|
||||
return proto.markedUnread;
|
||||
}
|
||||
|
||||
public long getMuteUntil() {
|
||||
return proto.mutedUntilTimestamp;
|
||||
}
|
||||
|
||||
public boolean notifyForMentionsWhenMuted() {
|
||||
return !proto.dontNotifyForMentionsIfMuted;
|
||||
}
|
||||
|
||||
public boolean shouldHideStory() {
|
||||
return proto.hideStory;
|
||||
}
|
||||
|
||||
public GroupV2Record.StorySendMode getStorySendMode() {
|
||||
return proto.storySendMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
SignalGroupV2Record that = (SignalGroupV2Record) o;
|
||||
return id.equals(that.id) &&
|
||||
proto.equals(that.proto);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, proto);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final StorageId id;
|
||||
private final GroupV2Record.Builder builder;
|
||||
|
||||
public Builder(byte[] rawId, GroupMasterKey masterKey, byte[] serializedUnknowns) {
|
||||
this(rawId, masterKey.serialize(), serializedUnknowns);
|
||||
}
|
||||
|
||||
public Builder(byte[] rawId, byte[] masterKey, byte[] serializedUnknowns) {
|
||||
this.id = StorageId.forGroupV2(rawId);
|
||||
|
||||
if (serializedUnknowns != null) {
|
||||
this.builder = parseUnknowns(serializedUnknowns);
|
||||
} else {
|
||||
this.builder = new GroupV2Record.Builder();
|
||||
}
|
||||
|
||||
builder.masterKey(ByteString.of(masterKey));
|
||||
}
|
||||
|
||||
public Builder setBlocked(boolean blocked) {
|
||||
builder.blocked(blocked);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setProfileSharingEnabled(boolean profileSharingEnabled) {
|
||||
builder.whitelisted(profileSharingEnabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setArchived(boolean archived) {
|
||||
builder.archived(archived);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setForcedUnread(boolean forcedUnread) {
|
||||
builder.markedUnread(forcedUnread);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMuteUntil(long muteUntil) {
|
||||
builder.mutedUntilTimestamp(muteUntil);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNotifyForMentionsWhenMuted(boolean value) {
|
||||
builder.dontNotifyForMentionsIfMuted(!value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setHideStory(boolean hideStory) {
|
||||
builder.hideStory(hideStory);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setStorySendMode(GroupV2Record.StorySendMode storySendMode) {
|
||||
builder.storySendMode(storySendMode);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static GroupV2Record.Builder parseUnknowns(byte[] serializedUnknowns) {
|
||||
try {
|
||||
return GroupV2Record.ADAPTER.decode(serializedUnknowns).newBuilder();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to combine unknown fields!", e);
|
||||
return new GroupV2Record.Builder();
|
||||
}
|
||||
}
|
||||
|
||||
public SignalGroupV2Record build() {
|
||||
return new SignalGroupV2Record(id, builder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.whispersystems.signalservice.api.storage
|
||||
|
||||
import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Wrapper around a [GroupV2Record] to pair it with a [StorageId].
|
||||
*/
|
||||
data class SignalGroupV2Record(
|
||||
override val id: StorageId,
|
||||
override val proto: GroupV2Record
|
||||
) : SignalRecord<GroupV2Record> {
|
||||
|
||||
companion object {
|
||||
fun newBuilder(serializedUnknowns: ByteArray?): GroupV2Record.Builder {
|
||||
return serializedUnknowns?.let { builderFromUnknowns(it) } ?: GroupV2Record.Builder()
|
||||
}
|
||||
|
||||
private fun builderFromUnknowns(serializedUnknowns: ByteArray): GroupV2Record.Builder {
|
||||
return try {
|
||||
GroupV2Record.ADAPTER.decode(serializedUnknowns).newBuilder()
|
||||
} catch (e: IOException) {
|
||||
GroupV2Record.Builder()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,20 @@
|
||||
package org.whispersystems.signalservice.api.storage
|
||||
|
||||
import com.squareup.wire.Message
|
||||
import org.signal.core.util.hasUnknownFields
|
||||
import kotlin.reflect.KVisibility
|
||||
import kotlin.reflect.full.memberProperties
|
||||
|
||||
/**
|
||||
* Pairs a storage record with its id. Also contains some useful common methods.
|
||||
*/
|
||||
interface SignalRecord<E> {
|
||||
val id: StorageId
|
||||
val proto: E
|
||||
|
||||
val serializedUnknowns: ByteArray?
|
||||
get() = (proto as Message<*, *>).takeIf { it.hasUnknownFields() }?.encode()
|
||||
|
||||
fun describeDiff(other: SignalRecord<*>): String {
|
||||
if (this::class != other::class) {
|
||||
return "Classes are different!"
|
||||
|
||||
@@ -64,33 +64,12 @@ object SignalStorageModels {
|
||||
|
||||
@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())
|
||||
val encryptedRecord = SignalStorageCipher.encrypt(itemKey, record.proto.encode())
|
||||
|
||||
return StorageItem.Builder()
|
||||
.key(record.id.raw.toByteString())
|
||||
.value_(encryptedRecord.toByteString())
|
||||
.build()
|
||||
}
|
||||
|
||||
private class InvalidStorageWriteError : Error()
|
||||
}
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
package org.whispersystems.signalservice.api.storage;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.signal.core.util.ProtoUtil;
|
||||
import org.signal.libsignal.protocol.logging.Log;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.StoryDistributionListRecord;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
public class SignalStoryDistributionListRecord implements SignalRecord<StoryDistributionListRecord> {
|
||||
|
||||
private static final String TAG = SignalStoryDistributionListRecord.class.getSimpleName();
|
||||
|
||||
private final StorageId id;
|
||||
private final StoryDistributionListRecord proto;
|
||||
private final boolean hasUnknownFields;
|
||||
private final List<SignalServiceAddress> recipients;
|
||||
|
||||
public SignalStoryDistributionListRecord(StorageId id, StoryDistributionListRecord proto) {
|
||||
this.id = id;
|
||||
this.proto = proto;
|
||||
this.hasUnknownFields = ProtoUtil.hasUnknownFields(proto);
|
||||
this.recipients = proto.recipientServiceIds
|
||||
.stream()
|
||||
.map(ServiceId::parseOrNull)
|
||||
.filter(Objects::nonNull)
|
||||
.map(SignalServiceAddress::new)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoryDistributionListRecord getProto() {
|
||||
return proto;
|
||||
}
|
||||
|
||||
public byte[] serializeUnknownFields() {
|
||||
return hasUnknownFields ? proto.encode() : null;
|
||||
}
|
||||
|
||||
public byte[] getIdentifier() {
|
||||
return proto.identifier.toByteArray();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return proto.name;
|
||||
}
|
||||
|
||||
public List<SignalServiceAddress> getRecipients() {
|
||||
return recipients;
|
||||
}
|
||||
|
||||
public long getDeletedAtTimestamp() {
|
||||
return proto.deletedAtTimestamp;
|
||||
}
|
||||
|
||||
public boolean allowsReplies() {
|
||||
return proto.allowsReplies;
|
||||
}
|
||||
|
||||
public boolean isBlockList() {
|
||||
return proto.isBlockList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
SignalStoryDistributionListRecord that = (SignalStoryDistributionListRecord) o;
|
||||
return id.equals(that.id) &&
|
||||
proto.equals(that.proto);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, proto);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final StorageId id;
|
||||
private final StoryDistributionListRecord.Builder builder;
|
||||
|
||||
public Builder(byte[] rawId, byte[] serializedUnknowns) {
|
||||
this.id = StorageId.forStoryDistributionList(rawId);
|
||||
|
||||
if (serializedUnknowns != null) {
|
||||
this.builder = parseUnknowns(serializedUnknowns);
|
||||
} else {
|
||||
this.builder = new StoryDistributionListRecord.Builder();
|
||||
}
|
||||
}
|
||||
|
||||
public Builder setIdentifier(byte[] identifier) {
|
||||
builder.identifier(ByteString.of(identifier));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setName(String name) {
|
||||
builder.name(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRecipients(List<SignalServiceAddress> recipients) {
|
||||
builder.recipientServiceIds = recipients.stream()
|
||||
.map(SignalServiceAddress::getIdentifier)
|
||||
.collect(Collectors.toList());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDeletedAtTimestamp(long deletedAtTimestamp) {
|
||||
builder.deletedAtTimestamp(deletedAtTimestamp);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAllowsReplies(boolean allowsReplies) {
|
||||
builder.allowsReplies(allowsReplies);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setIsBlockList(boolean isBlockList) {
|
||||
builder.isBlockList(isBlockList);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SignalStoryDistributionListRecord build() {
|
||||
return new SignalStoryDistributionListRecord(id, builder.build());
|
||||
}
|
||||
|
||||
private static StoryDistributionListRecord.Builder parseUnknowns(byte[] serializedUnknowns) {
|
||||
try {
|
||||
return StoryDistributionListRecord.ADAPTER.decode(serializedUnknowns).newBuilder();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to combine unknown fields!", e);
|
||||
return new StoryDistributionListRecord.Builder();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.whispersystems.signalservice.api.storage
|
||||
|
||||
import org.whispersystems.signalservice.internal.storage.protos.StoryDistributionListRecord
|
||||
import java.io.IOException
|
||||
|
||||
data class SignalStoryDistributionListRecord(
|
||||
override val id: StorageId,
|
||||
override val proto: StoryDistributionListRecord
|
||||
) : SignalRecord<StoryDistributionListRecord> {
|
||||
|
||||
companion object {
|
||||
fun newBuilder(serializedUnknowns: ByteArray?): StoryDistributionListRecord.Builder {
|
||||
return serializedUnknowns?.let { builderFromUnknowns(it) } ?: StoryDistributionListRecord.Builder()
|
||||
}
|
||||
|
||||
private fun builderFromUnknowns(serializedUnknowns: ByteArray): StoryDistributionListRecord.Builder {
|
||||
return try {
|
||||
StoryDistributionListRecord.ADAPTER.decode(serializedUnknowns).newBuilder()
|
||||
} catch (e: IOException) {
|
||||
StoryDistributionListRecord.Builder()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,9 @@ import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A copy of {@link ManifestRecord.Identifier} that allows us to more easily store unknown types with their integer constant.
|
||||
*/
|
||||
public class StorageId {
|
||||
private final int type;
|
||||
private final byte[] raw;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.storage
|
||||
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.internal.storage.protos.StoryDistributionListRecord
|
||||
|
||||
val StoryDistributionListRecord.recipientServiceAddresses: List<SignalServiceAddress>
|
||||
get() {
|
||||
return this.recipientServiceIds
|
||||
.mapNotNull { ServiceId.parseOrNull(it) }
|
||||
.map { SignalServiceAddress(it) }
|
||||
}
|
||||
@@ -46,6 +46,10 @@ public final class UuidUtil {
|
||||
return new UUID(high, low);
|
||||
}
|
||||
|
||||
public static UUID parseOrThrow(ByteString bytes) {
|
||||
return parseOrNull(bytes.toByteArray());
|
||||
}
|
||||
|
||||
public static boolean isUuid(String uuid) {
|
||||
return uuid != null && UUID_PATTERN.matcher(uuid).matches();
|
||||
}
|
||||
@@ -83,6 +87,10 @@ public final class UuidUtil {
|
||||
return byteArray != null && byteArray.length == 16 ? parseOrThrow(byteArray) : null;
|
||||
}
|
||||
|
||||
public static UUID parseOrNull(ByteString byteString) {
|
||||
return parseOrNull(byteString.toByteArray());
|
||||
}
|
||||
|
||||
public static List<UUID> fromByteStrings(Collection<ByteString> byteStringCollection) {
|
||||
ArrayList<UUID> result = new ArrayList<>(byteStringCollection.size());
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package org.whispersystems.signalservice.api.storage;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
@@ -14,27 +16,33 @@ public class SignalContactRecordTest {
|
||||
|
||||
@Test
|
||||
public void contacts_with_same_identity_key_contents_are_equal() {
|
||||
byte[] profileKey = new byte[32];
|
||||
byte[] profileKeyCopy = profileKey.clone();
|
||||
byte[] identityKey = new byte[32];
|
||||
byte[] identityKeyCopy = identityKey.clone();
|
||||
|
||||
SignalContactRecord a = contactBuilder(1, ACI_A, E164_A, "a").setIdentityKey(profileKey).build();
|
||||
SignalContactRecord b = contactBuilder(1, ACI_A, E164_A, "a").setIdentityKey(profileKeyCopy).build();
|
||||
ContactRecord contactA = contactBuilder(ACI_A, E164_A, "a").identityKey(ByteString.of(identityKey)).build();
|
||||
ContactRecord contactB = contactBuilder(ACI_A, E164_A, "a").identityKey(ByteString.of(identityKeyCopy)).build();
|
||||
|
||||
assertEquals(a, b);
|
||||
assertEquals(a.hashCode(), b.hashCode());
|
||||
SignalContactRecord signalContactA = new SignalContactRecord(StorageId.forContact(byteArray(1)), contactA);
|
||||
SignalContactRecord signalContactB = new SignalContactRecord(StorageId.forContact(byteArray(1)), contactB);
|
||||
|
||||
assertEquals(signalContactA, signalContactB);
|
||||
assertEquals(signalContactA.hashCode(), signalContactB.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contacts_with_different_identity_key_contents_are_not_equal() {
|
||||
byte[] profileKey = new byte[32];
|
||||
byte[] profileKeyCopy = profileKey.clone();
|
||||
profileKeyCopy[0] = 1;
|
||||
byte[] identityKey = new byte[32];
|
||||
byte[] identityKeyCopy = identityKey.clone();
|
||||
identityKeyCopy[0] = 1;
|
||||
|
||||
SignalContactRecord a = contactBuilder(1, ACI_A, E164_A, "a").setIdentityKey(profileKey).build();
|
||||
SignalContactRecord b = contactBuilder(1, ACI_A, E164_A, "a").setIdentityKey(profileKeyCopy).build();
|
||||
ContactRecord contactA = contactBuilder(ACI_A, E164_A, "a").identityKey(ByteString.of(identityKey)).build();
|
||||
ContactRecord contactB = contactBuilder(ACI_A, E164_A, "a").identityKey(ByteString.of(identityKeyCopy)).build();
|
||||
|
||||
assertNotEquals(a, b);
|
||||
assertNotEquals(a.hashCode(), b.hashCode());
|
||||
SignalContactRecord signalContactA = new SignalContactRecord(StorageId.forContact(byteArray(1)), contactA);
|
||||
SignalContactRecord signalContactB = new SignalContactRecord(StorageId.forContact(byteArray(1)), contactB);
|
||||
|
||||
assertNotEquals(signalContactA, signalContactB);
|
||||
assertNotEquals(signalContactA.hashCode(), signalContactB.hashCode());
|
||||
}
|
||||
|
||||
private static byte[] byteArray(int a) {
|
||||
@@ -46,13 +54,9 @@ public class SignalContactRecordTest {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static SignalContactRecord.Builder contactBuilder(int key,
|
||||
ACI serviceId,
|
||||
String e164,
|
||||
String givenName)
|
||||
{
|
||||
return new SignalContactRecord.Builder(byteArray(key), serviceId, null)
|
||||
.setE164(e164)
|
||||
.setProfileGivenName(givenName);
|
||||
private static ContactRecord.Builder contactBuilder(ACI serviceId, String e164, String givenName) {
|
||||
return new ContactRecord.Builder()
|
||||
.e164(e164)
|
||||
.givenName(givenName);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user