]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/profiles/ProfileStore.java
dc69a7ee72bd1a5c10d97df461095b207e4db71a
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / storage / profiles / ProfileStore.java
1 package org.asamk.signal.manager.storage.profiles;
2
3 import com.fasterxml.jackson.annotation.JsonProperty;
4 import com.fasterxml.jackson.core.JsonGenerator;
5 import com.fasterxml.jackson.core.JsonParser;
6 import com.fasterxml.jackson.databind.DeserializationContext;
7 import com.fasterxml.jackson.databind.JsonDeserializer;
8 import com.fasterxml.jackson.databind.JsonNode;
9 import com.fasterxml.jackson.databind.JsonSerializer;
10 import com.fasterxml.jackson.databind.ObjectMapper;
11 import com.fasterxml.jackson.databind.SerializerProvider;
12 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
13 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
14
15 import org.signal.zkgroup.InvalidInputException;
16 import org.signal.zkgroup.profiles.ProfileKey;
17 import org.signal.zkgroup.profiles.ProfileKeyCredential;
18 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
19 import org.whispersystems.signalservice.api.util.UuidUtil;
20
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.Base64;
24 import java.util.List;
25
26 public class ProfileStore {
27
28 private static final ObjectMapper jsonProcessor = new ObjectMapper();
29
30 @JsonProperty("profiles")
31 @JsonDeserialize(using = ProfileStoreDeserializer.class)
32 @JsonSerialize(using = ProfileStoreSerializer.class)
33 private final List<SignalProfileEntry> profiles = new ArrayList<>();
34
35 public SignalProfileEntry getProfileEntry(SignalServiceAddress serviceAddress) {
36 for (var entry : profiles) {
37 if (entry.getServiceAddress().matches(serviceAddress)) {
38 return entry;
39 }
40 }
41 return null;
42 }
43
44 public ProfileKey getProfileKey(SignalServiceAddress serviceAddress) {
45 for (var entry : profiles) {
46 if (entry.getServiceAddress().matches(serviceAddress)) {
47 return entry.getProfileKey();
48 }
49 }
50 return null;
51 }
52
53 public void updateProfile(
54 SignalServiceAddress serviceAddress,
55 ProfileKey profileKey,
56 long now,
57 SignalProfile profile,
58 ProfileKeyCredential profileKeyCredential
59 ) {
60 var newEntry = new SignalProfileEntry(serviceAddress, profileKey, now, profile, profileKeyCredential);
61 for (var i = 0; i < profiles.size(); i++) {
62 if (profiles.get(i).getServiceAddress().matches(serviceAddress)) {
63 profiles.set(i, newEntry);
64 return;
65 }
66 }
67
68 profiles.add(newEntry);
69 }
70
71 public void storeProfileKey(SignalServiceAddress serviceAddress, ProfileKey profileKey) {
72 var newEntry = new SignalProfileEntry(serviceAddress, profileKey, 0, null, null);
73 for (var i = 0; i < profiles.size(); i++) {
74 if (profiles.get(i).getServiceAddress().matches(serviceAddress)) {
75 if (!profiles.get(i).getProfileKey().equals(profileKey)) {
76 profiles.set(i, newEntry);
77 }
78 return;
79 }
80 }
81
82 profiles.add(newEntry);
83 }
84
85 public static class ProfileStoreDeserializer extends JsonDeserializer<List<SignalProfileEntry>> {
86
87 @Override
88 public List<SignalProfileEntry> deserialize(
89 JsonParser jsonParser, DeserializationContext deserializationContext
90 ) throws IOException {
91 JsonNode node = jsonParser.getCodec().readTree(jsonParser);
92
93 var addresses = new ArrayList<SignalProfileEntry>();
94
95 if (node.isArray()) {
96 for (var entry : node) {
97 var name = entry.hasNonNull("name") ? entry.get("name").asText() : null;
98 var uuid = entry.hasNonNull("uuid") ? UuidUtil.parseOrNull(entry.get("uuid").asText()) : null;
99 final var serviceAddress = new SignalServiceAddress(uuid, name);
100 ProfileKey profileKey = null;
101 try {
102 profileKey = new ProfileKey(Base64.getDecoder().decode(entry.get("profileKey").asText()));
103 } catch (InvalidInputException ignored) {
104 }
105 ProfileKeyCredential profileKeyCredential = null;
106 if (entry.hasNonNull("profileKeyCredential")) {
107 try {
108 profileKeyCredential = new ProfileKeyCredential(Base64.getDecoder()
109 .decode(entry.get("profileKeyCredential").asText()));
110 } catch (Throwable ignored) {
111 }
112 }
113 var lastUpdateTimestamp = entry.get("lastUpdateTimestamp").asLong();
114 var profile = jsonProcessor.treeToValue(entry.get("profile"), SignalProfile.class);
115 addresses.add(new SignalProfileEntry(serviceAddress,
116 profileKey,
117 lastUpdateTimestamp,
118 profile,
119 profileKeyCredential));
120 }
121 }
122
123 return addresses;
124 }
125 }
126
127 public static class ProfileStoreSerializer extends JsonSerializer<List<SignalProfileEntry>> {
128
129 @Override
130 public void serialize(
131 List<SignalProfileEntry> profiles, JsonGenerator json, SerializerProvider serializerProvider
132 ) throws IOException {
133 json.writeStartArray();
134 for (var profileEntry : profiles) {
135 final var address = profileEntry.getServiceAddress();
136 json.writeStartObject();
137 if (address.getNumber().isPresent()) {
138 json.writeStringField("name", address.getNumber().get());
139 }
140 if (address.getUuid().isPresent()) {
141 json.writeStringField("uuid", address.getUuid().get().toString());
142 }
143 json.writeStringField("profileKey",
144 Base64.getEncoder().encodeToString(profileEntry.getProfileKey().serialize()));
145 json.writeNumberField("lastUpdateTimestamp", profileEntry.getLastUpdateTimestamp());
146 json.writeObjectField("profile", profileEntry.getProfile());
147 if (profileEntry.getProfileKeyCredential() != null) {
148 json.writeStringField("profileKeyCredential",
149 Base64.getEncoder().encodeToString(profileEntry.getProfileKeyCredential().serialize()));
150 }
151 json.writeEndObject();
152 }
153 json.writeEndArray();
154 }
155 }
156 }