Use record classes

This commit is contained in:
AsamK 2021-10-24 22:26:12 +02:00
parent ce70a623c2
commit ce7aa580b6
66 changed files with 754 additions and 1877 deletions

View file

@ -15,6 +15,7 @@ repositories {
dependencies {
api("com.github.turasa:signal-service-java:2.15.3_unofficial_31")
api("com.fasterxml.jackson.core", "jackson-databind", "2.13.0")
implementation("com.google.protobuf:protobuf-javalite:3.10.0")
implementation("org.bouncycastle:bcprov-jdk15on:1.69")
implementation("org.slf4j:slf4j-api:1.7.32")

View file

@ -15,10 +15,7 @@ import java.util.Map;
import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
public class DeviceLinkInfo {
final String deviceIdentifier;
final ECPublicKey deviceKey;
public record DeviceLinkInfo(String deviceIdentifier, ECPublicKey deviceKey) {
public static DeviceLinkInfo parseDeviceLinkUri(URI linkUri) throws InvalidKeyException {
final var rawQuery = linkUri.getRawQuery();
@ -57,11 +54,6 @@ public class DeviceLinkInfo {
return map;
}
public DeviceLinkInfo(final String deviceIdentifier, final ECPublicKey deviceKey) {
this.deviceIdentifier = deviceIdentifier;
this.deviceKey = deviceKey;
}
public URI createDeviceLinkUri() {
final var deviceKeyString = Base64.getEncoder().encodeToString(deviceKey.serialize()).replace("=", "");
try {

View file

@ -1,55 +1,8 @@
package org.asamk.signal.manager;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class JsonStickerPack {
public record JsonStickerPack(String title, String author, JsonSticker cover, List<JsonSticker> stickers) {
@JsonProperty
public String title;
@JsonProperty
public String author;
@JsonProperty
public JsonSticker cover;
@JsonProperty
public List<JsonSticker> stickers;
// For deserialization
private JsonStickerPack() {
}
public JsonStickerPack(
final String title, final String author, final JsonSticker cover, final List<JsonSticker> stickers
) {
this.title = title;
this.author = author;
this.cover = cover;
this.stickers = stickers;
}
public static class JsonSticker {
@JsonProperty
public String emoji;
@JsonProperty
public String file;
@JsonProperty
public String contentType;
// For deserialization
private JsonSticker() {
}
public JsonSticker(final String emoji, final String file, final String contentType) {
this.emoji = emoji;
this.file = file;
this.contentType = contentType;
}
}
public record JsonSticker(String emoji, String file, String contentType) {}
}

View file

@ -57,11 +57,11 @@ public interface Manager extends Closeable {
) throws IOException, NotRegisteredException {
var pathConfig = PathConfig.createDefault(settingsPath);
if (!SignalAccount.userExists(pathConfig.getDataPath(), number)) {
if (!SignalAccount.userExists(pathConfig.dataPath(), number)) {
throw new NotRegisteredException();
}
var account = SignalAccount.load(pathConfig.getDataPath(), number, true, trustNewIdentity);
var account = SignalAccount.load(pathConfig.dataPath(), number, true, trustNewIdentity);
if (!account.isRegistered()) {
throw new NotRegisteredException();
@ -74,7 +74,7 @@ public interface Manager extends Closeable {
static List<String> getAllLocalNumbers(File settingsPath) {
var pathConfig = PathConfig.createDefault(settingsPath);
final var dataPath = pathConfig.getDataPath();
final var dataPath = pathConfig.dataPath();
final var files = dataPath.listFiles();
if (files == null) {

View file

@ -169,9 +169,9 @@ public class ManagerImpl implements Manager {
account.getSignalProtocolStore(),
executor,
sessionLock);
final var avatarStore = new AvatarStore(pathConfig.getAvatarsPath());
final var attachmentStore = new AttachmentStore(pathConfig.getAttachmentsPath());
final var stickerPackStore = new StickerPackStore(pathConfig.getStickerPacksPath());
final var avatarStore = new AvatarStore(pathConfig.avatarsPath());
final var attachmentStore = new AttachmentStore(pathConfig.attachmentsPath());
final var stickerPackStore = new StickerPackStore(pathConfig.stickerPacksPath());
this.attachmentHelper = new AttachmentHelper(dependencies, attachmentStore);
this.pinHelper = new PinHelper(dependencies.getKeyBackupService());
@ -426,7 +426,7 @@ public class ManagerImpl implements Manager {
public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException {
var info = DeviceLinkInfo.parseDeviceLinkUri(linkUri);
addDevice(info.deviceIdentifier, info.deviceKey);
addDevice(info.deviceIdentifier(), info.deviceKey());
}
private void addDevice(String deviceIdentifier, ECPublicKey deviceKey) throws IOException, InvalidKeyException {
@ -642,8 +642,8 @@ public class ManagerImpl implements Manager {
private void applyMessage(
final SignalServiceDataMessage.Builder messageBuilder, final Message message
) throws AttachmentInvalidException, IOException {
messageBuilder.withBody(message.getMessageText());
final var attachments = message.getAttachments();
messageBuilder.withBody(message.messageText());
final var attachments = message.attachments();
if (attachments != null) {
messageBuilder.withAttachments(attachmentHelper.uploadAttachments(attachments));
}

View file

@ -2,12 +2,9 @@ package org.asamk.signal.manager;
import java.io.File;
public class PathConfig {
private final File dataPath;
private final File attachmentsPath;
private final File avatarsPath;
private final File stickerPacksPath;
public record PathConfig(
File dataPath, File attachmentsPath, File avatarsPath, File stickerPacksPath
) {
public static PathConfig createDefault(final File settingsPath) {
return new PathConfig(new File(settingsPath, "data"),
@ -15,29 +12,4 @@ public class PathConfig {
new File(settingsPath, "avatars"),
new File(settingsPath, "stickers"));
}
private PathConfig(
final File dataPath, final File attachmentsPath, final File avatarsPath, final File stickerPacksPath
) {
this.dataPath = dataPath;
this.attachmentsPath = attachmentsPath;
this.avatarsPath = avatarsPath;
this.stickerPacksPath = stickerPacksPath;
}
public File getDataPath() {
return dataPath;
}
public File getAttachmentsPath() {
return attachmentsPath;
}
public File getAvatarsPath() {
return avatarsPath;
}
public File getStickerPacksPath() {
return stickerPacksPath;
}
}

View file

@ -95,8 +95,8 @@ public class ProvisioningManager {
logger.info("Received link information from {}, linking in progress ...", number);
if (SignalAccount.userExists(pathConfig.getDataPath(), number) && !canRelinkExistingAccount(number)) {
throw new UserAlreadyExists(number, SignalAccount.getFileName(pathConfig.getDataPath(), number));
if (SignalAccount.userExists(pathConfig.dataPath(), number) && !canRelinkExistingAccount(number)) {
throw new UserAlreadyExists(number, SignalAccount.getFileName(pathConfig.dataPath(), number));
}
var encryptedDeviceName = deviceName == null
@ -115,7 +115,7 @@ public class ProvisioningManager {
SignalAccount account = null;
try {
account = SignalAccount.createOrUpdateLinkedAccount(pathConfig.getDataPath(),
account = SignalAccount.createOrUpdateLinkedAccount(pathConfig.dataPath(),
number,
ret.getUuid(),
password,
@ -165,7 +165,7 @@ public class ProvisioningManager {
private boolean canRelinkExistingAccount(final String number) throws IOException {
final SignalAccount signalAccount;
try {
signalAccount = SignalAccount.load(pathConfig.getDataPath(), number, false, TrustNewIdentity.ON_FIRST_USE);
signalAccount = SignalAccount.load(pathConfig.dataPath(), number, false, TrustNewIdentity.ON_FIRST_USE);
} catch (IOException e) {
logger.debug("Account in use or failed to load.", e);
return false;

View file

@ -96,12 +96,12 @@ public class RegistrationManager implements Closeable {
var pathConfig = PathConfig.createDefault(settingsPath);
final var serviceConfiguration = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent);
if (!SignalAccount.userExists(pathConfig.getDataPath(), number)) {
if (!SignalAccount.userExists(pathConfig.dataPath(), number)) {
var identityKey = KeyUtils.generateIdentityKeyPair();
var registrationId = KeyHelper.generateRegistrationId(false);
var profileKey = KeyUtils.createProfileKey();
var account = SignalAccount.create(pathConfig.getDataPath(),
var account = SignalAccount.create(pathConfig.dataPath(),
number,
identityKey,
registrationId,
@ -111,7 +111,7 @@ public class RegistrationManager implements Closeable {
return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
}
var account = SignalAccount.load(pathConfig.getDataPath(), number, true, TrustNewIdentity.ON_FIRST_USE);
var account = SignalAccount.load(pathConfig.dataPath(), number, true, TrustNewIdentity.ON_FIRST_USE);
return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
}

View file

@ -1,38 +1,3 @@
package org.asamk.signal.manager.api;
public class Device {
private final long id;
private final String name;
private final long created;
private final long lastSeen;
private final boolean thisDevice;
public Device(long id, String name, long created, long lastSeen, final boolean thisDevice) {
this.id = id;
this.name = name;
this.created = created;
this.lastSeen = lastSeen;
this.thisDevice = thisDevice;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public long getCreated() {
return created;
}
public long getLastSeen() {
return lastSeen;
}
public boolean isThisDevice() {
return thisDevice;
}
}
public record Device(long id, String name, long created, long lastSeen, boolean isThisDevice) {}

View file

@ -7,116 +7,20 @@ import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import java.util.Set;
public class Group {
private final GroupId groupId;
private final String title;
private final String description;
private final GroupInviteLinkUrl groupInviteLinkUrl;
private final Set<RecipientAddress> members;
private final Set<RecipientAddress> pendingMembers;
private final Set<RecipientAddress> requestingMembers;
private final Set<RecipientAddress> adminMembers;
private final boolean isBlocked;
private final int messageExpirationTimer;
private final GroupPermission permissionAddMember;
private final GroupPermission permissionEditDetails;
private final GroupPermission permissionSendMessage;
private final boolean isMember;
private final boolean isAdmin;
public Group(
final GroupId groupId,
final String title,
final String description,
final GroupInviteLinkUrl groupInviteLinkUrl,
final Set<RecipientAddress> members,
final Set<RecipientAddress> pendingMembers,
final Set<RecipientAddress> requestingMembers,
final Set<RecipientAddress> adminMembers,
final boolean isBlocked,
final int messageExpirationTimer,
final GroupPermission permissionAddMember,
final GroupPermission permissionEditDetails,
final GroupPermission permissionSendMessage,
final boolean isMember,
final boolean isAdmin
) {
this.groupId = groupId;
this.title = title;
this.description = description;
this.groupInviteLinkUrl = groupInviteLinkUrl;
this.members = members;
this.pendingMembers = pendingMembers;
this.requestingMembers = requestingMembers;
this.adminMembers = adminMembers;
this.isBlocked = isBlocked;
this.messageExpirationTimer = messageExpirationTimer;
this.permissionAddMember = permissionAddMember;
this.permissionEditDetails = permissionEditDetails;
this.permissionSendMessage = permissionSendMessage;
this.isMember = isMember;
this.isAdmin = isAdmin;
}
public GroupId getGroupId() {
return groupId;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public GroupInviteLinkUrl getGroupInviteLinkUrl() {
return groupInviteLinkUrl;
}
public Set<RecipientAddress> getMembers() {
return members;
}
public Set<RecipientAddress> getPendingMembers() {
return pendingMembers;
}
public Set<RecipientAddress> getRequestingMembers() {
return requestingMembers;
}
public Set<RecipientAddress> getAdminMembers() {
return adminMembers;
}
public boolean isBlocked() {
return isBlocked;
}
public int getMessageExpirationTimer() {
return messageExpirationTimer;
}
public GroupPermission getPermissionAddMember() {
return permissionAddMember;
}
public GroupPermission getPermissionEditDetails() {
return permissionEditDetails;
}
public GroupPermission getPermissionSendMessage() {
return permissionSendMessage;
}
public boolean isMember() {
return isMember;
}
public boolean isAdmin() {
return isAdmin;
}
}
public record Group(
GroupId groupId,
String title,
String description,
GroupInviteLinkUrl groupInviteLinkUrl,
Set<RecipientAddress> members,
Set<RecipientAddress> pendingMembers,
Set<RecipientAddress> requestingMembers,
Set<RecipientAddress> adminMembers,
boolean isBlocked,
int messageExpirationTimer,
GroupPermission permissionAddMember,
GroupPermission permissionEditDetails,
GroupPermission permissionSendMessage,
boolean isMember,
boolean isAdmin
) {}

View file

@ -6,60 +6,16 @@ import org.whispersystems.libsignal.IdentityKey;
import java.util.Date;
public class Identity {
private final RecipientAddress recipient;
private final IdentityKey identityKey;
private final String safetyNumber;
private final byte[] scannableSafetyNumber;
private final TrustLevel trustLevel;
private final Date dateAdded;
public Identity(
final RecipientAddress recipient,
final IdentityKey identityKey,
final String safetyNumber,
final byte[] scannableSafetyNumber,
final TrustLevel trustLevel,
final Date dateAdded
) {
this.recipient = recipient;
this.identityKey = identityKey;
this.safetyNumber = safetyNumber;
this.scannableSafetyNumber = scannableSafetyNumber;
this.trustLevel = trustLevel;
this.dateAdded = dateAdded;
}
public RecipientAddress getRecipient() {
return recipient;
}
public IdentityKey getIdentityKey() {
return this.identityKey;
}
public TrustLevel getTrustLevel() {
return this.trustLevel;
}
boolean isTrusted() {
return trustLevel == TrustLevel.TRUSTED_UNVERIFIED || trustLevel == TrustLevel.TRUSTED_VERIFIED;
}
public Date getDateAdded() {
return this.dateAdded;
}
public record Identity(
RecipientAddress recipient,
IdentityKey identityKey,
String safetyNumber,
byte[] scannableSafetyNumber,
TrustLevel trustLevel,
Date dateAdded
) {
public byte[] getFingerprint() {
return identityKey.getPublicKey().serialize();
}
public String getSafetyNumber() {
return safetyNumber;
}
public byte[] getScannableSafetyNumber() {
return scannableSafetyNumber;
}
}

View file

@ -2,21 +2,4 @@ package org.asamk.signal.manager.api;
import java.util.List;
public class Message {
private final String messageText;
private final List<String> attachments;
public Message(final String messageText, final List<String> attachments) {
this.messageText = messageText;
this.attachments = attachments;
}
public String getMessageText() {
return messageText;
}
public List<String> getAttachments() {
return attachments;
}
}
public record Message(String messageText, List<String> attachments) {}

View file

@ -4,23 +4,4 @@ import org.whispersystems.signalservice.api.messages.SendMessageResult;
import java.util.List;
public class SendGroupMessageResults {
private final long timestamp;
private final List<SendMessageResult> results;
public SendGroupMessageResults(
final long timestamp, final List<SendMessageResult> results
) {
this.timestamp = timestamp;
this.results = results;
}
public long getTimestamp() {
return timestamp;
}
public List<SendMessageResult> getResults() {
return results;
}
}
public record SendGroupMessageResults(long timestamp, List<SendMessageResult> results) {}

View file

@ -5,23 +5,4 @@ import org.whispersystems.signalservice.api.messages.SendMessageResult;
import java.util.List;
import java.util.Map;
public class SendMessageResults {
private final long timestamp;
private final Map<RecipientIdentifier, List<SendMessageResult>> results;
public SendMessageResults(
final long timestamp, final Map<RecipientIdentifier, List<SendMessageResult>> results
) {
this.timestamp = timestamp;
this.results = results;
}
public long getTimestamp() {
return timestamp;
}
public Map<RecipientIdentifier, List<SendMessageResult>> getResults() {
return results;
}
}
public record SendMessageResults(long timestamp, Map<RecipientIdentifier, List<SendMessageResult>> results) {}

View file

@ -62,29 +62,9 @@ public class ConfigurationStore {
return new Storage(readReceipts, unidentifiedDeliveryIndicators, typingIndicators, linkPreviews);
}
public static final class Storage {
public Boolean readReceipts;
public Boolean unidentifiedDeliveryIndicators;
public Boolean typingIndicators;
public Boolean linkPreviews;
// For deserialization
private Storage() {
}
public Storage(
final Boolean readReceipts,
final Boolean unidentifiedDeliveryIndicators,
final Boolean typingIndicators,
final Boolean linkPreviews
) {
this.readReceipts = readReceipts;
this.unidentifiedDeliveryIndicators = unidentifiedDeliveryIndicators;
this.typingIndicators = typingIndicators;
this.linkPreviews = linkPreviews;
}
}
public record Storage(
Boolean readReceipts, Boolean unidentifiedDeliveryIndicators, Boolean typingIndicators, Boolean linkPreviews
) {}
public interface Saver {

View file

@ -262,7 +262,7 @@ public class GroupStore {
g1.blocked,
g1.archived,
g1.members.stream()
.map(m -> new Storage.GroupV1.Member(m.getId(), null, null))
.map(m -> new Storage.GroupV1.Member(m.id(), null, null))
.collect(Collectors.toList()));
}
@ -274,91 +274,22 @@ public class GroupStore {
}).collect(Collectors.toList()));
}
public static class Storage {
public record Storage(@JsonDeserialize(using = GroupsDeserializer.class) List<Object> groups) {
@JsonDeserialize(using = GroupsDeserializer.class)
public List<Storage.Group> groups;
private record GroupV1(
String groupId,
String expectedV2Id,
String name,
String color,
int messageExpirationTime,
boolean blocked,
boolean archived,
@JsonDeserialize(using = MembersDeserializer.class) @JsonSerialize(using = MembersSerializer.class) List<Member> members
) {
// For deserialization
public Storage() {
}
private record Member(Long recipientId, String uuid, String number) {}
public Storage(final List<Storage.Group> groups) {
this.groups = groups;
}
private abstract static class Group {
}
private static class GroupV1 extends Group {
public String groupId;
public String expectedV2Id;
public String name;
public String color;
public int messageExpirationTime;
public boolean blocked;
public boolean archived;
@JsonDeserialize(using = MembersDeserializer.class)
@JsonSerialize(using = MembersSerializer.class)
public List<Member> members;
// For deserialization
public GroupV1() {
}
public GroupV1(
final String groupId,
final String expectedV2Id,
final String name,
final String color,
final int messageExpirationTime,
final boolean blocked,
final boolean archived,
final List<Member> members
) {
this.groupId = groupId;
this.expectedV2Id = expectedV2Id;
this.name = name;
this.color = color;
this.messageExpirationTime = messageExpirationTime;
this.blocked = blocked;
this.archived = archived;
this.members = members;
}
private static final class Member {
public Long recipientId;
public String uuid;
public String number;
Member(Long recipientId, final String uuid, final String number) {
this.recipientId = recipientId;
this.uuid = uuid;
this.number = number;
}
}
private static final class JsonRecipientAddress {
public String uuid;
public String number;
// For deserialization
public JsonRecipientAddress() {
}
JsonRecipientAddress(final String uuid, final String number) {
this.uuid = uuid;
this.number = number;
}
}
private record JsonRecipientAddress(String uuid, String number) {}
private static class MembersSerializer extends JsonSerializer<List<Member>> {
@ -366,7 +297,7 @@ public class GroupStore {
public void serialize(
final List<Member> value, final JsonGenerator jgen, final SerializerProvider provider
) throws IOException {
jgen.writeStartArray(value.size());
jgen.writeStartArray(null, value.size());
for (var address : value) {
if (address.recipientId != null) {
jgen.writeNumber(address.recipientId);
@ -404,39 +335,19 @@ public class GroupStore {
}
}
private static class GroupV2 extends Group {
public String groupId;
public String masterKey;
public boolean blocked;
public boolean permissionDenied;
// For deserialization
private GroupV2() {
}
public GroupV2(
final String groupId, final String masterKey, final boolean blocked, final boolean permissionDenied
) {
this.groupId = groupId;
this.masterKey = masterKey;
this.blocked = blocked;
this.permissionDenied = permissionDenied;
}
}
private record GroupV2(String groupId, String masterKey, boolean blocked, boolean permissionDenied) {}
}
private static class GroupsDeserializer extends JsonDeserializer<List<Storage.Group>> {
private static class GroupsDeserializer extends JsonDeserializer<List<Object>> {
@Override
public List<Storage.Group> deserialize(
public List<Object> deserialize(
JsonParser jsonParser, DeserializationContext deserializationContext
) throws IOException {
var groups = new ArrayList<Storage.Group>();
var groups = new ArrayList<>();
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
for (var n : node) {
Storage.Group g;
Object g;
if (n.hasNonNull("masterKey")) {
// a v2 group
g = jsonParser.getCodec().treeToValue(n, Storage.GroupV2.class);

View file

@ -185,7 +185,7 @@ public class IdentityKeyStore implements org.whispersystems.libsignal.state.Iden
} catch (IOException e) {
throw new AssertionError("Failed to create identities path", e);
}
return new File(identitiesPath, String.valueOf(recipientId.getId()));
return new File(identitiesPath, String.valueOf(recipientId.id()));
}
private IdentityInfo loadIdentityLocked(final RecipientId recipientId) {
@ -203,9 +203,9 @@ public class IdentityKeyStore implements org.whispersystems.libsignal.state.Iden
try (var inputStream = new FileInputStream(file)) {
var storage = objectMapper.readValue(inputStream, IdentityStorage.class);
var id = new IdentityKey(Base64.getDecoder().decode(storage.getIdentityKey()));
var trustLevel = TrustLevel.fromInt(storage.getTrustLevel());
var added = new Date(storage.getAddedTimestamp());
var id = new IdentityKey(Base64.getDecoder().decode(storage.identityKey()));
var trustLevel = TrustLevel.fromInt(storage.trustLevel());
var added = new Date(storage.addedTimestamp());
final var identityInfo = new IdentityInfo(recipientId, id, trustLevel, added);
cachedIdentities.put(recipientId, identityInfo);
@ -251,32 +251,5 @@ public class IdentityKeyStore implements org.whispersystems.libsignal.state.Iden
}
}
private static final class IdentityStorage {
private String identityKey;
private int trustLevel;
private long addedTimestamp;
// For deserialization
private IdentityStorage() {
}
private IdentityStorage(final String identityKey, final int trustLevel, final long addedTimestamp) {
this.identityKey = identityKey;
this.trustLevel = trustLevel;
this.addedTimestamp = addedTimestamp;
}
public String getIdentityKey() {
return identityKey;
}
public int getTrustLevel() {
return trustLevel;
}
public long getAddedTimestamp() {
return addedTimestamp;
}
}
private record IdentityStorage(String identityKey, int trustLevel, long addedTimestamp) {}
}

View file

@ -76,7 +76,7 @@ public class MessageCache {
return messageCachePath;
}
var sender = String.valueOf(recipientId.getId());
var sender = String.valueOf(recipientId.id());
return new File(messageCachePath, sender.replace("/", "_"));
}

View file

@ -1,38 +1,8 @@
package org.asamk.signal.manager.storage.recipients;
public class RecipientId {
private final long id;
RecipientId(final long id) {
this.id = id;
}
public record RecipientId(long id) {
public static RecipientId of(long id) {
return new RecipientId(id);
}
public long getId() {
return id;
}
@Override
public String toString() {
return "RecipientId{" + "id=" + id + '}';
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final RecipientId that = (RecipientId) o;
return id == that.id;
}
@Override
public int hashCode() {
return (int) (id ^ (id >>> 32));
}
}

View file

@ -453,7 +453,7 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
.stream()
.map(Enum::name)
.collect(Collectors.toSet()));
return new Storage.Recipient(pair.getKey().getId(),
return new Storage.Recipient(pair.getKey().id(),
recipient.getAddress().getNumber().orElse(null),
recipient.getAddress().getUuid().map(UUID::toString).orElse(null),
recipient.getProfileKey() == null
@ -479,115 +479,32 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
}
}
private static class Storage {
private record Storage(List<Recipient> recipients, long lastId) {
public List<Recipient> recipients;
private record Recipient(
long id,
String number,
String uuid,
String profileKey,
String profileKeyCredential,
Storage.Recipient.Contact contact,
Storage.Recipient.Profile profile
) {
public long lastId;
private record Contact(
String name, String color, int messageExpirationTime, boolean blocked, boolean archived
) {}
// For deserialization
private Storage() {
}
public Storage(final List<Recipient> recipients, final long lastId) {
this.recipients = recipients;
this.lastId = lastId;
}
private static class Recipient {
public long id;
public String number;
public String uuid;
public String profileKey;
public String profileKeyCredential;
public Contact contact;
public Profile profile;
// For deserialization
private Recipient() {
}
public Recipient(
final long id,
final String number,
final String uuid,
final String profileKey,
final String profileKeyCredential,
final Contact contact,
final Profile profile
) {
this.id = id;
this.number = number;
this.uuid = uuid;
this.profileKey = profileKey;
this.profileKeyCredential = profileKeyCredential;
this.contact = contact;
this.profile = profile;
}
private static class Contact {
public String name;
public String color;
public int messageExpirationTime;
public boolean blocked;
public boolean archived;
// For deserialization
public Contact() {
}
public Contact(
final String name,
final String color,
final int messageExpirationTime,
final boolean blocked,
final boolean archived
) {
this.name = name;
this.color = color;
this.messageExpirationTime = messageExpirationTime;
this.blocked = blocked;
this.archived = archived;
}
}
private static class Profile {
public long lastUpdateTimestamp;
public String givenName;
public String familyName;
public String about;
public String aboutEmoji;
public String avatarUrlPath;
public String unidentifiedAccessMode;
public Set<String> capabilities;
// For deserialization
private Profile() {
}
public Profile(
final long lastUpdateTimestamp,
final String givenName,
final String familyName,
final String about,
final String aboutEmoji,
final String avatarUrlPath,
final String unidentifiedAccessMode,
final Set<String> capabilities
) {
this.lastUpdateTimestamp = lastUpdateTimestamp;
this.givenName = givenName;
this.familyName = familyName;
this.about = about;
this.aboutEmoji = aboutEmoji;
this.avatarUrlPath = avatarUrlPath;
this.unidentifiedAccessMode = unidentifiedAccessMode;
this.capabilities = capabilities;
}
}
private record Profile(
long lastUpdateTimestamp,
String givenName,
String familyName,
String about,
String aboutEmoji,
String avatarUrlPath,
String unidentifiedAccessMode,
Set<String> capabilities
) {}
}
}

View file

@ -103,7 +103,7 @@ public class SenderKeyRecordStore implements org.whispersystems.libsignal.groups
continue;
}
final var newKey = new Key(recipientId, key.getDeviceId(), key.distributionId);
final var newKey = new Key(recipientId, key.deviceId(), key.distributionId);
final var senderKeyRecord = loadSenderKeyLocked(newKey);
if (senderKeyRecord != null) {
continue;
@ -126,7 +126,7 @@ public class SenderKeyRecordStore implements org.whispersystems.libsignal.groups
}
private List<Key> getKeysLocked(RecipientId recipientId) {
final var files = senderKeysPath.listFiles((_file, s) -> s.startsWith(recipientId.getId() + "_"));
final var files = senderKeysPath.listFiles((_file, s) -> s.startsWith(recipientId.id() + "_"));
if (files == null) {
return List.of();
}
@ -152,7 +152,7 @@ public class SenderKeyRecordStore implements org.whispersystems.libsignal.groups
throw new AssertionError("Failed to create sender keys path", e);
}
return new File(senderKeysPath,
key.getRecipientId().getId() + "_" + key.getDeviceId() + "_" + key.distributionId.toString());
key.recipientId().id() + "_" + key.deviceId() + "_" + key.distributionId.toString());
}
private SenderKeyRecord loadSenderKeyLocked(final Key key) {
@ -212,50 +212,7 @@ public class SenderKeyRecordStore implements org.whispersystems.libsignal.groups
}
}
private static final class Key {
private record Key(RecipientId recipientId, int deviceId, UUID distributionId) {
private final RecipientId recipientId;
private final int deviceId;
private final UUID distributionId;
public Key(
final RecipientId recipientId, final int deviceId, final UUID distributionId
) {
this.recipientId = recipientId;
this.deviceId = deviceId;
this.distributionId = distributionId;
}
public RecipientId getRecipientId() {
return recipientId;
}
public int getDeviceId() {
return deviceId;
}
public UUID getDistributionId() {
return distributionId;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Key key = (Key) o;
if (deviceId != key.deviceId) return false;
if (!recipientId.equals(key.recipientId)) return false;
return distributionId.equals(key.distributionId);
}
@Override
public int hashCode() {
int result = recipientId.hashCode();
result = 31 * result + deviceId;
result = 31 * result + distributionId.hashCode();
return result;
}
}
}

View file

@ -87,8 +87,8 @@ public class SenderKeySharedStore {
synchronized (sharedSenderKeys) {
return sharedSenderKeys.get(distributionId)
.stream()
.map(k -> new SignalProtocolAddress(addressResolver.resolveRecipientAddress(k.getRecipientId())
.getIdentifier(), k.getDeviceId()))
.map(k -> new SignalProtocolAddress(addressResolver.resolveRecipientAddress(k.recipientId())
.getIdentifier(), k.deviceId()))
.collect(Collectors.toSet());
}
}
@ -146,7 +146,7 @@ public class SenderKeySharedStore {
sharedSenderKeys.put(distributionId, new HashSet<>(entries) {
{
entries.removeIf(e -> e.getRecipientId().equals(recipientId));
removeIf(e -> e.recipientId().equals(recipientId));
}
});
}
@ -163,7 +163,7 @@ public class SenderKeySharedStore {
entries.stream()
.map(e -> e.recipientId.equals(toBeMergedRecipientId) ? new SenderKeySharedEntry(
recipientId,
e.getDeviceId()) : e)
e.deviceId()) : e)
.collect(Collectors.toSet()));
}
saveLocked();
@ -181,8 +181,8 @@ public class SenderKeySharedStore {
var storage = new Storage(sharedSenderKeys.entrySet().stream().flatMap(pair -> {
final var sharedWith = pair.getValue();
return sharedWith.stream()
.map(entry -> new Storage.SharedSenderKey(entry.getRecipientId().getId(),
entry.getDeviceId(),
.map(entry -> new Storage.SharedSenderKey(entry.recipientId().id(),
entry.deviceId(),
pair.getKey().asUuid().toString()));
}).collect(Collectors.toList()));
@ -199,72 +199,10 @@ public class SenderKeySharedStore {
}
}
private static class Storage {
private record Storage(List<SharedSenderKey> sharedSenderKeys) {
public List<SharedSenderKey> sharedSenderKeys;
// For deserialization
private Storage() {
}
public Storage(final List<SharedSenderKey> sharedSenderKeys) {
this.sharedSenderKeys = sharedSenderKeys;
}
private static class SharedSenderKey {
public long recipientId;
public int deviceId;
public String distributionId;
// For deserialization
private SharedSenderKey() {
}
public SharedSenderKey(final long recipientId, final int deviceId, final String distributionId) {
this.recipientId = recipientId;
this.deviceId = deviceId;
this.distributionId = distributionId;
}
}
private record SharedSenderKey(long recipientId, int deviceId, String distributionId) {}
}
private static final class SenderKeySharedEntry {
private final RecipientId recipientId;
private final int deviceId;
public SenderKeySharedEntry(
final RecipientId recipientId, final int deviceId
) {
this.recipientId = recipientId;
this.deviceId = deviceId;
}
public RecipientId getRecipientId() {
return recipientId;
}
public int getDeviceId() {
return deviceId;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final SenderKeySharedEntry that = (SenderKeySharedEntry) o;
if (deviceId != that.deviceId) return false;
return recipientId.equals(that.recipientId);
}
@Override
public int hashCode() {
int result = recipientId.hashCode();
result = 31 * result + deviceId;
return result;
}
}
private record SenderKeySharedEntry(RecipientId recipientId, int deviceId) {}
}

View file

@ -88,8 +88,8 @@ public class SessionStore implements SignalServiceSessionStore {
synchronized (cachedSessions) {
return getKeysLocked(recipientId).stream()
// get all sessions for recipient except main device session
.filter(key -> key.getDeviceId() != 1 && key.getRecipientId().equals(recipientId))
.map(Key::getDeviceId)
.filter(key -> key.deviceId() != 1 && key.recipientId().equals(recipientId))
.map(Key::deviceId)
.collect(Collectors.toList());
}
}
@ -155,7 +155,7 @@ public class SessionStore implements SignalServiceSessionStore {
.stream()
.flatMap(recipientId -> getKeysLocked(recipientId).stream())
.filter(key -> isActive(this.loadSessionLocked(key)))
.map(key -> new SignalProtocolAddress(recipientIdToNameMap.get(key.recipientId), key.getDeviceId()))
.map(key -> new SignalProtocolAddress(recipientIdToNameMap.get(key.recipientId), key.deviceId()))
.collect(Collectors.toSet());
}
}
@ -197,7 +197,7 @@ public class SessionStore implements SignalServiceSessionStore {
if (session == null) {
continue;
}
final var newKey = new Key(recipientId, key.getDeviceId());
final var newKey = new Key(recipientId, key.deviceId());
storeSessionLocked(newKey, session);
}
}
@ -217,7 +217,7 @@ public class SessionStore implements SignalServiceSessionStore {
}
private List<Key> getKeysLocked(RecipientId recipientId) {
final var files = sessionsPath.listFiles((_file, s) -> s.startsWith(recipientId.getId() + "_"));
final var files = sessionsPath.listFiles((_file, s) -> s.startsWith(recipientId.id() + "_"));
if (files == null) {
return List.of();
}
@ -249,7 +249,7 @@ public class SessionStore implements SignalServiceSessionStore {
} catch (IOException e) {
throw new AssertionError("Failed to create sessions path", e);
}
return new File(sessionsPath, key.getRecipientId().getId() + "_" + key.getDeviceId());
return new File(sessionsPath, key.recipientId().id() + "_" + key.deviceId());
}
private SessionRecord loadSessionLocked(final Key key) {
@ -324,40 +324,5 @@ public class SessionStore implements SignalServiceSessionStore {
&& record.getSessionVersion() == CiphertextMessage.CURRENT_VERSION;
}
private static final class Key {
private final RecipientId recipientId;
private final int deviceId;
public Key(final RecipientId recipientId, final int deviceId) {
this.recipientId = recipientId;
this.deviceId = deviceId;
}
public RecipientId getRecipientId() {
return recipientId;
}
public int getDeviceId() {
return deviceId;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final var key = (Key) o;
if (deviceId != key.deviceId) return false;
return recipientId.equals(key.recipientId);
}
@Override
public int hashCode() {
int result = recipientId.hashCode();
result = 31 * result + deviceId;
return result;
}
}
private record Key(RecipientId recipientId, int deviceId) {}
}

View file

@ -65,33 +65,10 @@ public class StickerStore {
.collect(Collectors.toList()));
}
public static class Storage {
public record Storage(List<Storage.Sticker> stickers) {
public List<Storage.Sticker> stickers;
private record Sticker(String packId, String packKey, boolean installed) {
// For deserialization
private Storage() {
}
public Storage(final List<Sticker> stickers) {
this.stickers = stickers;
}
private static class Sticker {
public String packId;
public String packKey;
public boolean installed;
// For deserialization
private Sticker() {
}
public Sticker(final String packId, final String packKey, final boolean installed) {
this.packId = packId;
this.packKey = packKey;
this.installed = installed;
}
}
}

View file

@ -35,60 +35,59 @@ public class StickerUtils {
var pack = parseStickerPack(rootPath, zip);
if (pack.stickers == null) {
if (pack.stickers() == null) {
throw new StickerPackInvalidException("Must set a 'stickers' field.");
}
if (pack.stickers.isEmpty()) {
if (pack.stickers().isEmpty()) {
throw new StickerPackInvalidException("Must include stickers.");
}
var stickers = new ArrayList<SignalServiceStickerManifestUpload.StickerInfo>(pack.stickers.size());
for (var sticker : pack.stickers) {
if (sticker.file == null) {
var stickers = new ArrayList<SignalServiceStickerManifestUpload.StickerInfo>(pack.stickers().size());
for (var sticker : pack.stickers()) {
if (sticker.file() == null) {
throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
}
Pair<InputStream, Long> data;
try {
data = getInputStreamAndLength(rootPath, zip, sticker.file);
data = getInputStreamAndLength(rootPath, zip, sticker.file());
} catch (IOException ignored) {
throw new StickerPackInvalidException("Could not find find " + sticker.file);
throw new StickerPackInvalidException("Could not find find " + sticker.file());
}
var contentType = sticker.contentType != null && !sticker.contentType.isEmpty()
? sticker.contentType
: getContentType(rootPath, zip, sticker.file);
var contentType = sticker.contentType() != null && !sticker.contentType().isEmpty()
? sticker.contentType()
: getContentType(rootPath, zip, sticker.file());
var stickerInfo = new SignalServiceStickerManifestUpload.StickerInfo(data.first(),
data.second(),
Optional.fromNullable(sticker.emoji).or(""),
Optional.fromNullable(sticker.emoji()).or(""),
contentType);
stickers.add(stickerInfo);
}
SignalServiceStickerManifestUpload.StickerInfo cover = null;
if (pack.cover != null) {
if (pack.cover.file == null) {
if (pack.cover() != null) {
if (pack.cover().file() == null) {
throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
}
Pair<InputStream, Long> data;
try {
data = getInputStreamAndLength(rootPath, zip, pack.cover.file);
data = getInputStreamAndLength(rootPath, zip, pack.cover().file());
} catch (IOException ignored) {
throw new StickerPackInvalidException("Could not find find " + pack.cover.file);
throw new StickerPackInvalidException("Could not find find " + pack.cover().file());
}
var contentType = pack.cover.contentType != null && !pack.cover.contentType.isEmpty()
? pack.cover.contentType
: getContentType(rootPath, zip, pack.cover.file);
var contentType = pack.cover().contentType() != null && !pack.cover().contentType().isEmpty() ? pack.cover()
.contentType() : getContentType(rootPath, zip, pack.cover().file());
cover = new SignalServiceStickerManifestUpload.StickerInfo(data.first(),
data.second(),
Optional.fromNullable(pack.cover.emoji).or(""),
Optional.fromNullable(pack.cover().emoji()).or(""),
contentType);
}
return new SignalServiceStickerManifestUpload(pack.title, pack.author, cover, stickers);
return new SignalServiceStickerManifestUpload(pack.title(), pack.author(), cover, stickers);
}
private static JsonStickerPack parseStickerPack(String rootPath, ZipFile zip) throws IOException {