1 package org
.asamk
.signal
.manager
.storage
.recipients
;
3 import org
.asamk
.signal
.manager
.storage
.Utils
;
4 import org
.signal
.libsignal
.zkgroup
.InvalidInputException
;
5 import org
.signal
.libsignal
.zkgroup
.profiles
.ExpiringProfileKeyCredential
;
6 import org
.signal
.libsignal
.zkgroup
.profiles
.ProfileKey
;
7 import org
.slf4j
.Logger
;
8 import org
.slf4j
.LoggerFactory
;
9 import org
.whispersystems
.signalservice
.api
.push
.ServiceId
;
12 import java
.io
.FileInputStream
;
13 import java
.io
.FileNotFoundException
;
14 import java
.io
.IOException
;
15 import java
.nio
.file
.Files
;
16 import java
.util
.Base64
;
17 import java
.util
.List
;
18 import java
.util
.Objects
;
19 import java
.util
.Optional
;
21 import java
.util
.stream
.Collectors
;
23 public class LegacyRecipientStore2
{
25 private final static Logger logger
= LoggerFactory
.getLogger(LegacyRecipientStore2
.class);
27 public static void migrate(File file
, RecipientStore recipientStore
) {
28 final var objectMapper
= Utils
.createStorageObjectMapper();
29 try (var inputStream
= new FileInputStream(file
)) {
30 final var storage
= objectMapper
.readValue(inputStream
, Storage
.class);
32 final var recipients
= storage
.recipients
.stream().map(r
-> {
33 final var recipientId
= new RecipientId(r
.id
, recipientStore
);
34 final var address
= new RecipientAddress(Optional
.ofNullable(r
.uuid
).map(ServiceId
::parseOrThrow
),
35 Optional
.ofNullable(r
.number
));
37 Contact contact
= null;
38 if (r
.contact
!= null) {
39 contact
= new Contact(r
.contact
.name
,
42 r
.contact
.messageExpirationTime
,
45 r
.contact
.profileSharingEnabled
);
48 ProfileKey profileKey
= null;
49 if (r
.profileKey
!= null) {
51 profileKey
= new ProfileKey(Base64
.getDecoder().decode(r
.profileKey
));
52 } catch (InvalidInputException ignored
) {
56 ExpiringProfileKeyCredential expiringProfileKeyCredential
= null;
57 if (r
.expiringProfileKeyCredential
!= null) {
59 expiringProfileKeyCredential
= new ExpiringProfileKeyCredential(Base64
.getDecoder()
60 .decode(r
.expiringProfileKeyCredential
));
61 } catch (Throwable ignored
) {
65 Profile profile
= null;
66 if (r
.profile
!= null) {
67 profile
= new Profile(r
.profile
.lastUpdateTimestamp
,
72 r
.profile
.avatarUrlPath
,
73 r
.profile
.mobileCoinAddress
== null
75 : Base64
.getDecoder().decode(r
.profile
.mobileCoinAddress
),
76 Profile
.UnidentifiedAccessMode
.valueOfOrUnknown(r
.profile
.unidentifiedAccessMode
),
77 r
.profile
.capabilities
.stream()
78 .map(Profile
.Capability
::valueOfOrNull
)
79 .filter(Objects
::nonNull
)
80 .collect(Collectors
.toSet()));
83 return new Recipient(recipientId
, address
, contact
, profileKey
, expiringProfileKeyCredential
, profile
);
84 }).collect(Collectors
.toMap(Recipient
::getRecipientId
, r
-> r
));
86 recipientStore
.addLegacyRecipients(recipients
);
87 } catch (FileNotFoundException e
) {
89 } catch (IOException e
) {
90 logger
.warn("Failed to load recipient store", e
);
91 throw new RuntimeException(e
);
94 Files
.delete(file
.toPath());
95 } catch (IOException e
) {
96 logger
.warn("Failed to load recipient store", e
);
97 throw new RuntimeException(e
);
101 private record Storage(List
<Recipient
> recipients
, long lastId
) {
103 private record Recipient(
108 String expiringProfileKeyCredential
,
113 private record Contact(
116 int messageExpirationTime
,
119 boolean profileSharingEnabled
122 private record Profile(
123 long lastUpdateTimestamp
,
128 String avatarUrlPath
,
129 String mobileCoinAddress
,
130 String unidentifiedAccessMode
,
131 Set
<String
> capabilities