mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-15 20:42:00 +00:00
Add storage support for the AccountRecord.
This commit is contained in:
@@ -403,6 +403,18 @@ public class SignalServiceAccountManager {
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<SignalStorageManifest> getStorageManifest(StorageKey storageKey) throws IOException {
|
||||
try {
|
||||
String authToken = this.pushServiceSocket.getStorageAuth();
|
||||
StorageManifest storageManifest = this.pushServiceSocket.getStorageManifest(authToken);
|
||||
|
||||
return Optional.of(SignalStorageModels.remoteToLocalStorageManifest(storageManifest, storageKey));
|
||||
} catch (InvalidKeyException | NotFoundException e) {
|
||||
Log.w(TAG, "Error while fetching manifest.", e);
|
||||
return Optional.absent();
|
||||
}
|
||||
}
|
||||
|
||||
public long getStorageManifestVersion() throws IOException {
|
||||
try {
|
||||
String authToken = this.pushServiceSocket.getStorageAuth();
|
||||
@@ -431,6 +443,10 @@ public class SignalServiceAccountManager {
|
||||
}
|
||||
|
||||
public List<SignalStorageRecord> readStorageRecords(StorageKey storageKey, List<StorageId> storageKeys) throws IOException, InvalidKeyException {
|
||||
if (storageKeys.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<SignalStorageRecord> result = new ArrayList<>();
|
||||
ReadOperation.Builder operation = ReadOperation.newBuilder();
|
||||
Map<ByteString, Integer> typeMap = new HashMap<>();
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
package org.whispersystems.signalservice.api.storage;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class SignalAccountRecord implements SignalRecord {
|
||||
|
||||
private final StorageId id;
|
||||
private final AccountRecord proto;
|
||||
|
||||
private final Optional<String> givenName;
|
||||
private final Optional<String> familyName;
|
||||
private final Optional<String> avatarUrlPath;
|
||||
private final Optional<byte[]> profileKey;
|
||||
|
||||
public SignalAccountRecord(StorageId id, AccountRecord proto) {
|
||||
this.id = id;
|
||||
this.proto = proto;
|
||||
|
||||
this.givenName = OptionalUtil.absentIfEmpty(proto.getGivenName());
|
||||
this.familyName = OptionalUtil.absentIfEmpty(proto.getFamilyName());
|
||||
this.profileKey = OptionalUtil.absentIfEmpty(proto.getProfileKey());
|
||||
this.avatarUrlPath = OptionalUtil.absentIfEmpty(proto.getAvatarUrlPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Optional<String> getGivenName() {
|
||||
return givenName;
|
||||
}
|
||||
|
||||
public Optional<String> getFamilyName() {
|
||||
return familyName;
|
||||
}
|
||||
|
||||
public Optional<byte[]> getProfileKey() {
|
||||
return profileKey;
|
||||
}
|
||||
|
||||
public Optional<String> getAvatarUrlPath() {
|
||||
return avatarUrlPath;
|
||||
}
|
||||
|
||||
public boolean isNoteToSelfArchived() {
|
||||
return proto.getNoteToSelfArchived();
|
||||
}
|
||||
|
||||
public boolean isReadReceiptsEnabled() {
|
||||
return proto.getReadReceipts();
|
||||
}
|
||||
|
||||
public boolean isTypingIndicatorsEnabled() {
|
||||
return proto.getTypingIndicators();
|
||||
}
|
||||
|
||||
public boolean isSealedSenderIndicatorsEnabled() {
|
||||
return proto.getSealedSenderIndicators();
|
||||
}
|
||||
|
||||
public boolean isLinkPreviewsEnabled() {
|
||||
return proto.getLinkPreviews();
|
||||
}
|
||||
|
||||
AccountRecord toProto() {
|
||||
return proto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
SignalAccountRecord that = (SignalAccountRecord) 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 AccountRecord.Builder builder;
|
||||
|
||||
public Builder(byte[] rawId) {
|
||||
this.id = StorageId.forAccount(rawId);
|
||||
this.builder = AccountRecord.newBuilder();
|
||||
}
|
||||
|
||||
public Builder setGivenName(String givenName) {
|
||||
builder.setGivenName(givenName == null ? "" : givenName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFamilyName(String familyName) {
|
||||
builder.setFamilyName(familyName == null ? "" : familyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setProfileKey(byte[] profileKey) {
|
||||
builder.setProfileKey(profileKey == null ? ByteString.EMPTY : ByteString.copyFrom(profileKey));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAvatarUrlPath(String urlPath) {
|
||||
builder.setAvatarUrlPath(urlPath == null ? "" : urlPath);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNoteToSelfArchived(boolean archived) {
|
||||
builder.setNoteToSelfArchived(archived);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setReadReceiptsEnabled(boolean enabled) {
|
||||
builder.setReadReceipts(enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTypingIndicatorsEnabled(boolean enabled) {
|
||||
builder.setTypingIndicators(enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSealedSenderIndicatorsEnabled(boolean enabled) {
|
||||
builder.setSealedSenderIndicators(enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLinkPreviewsEnabled(boolean enabled) {
|
||||
builder.setLinkPreviews(enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SignalAccountRecord build() {
|
||||
return new SignalAccountRecord(id, builder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ public final class SignalContactRecord implements SignalRecord {
|
||||
private final Optional<String> username;
|
||||
private final Optional<byte[]> identityKey;
|
||||
|
||||
private SignalContactRecord(StorageId id, ContactRecord proto) {
|
||||
public SignalContactRecord(StorageId id, ContactRecord proto) {
|
||||
this.id = id;
|
||||
this.proto = proto;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ public final class SignalGroupV1Record implements SignalRecord {
|
||||
private final GroupV1Record proto;
|
||||
private final byte[] groupId;
|
||||
|
||||
private SignalGroupV1Record(StorageId id, GroupV1Record proto) {
|
||||
public SignalGroupV1Record(StorageId id, GroupV1Record proto) {
|
||||
this.id = id;
|
||||
this.proto = proto;
|
||||
this.groupId = proto.getId().toByteArray();
|
||||
|
||||
@@ -15,7 +15,7 @@ public final class SignalGroupV2Record implements SignalRecord {
|
||||
private final GroupV2Record proto;
|
||||
private final GroupMasterKey masterKey;
|
||||
|
||||
private SignalGroupV2Record(StorageId id, GroupV2Record proto) {
|
||||
public SignalGroupV2Record(StorageId id, GroupV2Record proto) {
|
||||
this.id = id;
|
||||
this.proto = proto;
|
||||
try {
|
||||
|
||||
@@ -1,14 +1,33 @@
|
||||
package org.whispersystems.signalservice.api.storage;
|
||||
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.StorageManifest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class SignalStorageManifest {
|
||||
private final long version;
|
||||
private final List<StorageId> storageIds;
|
||||
private final long version;
|
||||
private final List<StorageId> storageIds;
|
||||
private final Map<Integer, List<StorageId>> storageIdsByType;
|
||||
|
||||
public SignalStorageManifest(long version, List<StorageId> storageIds) {
|
||||
this.version = version;
|
||||
this.storageIds = storageIds;
|
||||
this.version = version;
|
||||
this.storageIds = storageIds;
|
||||
this.storageIdsByType = new HashMap<>();
|
||||
|
||||
for (StorageId id : storageIds) {
|
||||
List<StorageId> list = storageIdsByType.get(id.getType());
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
list.add(id);
|
||||
storageIdsByType.put(id.getType(), list);
|
||||
}
|
||||
}
|
||||
|
||||
public long getVersion() {
|
||||
@@ -18,4 +37,14 @@ public class SignalStorageManifest {
|
||||
public List<StorageId> getStorageIds() {
|
||||
return storageIds;
|
||||
}
|
||||
|
||||
public Optional<StorageId> getAccountStorageId() {
|
||||
List<StorageId> list = storageIdsByType.get(ManifestRecord.Identifier.Type.ACCOUNT_VALUE);
|
||||
|
||||
if (list.size() > 0) {
|
||||
return Optional.of(list.get(0));
|
||||
} else {
|
||||
return Optional.absent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,8 @@ package org.whispersystems.signalservice.api.storage;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.zkgroup.InvalidInputException;
|
||||
import org.signal.zkgroup.groups.GroupMasterKey;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.GroupV1Record;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.StorageItem;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.StorageManifest;
|
||||
@@ -37,13 +31,16 @@ public final class SignalStorageModels {
|
||||
byte[] key = item.getKey().toByteArray();
|
||||
byte[] rawRecord = SignalStorageCipher.decrypt(storageKey.deriveItemKey(key), item.getValue().toByteArray());
|
||||
StorageRecord record = StorageRecord.parseFrom(rawRecord);
|
||||
StorageId id = StorageId.forType(key, type);
|
||||
|
||||
if (record.hasContact() && type == ManifestRecord.Identifier.Type.CONTACT_VALUE) {
|
||||
return SignalStorageRecord.forContact(StorageId.forContact(key), remoteToLocalContactRecord(key, record.getContact()));
|
||||
return SignalStorageRecord.forContact(id, new SignalContactRecord(id, record.getContact()));
|
||||
} else if (record.hasGroupV1() && type == ManifestRecord.Identifier.Type.GROUPV1_VALUE) {
|
||||
return SignalStorageRecord.forGroupV1(StorageId.forGroupV1(key), remoteToLocalGroupV1Record(key, record.getGroupV1()));
|
||||
return SignalStorageRecord.forGroupV1(id, new SignalGroupV1Record(id, record.getGroupV1()));
|
||||
} else if (record.hasGroupV2() && type == ManifestRecord.Identifier.Type.GROUPV2_VALUE && record.getGroupV2().getMasterKey().size() == GroupMasterKey.SIZE) {
|
||||
return SignalStorageRecord.forGroupV2(StorageId.forGroupV2(key), remoteToLocalGroupV2Record(key, record.getGroupV2()));
|
||||
return SignalStorageRecord.forGroupV2(id, new SignalGroupV2Record(id, record.getGroupV2()));
|
||||
} else if (record.hasAccount() && type == ManifestRecord.Identifier.Type.ACCOUNT_VALUE) {
|
||||
return SignalStorageRecord.forAccount(id, new SignalAccountRecord(id, record.getAccount()));
|
||||
} else {
|
||||
return SignalStorageRecord.forUnknown(StorageId.forType(key, type));
|
||||
}
|
||||
@@ -58,6 +55,8 @@ public final class SignalStorageModels {
|
||||
builder.setGroupV1(record.getGroupV1().get().toProto());
|
||||
} else if (record.getGroupV2().isPresent()) {
|
||||
builder.setGroupV2(record.getGroupV2().get().toProto());
|
||||
} else if (record.getAccount().isPresent()) {
|
||||
builder.setAccount(record.getAccount().get().toProto());
|
||||
} else {
|
||||
throw new InvalidStorageWriteError();
|
||||
}
|
||||
@@ -72,42 +71,6 @@ public final class SignalStorageModels {
|
||||
.build();
|
||||
}
|
||||
|
||||
private static SignalContactRecord remoteToLocalContactRecord(byte[] key, ContactRecord contact) {
|
||||
SignalServiceAddress address = new SignalServiceAddress(UuidUtil.parseOrNull(contact.getServiceUuid()), contact.getServiceE164());
|
||||
|
||||
return new SignalContactRecord.Builder(key, address)
|
||||
.setBlocked(contact.getBlocked())
|
||||
.setProfileSharingEnabled(contact.getWhitelisted())
|
||||
.setProfileKey(contact.getProfileKey().toByteArray())
|
||||
.setGivenName(contact.getGivenName())
|
||||
.setFamilyName(contact.getFamilyName())
|
||||
.setUsername(contact.getUsername())
|
||||
.setIdentityKey(contact.getIdentityKey().toByteArray())
|
||||
.setIdentityState(contact.getIdentityState())
|
||||
.setArchived(contact.getArchived())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static SignalGroupV1Record remoteToLocalGroupV1Record(byte[] key, GroupV1Record groupV1) {
|
||||
return new SignalGroupV1Record.Builder(key, groupV1.getId().toByteArray())
|
||||
.setBlocked(groupV1.getBlocked())
|
||||
.setProfileSharingEnabled(groupV1.getWhitelisted())
|
||||
.setArchived(groupV1.getArchived())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static SignalGroupV2Record remoteToLocalGroupV2Record(byte[] key, GroupV2Record groupV2) {
|
||||
try {
|
||||
return new SignalGroupV2Record.Builder(key, new GroupMasterKey(groupV2.getMasterKey().toByteArray()))
|
||||
.setBlocked(groupV2.getBlocked())
|
||||
.setProfileSharingEnabled(groupV2.getWhitelisted())
|
||||
.setArchived(groupV2.getArchived())
|
||||
.build();
|
||||
} catch (InvalidInputException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
private static class InvalidStorageWriteError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.whispersystems.signalservice.api.storage;
|
||||
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -10,13 +11,14 @@ public class SignalStorageRecord implements SignalRecord {
|
||||
private final Optional<SignalContactRecord> contact;
|
||||
private final Optional<SignalGroupV1Record> groupV1;
|
||||
private final Optional<SignalGroupV2Record> groupV2;
|
||||
private final Optional<SignalAccountRecord> account;
|
||||
|
||||
public static SignalStorageRecord forContact(SignalContactRecord contact) {
|
||||
return forContact(contact.getId(), contact);
|
||||
}
|
||||
|
||||
public static SignalStorageRecord forContact(StorageId key, SignalContactRecord contact) {
|
||||
return new SignalStorageRecord(key, Optional.of(contact), Optional.<SignalGroupV1Record>absent(), Optional.<SignalGroupV2Record>absent());
|
||||
return new SignalStorageRecord(key, Optional.of(contact), Optional.<SignalGroupV1Record>absent(), Optional.<SignalGroupV2Record>absent(), Optional.<SignalAccountRecord>absent());
|
||||
}
|
||||
|
||||
public static SignalStorageRecord forGroupV1(SignalGroupV1Record groupV1) {
|
||||
@@ -24,7 +26,7 @@ public class SignalStorageRecord implements SignalRecord {
|
||||
}
|
||||
|
||||
public static SignalStorageRecord forGroupV1(StorageId key, SignalGroupV1Record groupV1) {
|
||||
return new SignalStorageRecord(key, Optional.<SignalContactRecord>absent(), Optional.of(groupV1), Optional.<SignalGroupV2Record>absent());
|
||||
return new SignalStorageRecord(key, Optional.<SignalContactRecord>absent(), Optional.of(groupV1), Optional.<SignalGroupV2Record>absent(), Optional.<SignalAccountRecord>absent());
|
||||
}
|
||||
|
||||
public static SignalStorageRecord forGroupV2(SignalGroupV2Record groupV2) {
|
||||
@@ -32,22 +34,32 @@ public class SignalStorageRecord implements SignalRecord {
|
||||
}
|
||||
|
||||
public static SignalStorageRecord forGroupV2(StorageId key, SignalGroupV2Record groupV2) {
|
||||
return new SignalStorageRecord(key, Optional.<SignalContactRecord>absent(), Optional.<SignalGroupV1Record>absent(), Optional.of(groupV2));
|
||||
return new SignalStorageRecord(key, Optional.<SignalContactRecord>absent(), Optional.<SignalGroupV1Record>absent(), Optional.of(groupV2), Optional.<SignalAccountRecord>absent());
|
||||
}
|
||||
|
||||
public static SignalStorageRecord forAccount(SignalAccountRecord account) {
|
||||
return forAccount(account.getId(), account);
|
||||
}
|
||||
|
||||
public static SignalStorageRecord forAccount(StorageId key, SignalAccountRecord account) {
|
||||
return new SignalStorageRecord(key, Optional.<SignalContactRecord>absent(), Optional.<SignalGroupV1Record>absent(), Optional.<SignalGroupV2Record>absent(), Optional.of(account));
|
||||
}
|
||||
|
||||
public static SignalStorageRecord forUnknown(StorageId key) {
|
||||
return new SignalStorageRecord(key,Optional.<SignalContactRecord>absent(), Optional.<SignalGroupV1Record>absent(), Optional.<SignalGroupV2Record>absent());
|
||||
return new SignalStorageRecord(key,Optional.<SignalContactRecord>absent(), Optional.<SignalGroupV1Record>absent(), Optional.<SignalGroupV2Record>absent(), Optional.<SignalAccountRecord>absent());
|
||||
}
|
||||
|
||||
private SignalStorageRecord(StorageId id,
|
||||
Optional<SignalContactRecord> contact,
|
||||
Optional<SignalGroupV1Record> groupV1,
|
||||
Optional<SignalGroupV2Record> groupV2)
|
||||
Optional<SignalGroupV2Record> groupV2,
|
||||
Optional<SignalAccountRecord> account)
|
||||
{
|
||||
this.id = id;
|
||||
this.contact = contact;
|
||||
this.groupV1 = groupV1;
|
||||
this.groupV2 = groupV2;
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -71,8 +83,12 @@ public class SignalStorageRecord implements SignalRecord {
|
||||
return groupV2;
|
||||
}
|
||||
|
||||
public Optional<SignalAccountRecord> getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public boolean isUnknown() {
|
||||
return !contact.isPresent() && !groupV1.isPresent();
|
||||
return !contact.isPresent() && !groupV1.isPresent() && !account.isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,6 +21,10 @@ public class StorageId {
|
||||
return new StorageId(ManifestRecord.Identifier.Type.GROUPV2_VALUE, raw);
|
||||
}
|
||||
|
||||
public static StorageId forAccount(byte[] raw) {
|
||||
return new StorageId(ManifestRecord.Identifier.Type.ACCOUNT_VALUE, raw);
|
||||
}
|
||||
|
||||
public static StorageId forType(byte[] raw, int type) {
|
||||
return new StorageId(type, raw);
|
||||
}
|
||||
|
||||
@@ -97,13 +97,13 @@ message GroupV2Record {
|
||||
}
|
||||
|
||||
message AccountRecord {
|
||||
message Config {
|
||||
bool readReceipts = 1;
|
||||
bool sealedSenderIndicators = 2;
|
||||
bool typingIndicators = 3;
|
||||
bool linkPreviews = 4;
|
||||
}
|
||||
|
||||
ContactRecord contact = 1;
|
||||
Config config = 2;
|
||||
bytes profileKey = 1;
|
||||
string givenName = 2;
|
||||
string familyName = 3;
|
||||
string avatarUrlPath = 4;
|
||||
bool noteToSelfArchived = 5;
|
||||
bool readReceipts = 6;
|
||||
bool sealedSenderIndicators = 7;
|
||||
bool typingIndicators = 8;
|
||||
bool linkPreviews = 9;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user