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