1 package org
.asamk
.signal
.manager
.helper
;
3 import com
.google
.protobuf
.InvalidProtocolBufferException
;
5 import org
.asamk
.signal
.manager
.api
.Pair
;
6 import org
.asamk
.signal
.manager
.groups
.GroupLinkPassword
;
7 import org
.asamk
.signal
.manager
.groups
.GroupLinkState
;
8 import org
.asamk
.signal
.manager
.groups
.GroupPermission
;
9 import org
.asamk
.signal
.manager
.groups
.GroupUtils
;
10 import org
.asamk
.signal
.manager
.groups
.NotAGroupMemberException
;
11 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfoV2
;
12 import org
.asamk
.signal
.manager
.storage
.recipients
.Profile
;
13 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
14 import org
.asamk
.signal
.manager
.util
.IOUtils
;
15 import org
.asamk
.signal
.manager
.util
.Utils
;
16 import org
.signal
.storageservice
.protos
.groups
.AccessControl
;
17 import org
.signal
.storageservice
.protos
.groups
.GroupChange
;
18 import org
.signal
.storageservice
.protos
.groups
.Member
;
19 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroup
;
20 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroupChange
;
21 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroupJoinInfo
;
22 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedPendingMember
;
23 import org
.signal
.zkgroup
.InvalidInputException
;
24 import org
.signal
.zkgroup
.VerificationFailedException
;
25 import org
.signal
.zkgroup
.auth
.AuthCredentialResponse
;
26 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
27 import org
.signal
.zkgroup
.groups
.GroupSecretParams
;
28 import org
.signal
.zkgroup
.groups
.UuidCiphertext
;
29 import org
.slf4j
.Logger
;
30 import org
.slf4j
.LoggerFactory
;
31 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
32 import org
.whispersystems
.signalservice
.api
.groupsv2
.DecryptedGroupUtil
;
33 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupCandidate
;
34 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
35 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Api
;
36 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2AuthorizationString
;
37 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
38 import org
.whispersystems
.signalservice
.api
.groupsv2
.InvalidGroupStateException
;
39 import org
.whispersystems
.signalservice
.api
.groupsv2
.NotAbleToApplyGroupV2ChangeException
;
40 import org
.whispersystems
.signalservice
.api
.push
.ACI
;
41 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
42 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NonSuccessfulResponseCodeException
;
45 import java
.io
.FileInputStream
;
46 import java
.io
.IOException
;
47 import java
.io
.InputStream
;
48 import java
.util
.ArrayList
;
49 import java
.util
.HashMap
;
51 import java
.util
.UUID
;
52 import java
.util
.concurrent
.TimeUnit
;
53 import java
.util
.stream
.Collectors
;
55 public class GroupV2Helper
{
57 private final static Logger logger
= LoggerFactory
.getLogger(GroupV2Helper
.class);
59 private final ProfileHelper profileHelper
;
60 private final SelfRecipientIdProvider selfRecipientIdProvider
;
61 private final GroupsV2Operations groupsV2Operations
;
62 private final GroupsV2Api groupsV2Api
;
63 private final SignalServiceAddressResolver addressResolver
;
65 private HashMap
<Integer
, AuthCredentialResponse
> groupApiCredentials
;
68 final ProfileHelper profileHelper
,
69 final SelfRecipientIdProvider selfRecipientIdProvider
,
70 final GroupsV2Operations groupsV2Operations
,
71 final GroupsV2Api groupsV2Api
,
72 final SignalServiceAddressResolver addressResolver
74 this.profileHelper
= profileHelper
;
75 this.selfRecipientIdProvider
= selfRecipientIdProvider
;
76 this.groupsV2Operations
= groupsV2Operations
;
77 this.groupsV2Api
= groupsV2Api
;
78 this.addressResolver
= addressResolver
;
81 public DecryptedGroup
getDecryptedGroup(final GroupSecretParams groupSecretParams
) throws NotAGroupMemberException
{
83 final var groupsV2AuthorizationString
= getGroupAuthForToday(groupSecretParams
);
84 return groupsV2Api
.getGroup(groupSecretParams
, groupsV2AuthorizationString
);
85 } catch (NonSuccessfulResponseCodeException e
) {
86 if (e
.getCode() == 403) {
87 throw new NotAGroupMemberException(GroupUtils
.getGroupIdV2(groupSecretParams
), null);
89 logger
.warn("Failed to retrieve Group V2 info, ignoring: {}", e
.getMessage());
91 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
92 logger
.warn("Failed to retrieve Group V2 info, ignoring: {}", e
.getMessage());
97 public DecryptedGroupJoinInfo
getDecryptedGroupJoinInfo(
98 GroupMasterKey groupMasterKey
, GroupLinkPassword password
99 ) throws IOException
, GroupLinkNotActiveException
{
100 var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
102 return groupsV2Api
.getGroupJoinInfo(groupSecretParams
,
103 Optional
.fromNullable(password
).transform(GroupLinkPassword
::serialize
),
104 getGroupAuthForToday(groupSecretParams
));
107 public Pair
<GroupInfoV2
, DecryptedGroup
> createGroup(
108 String name
, Set
<RecipientId
> members
, File avatarFile
109 ) throws IOException
{
110 final var avatarBytes
= readAvatarBytes(avatarFile
);
111 final var newGroup
= buildNewGroup(name
, members
, avatarBytes
);
112 if (newGroup
== null) {
116 final var groupSecretParams
= newGroup
.getGroupSecretParams();
118 final GroupsV2AuthorizationString groupAuthForToday
;
119 final DecryptedGroup decryptedGroup
;
121 groupAuthForToday
= getGroupAuthForToday(groupSecretParams
);
122 groupsV2Api
.putNewGroup(newGroup
, groupAuthForToday
);
123 decryptedGroup
= groupsV2Api
.getGroup(groupSecretParams
, groupAuthForToday
);
124 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
125 logger
.warn("Failed to create V2 group: {}", e
.getMessage());
128 if (decryptedGroup
== null) {
129 logger
.warn("Failed to create V2 group, unknown error!");
133 final var groupId
= GroupUtils
.getGroupIdV2(groupSecretParams
);
134 final var masterKey
= groupSecretParams
.getMasterKey();
135 var g
= new GroupInfoV2(groupId
, masterKey
);
137 return new Pair
<>(g
, decryptedGroup
);
140 private byte[] readAvatarBytes(final File avatarFile
) throws IOException
{
141 final byte[] avatarBytes
;
142 try (InputStream avatar
= avatarFile
== null ?
null : new FileInputStream(avatarFile
)) {
143 avatarBytes
= avatar
== null ?
null : IOUtils
.readFully(avatar
);
148 private GroupsV2Operations
.NewGroup
buildNewGroup(
149 String name
, Set
<RecipientId
> members
, byte[] avatar
151 final var profileKeyCredential
= profileHelper
.getRecipientProfileKeyCredential(selfRecipientIdProvider
.getSelfRecipientId());
152 if (profileKeyCredential
== null) {
153 logger
.warn("Cannot create a V2 group as self does not have a versioned profile");
157 if (!areMembersValid(members
)) return null;
159 final var self
= new GroupCandidate(getSelfAci().uuid(), Optional
.fromNullable(profileKeyCredential
));
160 final var memberList
= new ArrayList
<>(members
);
161 final var credentials
= profileHelper
.getRecipientProfileKeyCredential(memberList
).stream();
162 final var uuids
= memberList
.stream()
163 .map(member
-> addressResolver
.resolveSignalServiceAddress(member
).getAci().uuid());
164 var candidates
= Utils
.zip(uuids
,
166 (uuid
, credential
) -> new GroupCandidate(uuid
, Optional
.fromNullable(credential
)))
167 .collect(Collectors
.toSet());
169 final var groupSecretParams
= GroupSecretParams
.generate();
170 return groupsV2Operations
.createNewGroup(groupSecretParams
,
172 Optional
.fromNullable(avatar
),
179 private boolean areMembersValid(final Set
<RecipientId
> members
) {
180 final var noGv2Capability
= profileHelper
.getRecipientProfile(new ArrayList
<>(members
))
182 .filter(profile
-> profile
!= null && !profile
.getCapabilities().contains(Profile
.Capability
.gv2
))
183 .collect(Collectors
.toSet());
184 if (noGv2Capability
.size() > 0) {
185 logger
.warn("Cannot create a V2 group as some members don't support Groups V2: {}",
186 noGv2Capability
.stream().map(Profile
::getDisplayName
).collect(Collectors
.joining(", ")));
193 public Pair
<DecryptedGroup
, GroupChange
> updateGroup(
194 GroupInfoV2 groupInfoV2
, String name
, String description
, File avatarFile
195 ) throws IOException
{
196 final var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupInfoV2
.getMasterKey());
197 var groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
199 var change
= name
!= null ? groupOperations
.createModifyGroupTitle(name
) : GroupChange
.Actions
.newBuilder();
201 if (description
!= null) {
202 change
.setModifyDescription(groupOperations
.createModifyGroupDescriptionAction(description
));
205 if (avatarFile
!= null) {
206 final var avatarBytes
= readAvatarBytes(avatarFile
);
207 var avatarCdnKey
= groupsV2Api
.uploadAvatar(avatarBytes
,
209 getGroupAuthForToday(groupSecretParams
));
210 change
.setModifyAvatar(GroupChange
.Actions
.ModifyAvatarAction
.newBuilder().setAvatar(avatarCdnKey
));
213 change
.setSourceUuid(getSelfAci().toByteString());
215 return commitChange(groupInfoV2
, change
);
218 public Pair
<DecryptedGroup
, GroupChange
> addMembers(
219 GroupInfoV2 groupInfoV2
, Set
<RecipientId
> newMembers
220 ) throws IOException
{
221 GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
223 if (!areMembersValid(newMembers
)) {
224 throw new IOException("Failed to update group");
227 final var memberList
= new ArrayList
<>(newMembers
);
228 final var credentials
= profileHelper
.getRecipientProfileKeyCredential(memberList
).stream();
229 final var uuids
= memberList
.stream()
230 .map(member
-> addressResolver
.resolveSignalServiceAddress(member
).getAci().uuid());
231 var candidates
= Utils
.zip(uuids
,
233 (uuid
, credential
) -> new GroupCandidate(uuid
, Optional
.fromNullable(credential
)))
234 .collect(Collectors
.toSet());
236 final var aci
= getSelfAci();
237 final var change
= groupOperations
.createModifyGroupMembershipChange(candidates
, aci
.uuid());
239 change
.setSourceUuid(getSelfAci().toByteString());
241 return commitChange(groupInfoV2
, change
);
244 public Pair
<DecryptedGroup
, GroupChange
> leaveGroup(
245 GroupInfoV2 groupInfoV2
, Set
<RecipientId
> membersToMakeAdmin
246 ) throws IOException
{
247 var pendingMembersList
= groupInfoV2
.getGroup().getPendingMembersList();
248 final var selfAci
= getSelfAci();
249 var selfPendingMember
= DecryptedGroupUtil
.findPendingByUuid(pendingMembersList
, selfAci
.uuid());
251 if (selfPendingMember
.isPresent()) {
252 return revokeInvites(groupInfoV2
, Set
.of(selfPendingMember
.get()));
255 final var adminUuids
= membersToMakeAdmin
.stream()
256 .map(addressResolver
::resolveSignalServiceAddress
)
257 .map(SignalServiceAddress
::getAci
)
260 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
261 return commitChange(groupInfoV2
,
262 groupOperations
.createLeaveAndPromoteMembersToAdmin(selfAci
.uuid(), adminUuids
));
265 public Pair
<DecryptedGroup
, GroupChange
> removeMembers(
266 GroupInfoV2 groupInfoV2
, Set
<RecipientId
> members
267 ) throws IOException
{
268 final var memberUuids
= members
.stream()
269 .map(addressResolver
::resolveSignalServiceAddress
)
270 .map(SignalServiceAddress
::getAci
)
272 .collect(Collectors
.toSet());
273 return ejectMembers(groupInfoV2
, memberUuids
);
276 public Pair
<DecryptedGroup
, GroupChange
> revokeInvitedMembers(
277 GroupInfoV2 groupInfoV2
, Set
<RecipientId
> members
278 ) throws IOException
{
279 var pendingMembersList
= groupInfoV2
.getGroup().getPendingMembersList();
280 final var memberUuids
= members
.stream()
281 .map(addressResolver
::resolveSignalServiceAddress
)
282 .map(SignalServiceAddress
::getAci
)
284 .map(uuid
-> DecryptedGroupUtil
.findPendingByUuid(pendingMembersList
, uuid
))
285 .filter(Optional
::isPresent
)
287 .collect(Collectors
.toSet());
288 return revokeInvites(groupInfoV2
, memberUuids
);
291 public Pair
<DecryptedGroup
, GroupChange
> resetGroupLinkPassword(GroupInfoV2 groupInfoV2
) throws IOException
{
292 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
293 final var newGroupLinkPassword
= GroupLinkPassword
.createNew().serialize();
294 final var change
= groupOperations
.createModifyGroupLinkPasswordChange(newGroupLinkPassword
);
295 return commitChange(groupInfoV2
, change
);
298 public Pair
<DecryptedGroup
, GroupChange
> setGroupLinkState(
299 GroupInfoV2 groupInfoV2
, GroupLinkState state
300 ) throws IOException
{
301 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
303 final var accessRequired
= toAccessControl(state
);
304 final var requiresNewPassword
= state
!= GroupLinkState
.DISABLED
&& groupInfoV2
.getGroup()
305 .getInviteLinkPassword()
308 final var change
= requiresNewPassword ? groupOperations
.createModifyGroupLinkPasswordAndRightsChange(
309 GroupLinkPassword
.createNew().serialize(),
310 accessRequired
) : groupOperations
.createChangeJoinByLinkRights(accessRequired
);
311 return commitChange(groupInfoV2
, change
);
314 public Pair
<DecryptedGroup
, GroupChange
> setEditDetailsPermission(
315 GroupInfoV2 groupInfoV2
, GroupPermission permission
316 ) throws IOException
{
317 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
319 final var accessRequired
= toAccessControl(permission
);
320 final var change
= groupOperations
.createChangeAttributesRights(accessRequired
);
321 return commitChange(groupInfoV2
, change
);
324 public Pair
<DecryptedGroup
, GroupChange
> setAddMemberPermission(
325 GroupInfoV2 groupInfoV2
, GroupPermission permission
326 ) throws IOException
{
327 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
329 final var accessRequired
= toAccessControl(permission
);
330 final var change
= groupOperations
.createChangeMembershipRights(accessRequired
);
331 return commitChange(groupInfoV2
, change
);
334 public GroupChange
joinGroup(
335 GroupMasterKey groupMasterKey
,
336 GroupLinkPassword groupLinkPassword
,
337 DecryptedGroupJoinInfo decryptedGroupJoinInfo
338 ) throws IOException
{
339 final var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
340 final var groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
342 final var selfRecipientId
= this.selfRecipientIdProvider
.getSelfRecipientId();
343 final var profileKeyCredential
= profileHelper
.getRecipientProfileKeyCredential(selfRecipientId
);
344 if (profileKeyCredential
== null) {
345 throw new IOException("Cannot join a V2 group as self does not have a versioned profile");
348 var requestToJoin
= decryptedGroupJoinInfo
.getAddFromInviteLink() == AccessControl
.AccessRequired
.ADMINISTRATOR
;
349 var change
= requestToJoin
350 ? groupOperations
.createGroupJoinRequest(profileKeyCredential
)
351 : groupOperations
.createGroupJoinDirect(profileKeyCredential
);
353 change
.setSourceUuid(addressResolver
.resolveSignalServiceAddress(selfRecipientId
).getAci().toByteString());
355 return commitChange(groupSecretParams
, decryptedGroupJoinInfo
.getRevision(), change
, groupLinkPassword
);
358 public Pair
<DecryptedGroup
, GroupChange
> acceptInvite(GroupInfoV2 groupInfoV2
) throws IOException
{
359 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
361 final var selfRecipientId
= this.selfRecipientIdProvider
.getSelfRecipientId();
362 final var profileKeyCredential
= profileHelper
.getRecipientProfileKeyCredential(selfRecipientId
);
363 if (profileKeyCredential
== null) {
364 throw new IOException("Cannot join a V2 group as self does not have a versioned profile");
367 final var change
= groupOperations
.createAcceptInviteChange(profileKeyCredential
);
369 final var aci
= addressResolver
.resolveSignalServiceAddress(selfRecipientId
).getAci();
370 change
.setSourceUuid(aci
.toByteString());
372 return commitChange(groupInfoV2
, change
);
375 public Pair
<DecryptedGroup
, GroupChange
> setMemberAdmin(
376 GroupInfoV2 groupInfoV2
, RecipientId recipientId
, boolean admin
377 ) throws IOException
{
378 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
379 final var address
= addressResolver
.resolveSignalServiceAddress(recipientId
);
380 final var newRole
= admin ? Member
.Role
.ADMINISTRATOR
: Member
.Role
.DEFAULT
;
381 final var change
= groupOperations
.createChangeMemberRole(address
.getAci().uuid(), newRole
);
382 return commitChange(groupInfoV2
, change
);
385 public Pair
<DecryptedGroup
, GroupChange
> setMessageExpirationTimer(
386 GroupInfoV2 groupInfoV2
, int messageExpirationTimer
387 ) throws IOException
{
388 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
389 final var change
= groupOperations
.createModifyGroupTimerChange(messageExpirationTimer
);
390 return commitChange(groupInfoV2
, change
);
393 public Pair
<DecryptedGroup
, GroupChange
> setIsAnnouncementGroup(
394 GroupInfoV2 groupInfoV2
, boolean isAnnouncementGroup
395 ) throws IOException
{
396 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
397 final var change
= groupOperations
.createAnnouncementGroupChange(isAnnouncementGroup
);
398 return commitChange(groupInfoV2
, change
);
401 private AccessControl
.AccessRequired
toAccessControl(final GroupLinkState state
) {
402 return switch (state
) {
403 case DISABLED
-> AccessControl
.AccessRequired
.UNSATISFIABLE
;
404 case ENABLED
-> AccessControl
.AccessRequired
.ANY
;
405 case ENABLED_WITH_APPROVAL
-> AccessControl
.AccessRequired
.ADMINISTRATOR
;
409 private AccessControl
.AccessRequired
toAccessControl(final GroupPermission permission
) {
410 return switch (permission
) {
411 case EVERY_MEMBER
-> AccessControl
.AccessRequired
.MEMBER
;
412 case ONLY_ADMINS
-> AccessControl
.AccessRequired
.ADMINISTRATOR
;
416 private GroupsV2Operations
.GroupOperations
getGroupOperations(final GroupInfoV2 groupInfoV2
) {
417 final var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupInfoV2
.getMasterKey());
418 return groupsV2Operations
.forGroup(groupSecretParams
);
421 private Pair
<DecryptedGroup
, GroupChange
> revokeInvites(
422 GroupInfoV2 groupInfoV2
, Set
<DecryptedPendingMember
> pendingMembers
423 ) throws IOException
{
424 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
425 final var uuidCipherTexts
= pendingMembers
.stream().map(member
-> {
427 return new UuidCiphertext(member
.getUuidCipherText().toByteArray());
428 } catch (InvalidInputException e
) {
429 throw new AssertionError(e
);
431 }).collect(Collectors
.toSet());
432 return commitChange(groupInfoV2
, groupOperations
.createRemoveInvitationChange(uuidCipherTexts
));
435 private Pair
<DecryptedGroup
, GroupChange
> ejectMembers(
436 GroupInfoV2 groupInfoV2
, Set
<UUID
> uuids
437 ) throws IOException
{
438 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
439 return commitChange(groupInfoV2
, groupOperations
.createRemoveMembersChange(uuids
));
442 private Pair
<DecryptedGroup
, GroupChange
> commitChange(
443 GroupInfoV2 groupInfoV2
, GroupChange
.Actions
.Builder change
444 ) throws IOException
{
445 final var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupInfoV2
.getMasterKey());
446 final var groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
447 final var previousGroupState
= groupInfoV2
.getGroup();
448 final var nextRevision
= previousGroupState
.getRevision() + 1;
449 final var changeActions
= change
.setRevision(nextRevision
).build();
450 final DecryptedGroupChange decryptedChange
;
451 final DecryptedGroup decryptedGroupState
;
454 decryptedChange
= groupOperations
.decryptChange(changeActions
, getSelfAci().uuid());
455 decryptedGroupState
= DecryptedGroupUtil
.apply(previousGroupState
, decryptedChange
);
456 } catch (VerificationFailedException
| InvalidGroupStateException
| NotAbleToApplyGroupV2ChangeException e
) {
457 throw new IOException(e
);
460 var signedGroupChange
= groupsV2Api
.patchGroup(changeActions
,
461 getGroupAuthForToday(groupSecretParams
),
464 return new Pair
<>(decryptedGroupState
, signedGroupChange
);
467 private GroupChange
commitChange(
468 GroupSecretParams groupSecretParams
,
470 GroupChange
.Actions
.Builder change
,
471 GroupLinkPassword password
472 ) throws IOException
{
473 final var nextRevision
= currentRevision
+ 1;
474 final var changeActions
= change
.setRevision(nextRevision
).build();
476 return groupsV2Api
.patchGroup(changeActions
,
477 getGroupAuthForToday(groupSecretParams
),
478 Optional
.fromNullable(password
).transform(GroupLinkPassword
::serialize
));
481 public DecryptedGroup
getUpdatedDecryptedGroup(
482 DecryptedGroup group
, byte[] signedGroupChange
, GroupMasterKey groupMasterKey
485 final var decryptedGroupChange
= getDecryptedGroupChange(signedGroupChange
, groupMasterKey
);
486 if (decryptedGroupChange
== null) {
489 return DecryptedGroupUtil
.apply(group
, decryptedGroupChange
);
490 } catch (NotAbleToApplyGroupV2ChangeException e
) {
495 private DecryptedGroupChange
getDecryptedGroupChange(byte[] signedGroupChange
, GroupMasterKey groupMasterKey
) {
496 if (signedGroupChange
!= null) {
497 var groupOperations
= groupsV2Operations
.forGroup(GroupSecretParams
.deriveFromMasterKey(groupMasterKey
));
500 return groupOperations
.decryptChange(GroupChange
.parseFrom(signedGroupChange
), true).orNull();
501 } catch (VerificationFailedException
| InvalidGroupStateException
| InvalidProtocolBufferException e
) {
509 private static int currentTimeDays() {
510 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
513 private GroupsV2AuthorizationString
getGroupAuthForToday(
514 final GroupSecretParams groupSecretParams
515 ) throws IOException
{
516 final var today
= currentTimeDays();
517 if (groupApiCredentials
== null || !groupApiCredentials
.containsKey(today
)) {
518 // Returns credentials for the next 7 days
519 groupApiCredentials
= groupsV2Api
.getCredentials(today
);
520 // TODO cache credentials on disk until they expire
522 var authCredentialResponse
= groupApiCredentials
.get(today
);
523 final var aci
= getSelfAci();
525 return groupsV2Api
.getGroupsV2AuthorizationString(aci
, today
, groupSecretParams
, authCredentialResponse
);
526 } catch (VerificationFailedException e
) {
527 throw new IOException(e
);
531 private ACI
getSelfAci() {
532 return addressResolver
.resolveSignalServiceAddress(this.selfRecipientIdProvider
.getSelfRecipientId()).getAci();