From b94c1e50e62946a4d774a4c53ce70858145a4422 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 10 Sep 2020 14:20:16 +0200 Subject: [PATCH] Cache profiles for 24h before retrieving them again --- .../org/asamk/signal/manager/Manager.java | 14 +++ .../asamk/signal/storage/SignalAccount.java | 17 ++++ .../signal/storage/profiles/ProfileStore.java | 90 +++++++++++++++++++ .../profiles}/SignalProfile.java | 18 +++- .../storage/profiles/SignalProfileEntry.java | 30 +++++++ .../storage/protocol/RecipientStore.java | 3 - 6 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java rename src/main/java/org/asamk/signal/{manager => storage/profiles}/SignalProfile.java (69%) create mode 100644 src/main/java/org/asamk/signal/storage/profiles/SignalProfileEntry.java diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 31f2d9d6..d16aabe6 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -22,6 +22,8 @@ import org.asamk.signal.storage.SignalAccount; import org.asamk.signal.storage.contacts.ContactInfo; import org.asamk.signal.storage.groups.GroupInfo; import org.asamk.signal.storage.groups.JsonGroupStore; +import org.asamk.signal.storage.profiles.SignalProfile; +import org.asamk.signal.storage.profiles.SignalProfileEntry; import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.Util; @@ -442,6 +444,18 @@ public class Manager implements Closeable { } private SignalProfile getRecipientProfile(SignalServiceAddress address, Optional unidentifiedAccess, ProfileKey profileKey) throws IOException { + SignalProfileEntry profileEntry = account.getProfileStore().getProfile(address); + long now = new Date().getTime(); + // Profiles are cache for 24h before retrieving them again + if (profileEntry == null || profileEntry.getProfile() == null || now - profileEntry.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000) { + SignalProfile profile = retrieveRecipientProfile(address, unidentifiedAccess, profileKey); + profileEntry = new SignalProfileEntry(profileKey, now, profile); + account.getProfileStore().updateProfile(address, profileEntry); + } + return profileEntry.getProfile(); + } + + private SignalProfile retrieveRecipientProfile(SignalServiceAddress address, Optional unidentifiedAccess, ProfileKey profileKey) throws IOException { final SignalServiceProfile encryptedProfile = getEncryptedRecipientProfile(address, unidentifiedAccess); File avatarFile = null; diff --git a/src/main/java/org/asamk/signal/storage/SignalAccount.java b/src/main/java/org/asamk/signal/storage/SignalAccount.java index d0638e41..d9b37825 100644 --- a/src/main/java/org/asamk/signal/storage/SignalAccount.java +++ b/src/main/java/org/asamk/signal/storage/SignalAccount.java @@ -14,6 +14,7 @@ import org.asamk.signal.storage.contacts.ContactInfo; import org.asamk.signal.storage.contacts.JsonContactsStore; import org.asamk.signal.storage.groups.GroupInfo; import org.asamk.signal.storage.groups.JsonGroupStore; +import org.asamk.signal.storage.profiles.ProfileStore; import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; import org.asamk.signal.storage.protocol.JsonSignalProtocolStore; import org.asamk.signal.storage.protocol.RecipientStore; @@ -67,6 +68,7 @@ public class SignalAccount implements Closeable { private JsonGroupStore groupStore; private JsonContactsStore contactStore; private RecipientStore recipientStore; + private ProfileStore profileStore; private SignalAccount(final FileChannel fileChannel, final FileLock lock) { this.fileChannel = fileChannel; @@ -109,6 +111,7 @@ public class SignalAccount implements Closeable { account.groupStore = new JsonGroupStore(); account.contactStore = new JsonContactsStore(); account.recipientStore = new RecipientStore(); + account.profileStore = new ProfileStore(); account.registered = false; return account; @@ -134,6 +137,7 @@ public class SignalAccount implements Closeable { account.groupStore = new JsonGroupStore(); account.contactStore = new JsonContactsStore(); account.recipientStore = new RecipientStore(); + account.profileStore = new ProfileStore(); account.registered = true; account.isMultiDevice = true; @@ -245,6 +249,14 @@ public class SignalAccount implements Closeable { } } + JsonNode profileStoreNode = rootNode.get("profileStore"); + if (profileStoreNode != null) { + profileStore = jsonProcessor.convertValue(profileStoreNode, ProfileStore.class); + } + if (profileStore == null) { + profileStore = new ProfileStore(); + } + JsonNode threadStoreNode = rootNode.get("threadStore"); if (threadStoreNode != null) { LegacyJsonThreadStore threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class); @@ -291,6 +303,7 @@ public class SignalAccount implements Closeable { .putPOJO("groupStore", groupStore) .putPOJO("contactStore", contactStore) .putPOJO("recipientStore", recipientStore) + .putPOJO("profileStore", profileStore) ; try { synchronized (fileChannel) { @@ -347,6 +360,10 @@ public class SignalAccount implements Closeable { return recipientStore; } + public ProfileStore getProfileStore() { + return profileStore; + } + public String getUsername() { return username; } diff --git a/src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java b/src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java new file mode 100644 index 00000000..691ac0ec --- /dev/null +++ b/src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java @@ -0,0 +1,90 @@ +package org.asamk.signal.storage.profiles; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import org.signal.zkgroup.InvalidInputException; +import org.signal.zkgroup.profiles.ProfileKey; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.api.util.UuidUtil; +import org.whispersystems.util.Base64; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class ProfileStore { + + private static final ObjectMapper jsonProcessor = new ObjectMapper(); + + @JsonProperty("profiles") + @JsonDeserialize(using = ProfileStoreDeserializer.class) + @JsonSerialize(using = ProfileStoreSerializer.class) + private final Map profiles = new HashMap<>(); + + public SignalProfileEntry getProfile(SignalServiceAddress serviceAddress) { + return profiles.get(serviceAddress); + } + + public SignalProfileEntry updateProfile(SignalServiceAddress serviceAddress, SignalProfileEntry profile) { + return profiles.put(serviceAddress, profile); + } + + public static class ProfileStoreDeserializer extends JsonDeserializer> { + + @Override + public Map deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + JsonNode node = jsonParser.getCodec().readTree(jsonParser); + + Map addresses = new HashMap<>(); + + if (node.isArray()) { + for (JsonNode recipient : node) { + String recipientName = recipient.get("name").asText(); + UUID uuid = UuidUtil.parseOrThrow(recipient.get("uuid").asText()); + final SignalServiceAddress serviceAddress = new SignalServiceAddress(uuid, recipientName); + ProfileKey profileKey = null; + try { + profileKey = new ProfileKey(Base64.decode(recipient.get("profileKey").asText())); + } catch (InvalidInputException ignored) { + } + long lastUpdateTimestamp = recipient.get("lastUpdateTimestamp").asLong(); + SignalProfile profile = jsonProcessor.treeToValue(recipient.get("profile"), SignalProfile.class); + addresses.put(serviceAddress, new SignalProfileEntry(profileKey, lastUpdateTimestamp, profile)); + } + } + + return addresses; + } + } + + public static class ProfileStoreSerializer extends JsonSerializer> { + + @Override + public void serialize(Map profiles, JsonGenerator json, SerializerProvider serializerProvider) throws IOException { + json.writeStartArray(); + for (Map.Entry entry : profiles.entrySet()) { + final SignalServiceAddress address = entry.getKey(); + final SignalProfileEntry profileEntry = entry.getValue(); + json.writeStartObject(); + json.writeStringField("name", address.getNumber().get()); + json.writeStringField("uuid", address.getUuid().get().toString()); + json.writeStringField("profileKey", Base64.encodeBytes(profileEntry.getProfileKey().serialize())); + json.writeNumberField("lastUpdateTimestamp", profileEntry.getLastUpdateTimestamp()); + json.writeObjectField("profile", profileEntry.getProfile()); + json.writeEndObject(); + } + json.writeEndArray(); + } + } +} diff --git a/src/main/java/org/asamk/signal/manager/SignalProfile.java b/src/main/java/org/asamk/signal/storage/profiles/SignalProfile.java similarity index 69% rename from src/main/java/org/asamk/signal/manager/SignalProfile.java rename to src/main/java/org/asamk/signal/storage/profiles/SignalProfile.java index 1217793c..71ab60e6 100644 --- a/src/main/java/org/asamk/signal/manager/SignalProfile.java +++ b/src/main/java/org/asamk/signal/storage/profiles/SignalProfile.java @@ -1,4 +1,6 @@ -package org.asamk.signal.manager; +package org.asamk.signal.storage.profiles; + +import com.fasterxml.jackson.annotation.JsonProperty; import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; @@ -6,16 +8,21 @@ import java.io.File; public class SignalProfile { + @JsonProperty private final String identityKey; + @JsonProperty private final String name; private final File avatarFile; + @JsonProperty private final String unidentifiedAccess; + @JsonProperty private final boolean unrestrictedUnidentifiedAccess; + @JsonProperty private final SignalServiceProfile.Capabilities capabilities; public SignalProfile(final String identityKey, final String name, final File avatarFile, final String unidentifiedAccess, final boolean unrestrictedUnidentifiedAccess, final SignalServiceProfile.Capabilities capabilities) { @@ -27,6 +34,15 @@ public class SignalProfile { this.capabilities = capabilities; } + public SignalProfile(@JsonProperty("identityKey") final String identityKey, @JsonProperty("name") final String name, @JsonProperty("unidentifiedAccess") final String unidentifiedAccess, @JsonProperty("unrestrictedUnidentifiedAccess") final boolean unrestrictedUnidentifiedAccess, @JsonProperty("capabilities") final SignalServiceProfile.Capabilities capabilities) { + this.identityKey = identityKey; + this.name = name; + this.avatarFile = null; + this.unidentifiedAccess = unidentifiedAccess; + this.unrestrictedUnidentifiedAccess = unrestrictedUnidentifiedAccess; + this.capabilities = capabilities; + } + public String getIdentityKey() { return identityKey; } diff --git a/src/main/java/org/asamk/signal/storage/profiles/SignalProfileEntry.java b/src/main/java/org/asamk/signal/storage/profiles/SignalProfileEntry.java new file mode 100644 index 00000000..0423e12e --- /dev/null +++ b/src/main/java/org/asamk/signal/storage/profiles/SignalProfileEntry.java @@ -0,0 +1,30 @@ +package org.asamk.signal.storage.profiles; + +import org.signal.zkgroup.profiles.ProfileKey; + +public class SignalProfileEntry { + + private ProfileKey profileKey; + + private long lastUpdateTimestamp; + + private SignalProfile profile; + + public SignalProfileEntry(final ProfileKey profileKey, final long lastUpdateTimestamp, final SignalProfile profile) { + this.profileKey = profileKey; + this.lastUpdateTimestamp = lastUpdateTimestamp; + this.profile = profile; + } + + public ProfileKey getProfileKey() { + return profileKey; + } + + public long getLastUpdateTimestamp() { + return lastUpdateTimestamp; + } + + public SignalProfile getProfile() { + return profile; + } +} diff --git a/src/main/java/org/asamk/signal/storage/protocol/RecipientStore.java b/src/main/java/org/asamk/signal/storage/protocol/RecipientStore.java index 4943bd36..47f06c46 100644 --- a/src/main/java/org/asamk/signal/storage/protocol/RecipientStore.java +++ b/src/main/java/org/asamk/signal/storage/protocol/RecipientStore.java @@ -26,9 +26,6 @@ public class RecipientStore { @JsonSerialize(using = RecipientStoreSerializer.class) private final Set addresses = new HashSet<>(); - public RecipientStore() { - } - public SignalServiceAddress resolveServiceAddress(SignalServiceAddress serviceAddress) { if (addresses.contains(serviceAddress)) { // If the Set already contains the exact address with UUID and Number, -- 2.50.1