1 package org
.asamk
.signal
.manager
.storage
.recipients
;
3 import org
.asamk
.signal
.manager
.api
.Contact
;
4 import org
.asamk
.signal
.manager
.api
.Profile
;
5 import org
.asamk
.signal
.manager
.storage
.Utils
;
6 import org
.signal
.libsignal
.zkgroup
.InvalidInputException
;
7 import org
.signal
.libsignal
.zkgroup
.profiles
.ExpiringProfileKeyCredential
;
8 import org
.signal
.libsignal
.zkgroup
.profiles
.ProfileKey
;
9 import org
.slf4j
.Logger
;
10 import org
.slf4j
.LoggerFactory
;
11 import org
.whispersystems
.signalservice
.api
.push
.ServiceId
;
14 import java
.io
.FileInputStream
;
15 import java
.io
.FileNotFoundException
;
16 import java
.io
.IOException
;
17 import java
.nio
.file
.Files
;
18 import java
.util
.Base64
;
19 import java
.util
.List
;
20 import java
.util
.Objects
;
21 import java
.util
.Optional
;
23 import java
.util
.stream
.Collectors
;
25 public class LegacyRecipientStore2
{
27 private static final Logger logger
= LoggerFactory
.getLogger(LegacyRecipientStore2
.class);
29 public static void migrate(File file
, RecipientStore recipientStore
) {
30 final var objectMapper
= Utils
.createStorageObjectMapper();
31 try (var inputStream
= new FileInputStream(file
)) {
32 final var storage
= objectMapper
.readValue(inputStream
, Storage
.class);
34 final var recipients
= storage
.recipients
.stream().map(r
-> {
35 final var recipientId
= new RecipientId(r
.id
, recipientStore
);
36 final var address
= new RecipientAddress(Optional
.ofNullable(r
.uuid
).map(ServiceId
::parseOrThrow
),
37 Optional
.ofNullable(r
.number
));
39 Contact contact
= null;
40 if (r
.contact
!= null) {
41 contact
= new Contact(r
.contact
.name
,
44 r
.contact
.messageExpirationTime
,
47 r
.contact
.profileSharingEnabled
,
51 ProfileKey profileKey
= null;
52 if (r
.profileKey
!= null) {
54 profileKey
= new ProfileKey(Base64
.getDecoder().decode(r
.profileKey
));
55 } catch (InvalidInputException ignored
) {
59 ExpiringProfileKeyCredential expiringProfileKeyCredential
= null;
60 if (r
.expiringProfileKeyCredential
!= null) {
62 expiringProfileKeyCredential
= new ExpiringProfileKeyCredential(Base64
.getDecoder()
63 .decode(r
.expiringProfileKeyCredential
));
64 } catch (Throwable ignored
) {
68 Profile profile
= null;
69 if (r
.profile
!= null) {
70 profile
= new Profile(r
.profile
.lastUpdateTimestamp
,
75 r
.profile
.avatarUrlPath
,
76 r
.profile
.mobileCoinAddress
== null
78 : Base64
.getDecoder().decode(r
.profile
.mobileCoinAddress
),
79 Profile
.UnidentifiedAccessMode
.valueOfOrUnknown(r
.profile
.unidentifiedAccessMode
),
80 r
.profile
.capabilities
.stream()
81 .map(Profile
.Capability
::valueOfOrNull
)
82 .filter(Objects
::nonNull
)
83 .collect(Collectors
.toSet()));
86 return new Recipient(recipientId
, address
, contact
, profileKey
, expiringProfileKeyCredential
, profile
);
87 }).collect(Collectors
.toMap(Recipient
::getRecipientId
, r
-> r
));
89 recipientStore
.addLegacyRecipients(recipients
);
90 } catch (FileNotFoundException e
) {
92 } catch (IOException e
) {
93 logger
.warn("Failed to load recipient store", e
);
94 throw new RuntimeException(e
);
97 Files
.delete(file
.toPath());
98 } catch (IOException e
) {
99 logger
.warn("Failed to load recipient store", e
);
100 throw new RuntimeException(e
);
104 public record Storage(List
<Recipient
> recipients
, long lastId
) {
106 public record Recipient(
111 String expiringProfileKeyCredential
,
116 public record Contact(
119 int messageExpirationTime
,
122 boolean profileSharingEnabled
125 public record Profile(
126 long lastUpdateTimestamp
,
131 String avatarUrlPath
,
132 String mobileCoinAddress
,
133 String unidentifiedAccessMode
,
134 Set
<String
> capabilities