1 package org
.asamk
.signal
.manager
.storage
.profiles
;
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
;
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
;
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
;
27 public class ProfileStore
{
29 private static final ObjectMapper jsonProcessor
= new ObjectMapper();
31 @JsonProperty("profiles")
32 @JsonDeserialize(using
= ProfileStoreDeserializer
.class)
33 @JsonSerialize(using
= ProfileStoreSerializer
.class)
34 private final List
<SignalProfileEntry
> profiles
= new ArrayList
<>();
36 public SignalProfileEntry
getProfileEntry(SignalServiceAddress serviceAddress
) {
37 for (SignalProfileEntry entry
: profiles
) {
38 if (entry
.getServiceAddress().matches(serviceAddress
)) {
45 public ProfileKey
getProfileKey(SignalServiceAddress serviceAddress
) {
46 for (SignalProfileEntry entry
: profiles
) {
47 if (entry
.getServiceAddress().matches(serviceAddress
)) {
48 return entry
.getProfileKey();
54 public void updateProfile(
55 SignalServiceAddress serviceAddress
,
56 ProfileKey profileKey
,
58 SignalProfile profile
,
59 ProfileKeyCredential profileKeyCredential
61 SignalProfileEntry newEntry
= new SignalProfileEntry(serviceAddress
,
65 profileKeyCredential
);
66 for (int i
= 0; i
< profiles
.size(); i
++) {
67 if (profiles
.get(i
).getServiceAddress().matches(serviceAddress
)) {
68 profiles
.set(i
, newEntry
);
73 profiles
.add(newEntry
);
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
);
87 profiles
.add(newEntry
);
90 public static class ProfileStoreDeserializer
extends JsonDeserializer
<List
<SignalProfileEntry
>> {
93 public List
<SignalProfileEntry
> deserialize(
94 JsonParser jsonParser
, DeserializationContext deserializationContext
95 ) throws IOException
{
96 JsonNode node
= jsonParser
.getCodec().readTree(jsonParser
);
98 List
<SignalProfileEntry
> addresses
= new ArrayList
<>();
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;
107 profileKey
= new ProfileKey(Base64
.getDecoder().decode(entry
.get("profileKey").asText()));
108 } catch (InvalidInputException ignored
) {
110 ProfileKeyCredential profileKeyCredential
= null;
111 if (entry
.hasNonNull("profileKeyCredential")) {
113 profileKeyCredential
= new ProfileKeyCredential(Base64
.getDecoder()
114 .decode(entry
.get("profileKeyCredential").asText()));
115 } catch (Throwable ignored
) {
118 long lastUpdateTimestamp
= entry
.get("lastUpdateTimestamp").asLong();
119 SignalProfile profile
= jsonProcessor
.treeToValue(entry
.get("profile"), SignalProfile
.class);
120 addresses
.add(new SignalProfileEntry(serviceAddress
,
124 profileKeyCredential
));
132 public static class ProfileStoreSerializer
extends JsonSerializer
<List
<SignalProfileEntry
>> {
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());
145 if (address
.getUuid().isPresent()) {
146 json
.writeStringField("uuid", address
.getUuid().get().toString());
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()));
156 json
.writeEndObject();
158 json
.writeEndArray();