]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java
29b30ffd14f13478e197fce80e9e9ec7de44d13a
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / storage / recipients / LegacyRecipientStore2.java
1 package org.asamk.signal.manager.storage.recipients;
2
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.util.UuidUtil;
10
11 import java.io.File;
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;
20 import java.util.Set;
21 import java.util.stream.Collectors;
22
23 public class LegacyRecipientStore2 {
24
25 private final static Logger logger = LoggerFactory.getLogger(LegacyRecipientStore2.class);
26
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);
31
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(UuidUtil::parseOrThrow),
35 Optional.ofNullable(r.number));
36
37 Contact contact = null;
38 if (r.contact != null) {
39 contact = new Contact(r.contact.name,
40 null,
41 r.contact.color,
42 r.contact.messageExpirationTime,
43 r.contact.blocked,
44 r.contact.archived,
45 r.contact.profileSharingEnabled);
46 }
47
48 ProfileKey profileKey = null;
49 if (r.profileKey != null) {
50 try {
51 profileKey = new ProfileKey(Base64.getDecoder().decode(r.profileKey));
52 } catch (InvalidInputException ignored) {
53 }
54 }
55
56 ExpiringProfileKeyCredential expiringProfileKeyCredential = null;
57 if (r.expiringProfileKeyCredential != null) {
58 try {
59 expiringProfileKeyCredential = new ExpiringProfileKeyCredential(Base64.getDecoder()
60 .decode(r.expiringProfileKeyCredential));
61 } catch (Throwable ignored) {
62 }
63 }
64
65 Profile profile = null;
66 if (r.profile != null) {
67 profile = new Profile(r.profile.lastUpdateTimestamp,
68 r.profile.givenName,
69 r.profile.familyName,
70 r.profile.about,
71 r.profile.aboutEmoji,
72 r.profile.avatarUrlPath,
73 r.profile.mobileCoinAddress == null
74 ? 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()));
81 }
82
83 return new Recipient(recipientId, address, contact, profileKey, expiringProfileKeyCredential, profile);
84 }).collect(Collectors.toMap(Recipient::getRecipientId, r -> r));
85
86 recipientStore.addLegacyRecipients(recipients);
87 Files.delete(file.toPath());
88 } catch (FileNotFoundException e) {
89 // nothing to migrate
90 } catch (IOException e) {
91 logger.warn("Failed to load recipient store", e);
92 throw new RuntimeException(e);
93 }
94 }
95
96 private record Storage(List<Recipient> recipients, long lastId) {
97
98 private record Recipient(
99 long id,
100 String number,
101 String uuid,
102 String profileKey,
103 String expiringProfileKeyCredential,
104 Contact contact,
105 Profile profile
106 ) {
107
108 private record Contact(
109 String name,
110 String color,
111 int messageExpirationTime,
112 boolean blocked,
113 boolean archived,
114 boolean profileSharingEnabled
115 ) {}
116
117 private record Profile(
118 long lastUpdateTimestamp,
119 String givenName,
120 String familyName,
121 String about,
122 String aboutEmoji,
123 String avatarUrlPath,
124 String mobileCoinAddress,
125 String unidentifiedAccessMode,
126 Set<String> capabilities
127 ) {}
128 }
129 }
130 }