1 package org
.asamk
.signal
.manager
.syncStorage
;
3 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientAddress
;
4 import org
.signal
.core
.util
.Base64
;
5 import org
.signal
.core
.util
.SetUtil
;
6 import org
.slf4j
.Logger
;
7 import org
.slf4j
.LoggerFactory
;
8 import org
.whispersystems
.signalservice
.api
.storage
.SignalStorageManifest
;
9 import org
.whispersystems
.signalservice
.api
.storage
.SignalStorageRecord
;
10 import org
.whispersystems
.signalservice
.api
.storage
.StorageId
;
11 import org
.whispersystems
.signalservice
.internal
.storage
.protos
.ManifestRecord
;
13 import java
.nio
.ByteBuffer
;
14 import java
.util
.HashSet
;
15 import java
.util
.List
;
17 import java
.util
.stream
.Collectors
;
19 public final class StorageSyncValidations
{
21 private static final Logger logger
= LoggerFactory
.getLogger(StorageSyncValidations
.class);
23 private StorageSyncValidations() {
26 public static void validate(
27 WriteOperationResult result
,
28 SignalStorageManifest previousManifest
,
29 boolean forcePushPending
,
32 validateManifestAndInserts(result
.manifest(), result
.inserts(), self
);
34 if (!result
.deletes().isEmpty()) {
35 Set
<String
> allSetEncoded
= result
.manifest()
38 .map(StorageId
::getRaw
)
39 .map(Base64
::encodeWithPadding
)
40 .collect(Collectors
.toSet());
42 for (byte[] delete
: result
.deletes()) {
43 String encoded
= Base64
.encodeWithPadding(delete
);
44 if (allSetEncoded
.contains(encoded
)) {
45 throw new DeletePresentInFullIdSetError();
50 if (previousManifest
.getVersion() == 0) {
52 "Previous manifest is empty, not bothering with additional validations around the diffs between the two manifests.");
56 if (result
.manifest().getVersion() != previousManifest
.getVersion() + 1) {
57 throw new IncorrectManifestVersionError();
60 if (forcePushPending
) {
62 "Force push pending, not bothering with additional validations around the diffs between the two manifests.");
66 Set
<ByteBuffer
> previousIds
= previousManifest
.getStorageIds()
68 .map(id
-> ByteBuffer
.wrap(id
.getRaw()))
69 .collect(Collectors
.toSet());
70 Set
<ByteBuffer
> newIds
= result
.manifest()
73 .map(id
-> ByteBuffer
.wrap(id
.getRaw()))
74 .collect(Collectors
.toSet());
76 Set
<ByteBuffer
> manifestInserts
= SetUtil
.difference(newIds
, previousIds
);
77 Set
<ByteBuffer
> manifestDeletes
= SetUtil
.difference(previousIds
, newIds
);
79 Set
<ByteBuffer
> declaredInserts
= result
.inserts()
81 .map(r
-> ByteBuffer
.wrap(r
.getId().getRaw()))
82 .collect(Collectors
.toSet());
83 Set
<ByteBuffer
> declaredDeletes
= result
.deletes().stream().map(ByteBuffer
::wrap
).collect(Collectors
.toSet());
85 if (declaredInserts
.size() > manifestInserts
.size()) {
86 logger
.debug("DeclaredInserts: " + declaredInserts
.size() + ", ManifestInserts: " + manifestInserts
.size());
87 throw new MoreInsertsThanExpectedError();
90 if (declaredInserts
.size() < manifestInserts
.size()) {
91 logger
.debug("DeclaredInserts: " + declaredInserts
.size() + ", ManifestInserts: " + manifestInserts
.size());
92 throw new LessInsertsThanExpectedError();
95 if (!declaredInserts
.containsAll(manifestInserts
)) {
96 throw new InsertMismatchError();
99 if (declaredDeletes
.size() > manifestDeletes
.size()) {
100 logger
.debug("DeclaredDeletes: " + declaredDeletes
.size() + ", ManifestDeletes: " + manifestDeletes
.size());
101 throw new MoreDeletesThanExpectedError();
104 if (declaredDeletes
.size() < manifestDeletes
.size()) {
105 logger
.debug("DeclaredDeletes: " + declaredDeletes
.size() + ", ManifestDeletes: " + manifestDeletes
.size());
106 throw new LessDeletesThanExpectedError();
109 if (!declaredDeletes
.containsAll(manifestDeletes
)) {
110 throw new DeleteMismatchError();
114 public static void validateForcePush(
115 SignalStorageManifest manifest
, List
<SignalStorageRecord
> inserts
, RecipientAddress self
117 validateManifestAndInserts(manifest
, inserts
, self
);
120 private static void validateManifestAndInserts(
121 SignalStorageManifest manifest
, List
<SignalStorageRecord
> inserts
, RecipientAddress self
123 int accountCount
= 0;
124 for (StorageId id
: manifest
.getStorageIds()) {
125 accountCount
+= id
.getType() == ManifestRecord
.Identifier
.Type
.ACCOUNT
.getValue() ?
1 : 0;
128 if (accountCount
> 1) {
129 throw new MultipleAccountError();
132 if (accountCount
== 0) {
133 throw new MissingAccountError();
136 Set
<StorageId
> allSet
= new HashSet
<>(manifest
.getStorageIds());
137 Set
<StorageId
> insertSet
= inserts
.stream().map(SignalStorageRecord
::getId
).collect(Collectors
.toSet());
138 Set
<ByteBuffer
> rawIdSet
= allSet
.stream().map(id
-> ByteBuffer
.wrap(id
.getRaw())).collect(Collectors
.toSet());
140 if (allSet
.size() != manifest
.getStorageIds().size()) {
141 throw new DuplicateStorageIdError();
144 if (rawIdSet
.size() != allSet
.size()) {
145 List
<StorageId
> ids
= manifest
.getStorageIdsByType().get(ManifestRecord
.Identifier
.Type
.CONTACT
.getValue());
146 if (ids
.size() != new HashSet
<>(ids
).size()) {
147 throw new DuplicateContactIdError();
150 ids
= manifest
.getStorageIdsByType().get(ManifestRecord
.Identifier
.Type
.GROUPV1
.getValue());
151 if (ids
.size() != new HashSet
<>(ids
).size()) {
152 throw new DuplicateGroupV1IdError();
155 ids
= manifest
.getStorageIdsByType().get(ManifestRecord
.Identifier
.Type
.GROUPV2
.getValue());
156 if (ids
.size() != new HashSet
<>(ids
).size()) {
157 throw new DuplicateGroupV2IdError();
160 ids
= manifest
.getStorageIdsByType().get(ManifestRecord
.Identifier
.Type
.STORY_DISTRIBUTION_LIST
.getValue());
161 if (ids
.size() != new HashSet
<>(ids
).size()) {
162 throw new DuplicateDistributionListIdError();
165 throw new DuplicateRawIdAcrossTypesError();
168 if (inserts
.size() > insertSet
.size()) {
169 throw new DuplicateInsertInWriteError();
172 for (SignalStorageRecord insert
: inserts
) {
173 if (!allSet
.contains(insert
.getId())) {
174 throw new InsertNotPresentInFullIdSetError();
177 if (insert
.isUnknown()) {
178 throw new UnknownInsertError();
181 if (insert
.getContact().isPresent()) {
182 final var contact
= insert
.getContact().get();
183 final var aci
= contact
.getAci();
184 final var pni
= contact
.getPni();
185 final var number
= contact
.getNumber();
186 final var username
= contact
.getUsername();
187 final var address
= new RecipientAddress(aci
, pni
, number
, username
);
188 if (self
.matches(address
)) {
189 throw new SelfAddedAsContactError();
192 if (insert
.getAccount().isPresent() && insert
.getAccount().get().getProfileKey().isEmpty()) {
193 logger
.debug("Uploading a null profile key in our AccountRecord!");
198 private static final class DuplicateStorageIdError
extends Error
{}
200 private static final class DuplicateRawIdAcrossTypesError
extends Error
{}
202 private static final class DuplicateContactIdError
extends Error
{}
204 private static final class DuplicateGroupV1IdError
extends Error
{}
206 private static final class DuplicateGroupV2IdError
extends Error
{}
208 private static final class DuplicateDistributionListIdError
extends Error
{}
210 private static final class DuplicateInsertInWriteError
extends Error
{}
212 private static final class InsertNotPresentInFullIdSetError
extends Error
{}
214 private static final class DeletePresentInFullIdSetError
extends Error
{}
216 private static final class UnknownInsertError
extends Error
{}
218 private static final class MultipleAccountError
extends Error
{}
220 private static final class MissingAccountError
extends Error
{}
222 private static final class SelfAddedAsContactError
extends Error
{}
224 private static final class IncorrectManifestVersionError
extends Error
{}
226 private static final class MoreInsertsThanExpectedError
extends Error
{}
228 private static final class LessInsertsThanExpectedError
extends Error
{}
230 private static final class InsertMismatchError
extends Error
{}
232 private static final class MoreDeletesThanExpectedError
extends Error
{}
234 private static final class LessDeletesThanExpectedError
extends Error
{}
236 private static final class DeleteMismatchError
extends Error
{}