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 final static 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
);
50 ProfileKey profileKey
= null;
51 if (r
.profileKey
!= null) {
53 profileKey
= new ProfileKey(Base64
.getDecoder().decode(r
.profileKey
));
54 } catch (InvalidInputException ignored
) {
58 ExpiringProfileKeyCredential expiringProfileKeyCredential
= null;
59 if (r
.expiringProfileKeyCredential
!= null) {
61 expiringProfileKeyCredential
= new ExpiringProfileKeyCredential(Base64
.getDecoder()
62 .decode(r
.expiringProfileKeyCredential
));
63 } catch (Throwable ignored
) {
67 Profile profile
= null;
68 if (r
.profile
!= null) {
69 profile
= new Profile(r
.profile
.lastUpdateTimestamp
,
74 r
.profile
.avatarUrlPath
,
75 r
.profile
.mobileCoinAddress
== null
77 : Base64
.getDecoder().decode(r
.profile
.mobileCoinAddress
),
78 Profile
.UnidentifiedAccessMode
.valueOfOrUnknown(r
.profile
.unidentifiedAccessMode
),
79 r
.profile
.capabilities
.stream()
80 .map(Profile
.Capability
::valueOfOrNull
)
81 .filter(Objects
::nonNull
)
82 .collect(Collectors
.toSet()));
85 return new Recipient(recipientId
, address
, contact
, profileKey
, expiringProfileKeyCredential
, profile
);
86 }).collect(Collectors
.toMap(Recipient
::getRecipientId
, r
-> r
));
88 recipientStore
.addLegacyRecipients(recipients
);
89 } catch (FileNotFoundException e
) {
91 } catch (IOException e
) {
92 logger
.warn("Failed to load recipient store", e
);
93 throw new RuntimeException(e
);
96 Files
.delete(file
.toPath());
97 } catch (IOException e
) {
98 logger
.warn("Failed to load recipient store", e
);
99 throw new RuntimeException(e
);
103 public record Storage(List
<Recipient
> recipients
, long lastId
) {
105 public record Recipient(
110 String expiringProfileKeyCredential
,
115 public record Contact(
118 int messageExpirationTime
,
121 boolean profileSharingEnabled
124 public record Profile(
125 long lastUpdateTimestamp
,
130 String avatarUrlPath
,
131 String mobileCoinAddress
,
132 String unidentifiedAccessMode
,
133 Set
<String
> capabilities