1 package org
.asamk
.signal
.manager
.helper
;
3 import com
.google
.protobuf
.InvalidProtocolBufferException
;
5 import org
.asamk
.signal
.manager
.groups
.GroupLinkPassword
;
6 import org
.asamk
.signal
.manager
.groups
.GroupLinkState
;
7 import org
.asamk
.signal
.manager
.groups
.GroupPermission
;
8 import org
.asamk
.signal
.manager
.groups
.GroupUtils
;
9 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfoV2
;
10 import org
.asamk
.signal
.manager
.storage
.recipients
.Profile
;
11 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
12 import org
.asamk
.signal
.manager
.util
.IOUtils
;
13 import org
.signal
.storageservice
.protos
.groups
.AccessControl
;
14 import org
.signal
.storageservice
.protos
.groups
.GroupChange
;
15 import org
.signal
.storageservice
.protos
.groups
.Member
;
16 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroup
;
17 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroupChange
;
18 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroupJoinInfo
;
19 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedPendingMember
;
20 import org
.signal
.zkgroup
.InvalidInputException
;
21 import org
.signal
.zkgroup
.VerificationFailedException
;
22 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
23 import org
.signal
.zkgroup
.groups
.GroupSecretParams
;
24 import org
.signal
.zkgroup
.groups
.UuidCiphertext
;
25 import org
.slf4j
.Logger
;
26 import org
.slf4j
.LoggerFactory
;
27 import org
.whispersystems
.libsignal
.util
.Pair
;
28 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
29 import org
.whispersystems
.signalservice
.api
.groupsv2
.DecryptedGroupUtil
;
30 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupCandidate
;
31 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
32 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Api
;
33 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2AuthorizationString
;
34 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
35 import org
.whispersystems
.signalservice
.api
.groupsv2
.InvalidGroupStateException
;
36 import org
.whispersystems
.signalservice
.api
.groupsv2
.NotAbleToApplyGroupV2ChangeException
;
37 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
38 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
41 import java
.io
.FileInputStream
;
42 import java
.io
.IOException
;
43 import java
.io
.InputStream
;
45 import java
.util
.UUID
;
46 import java
.util
.concurrent
.TimeUnit
;
47 import java
.util
.stream
.Collectors
;
49 public class GroupV2Helper
{
51 private final static Logger logger
= LoggerFactory
.getLogger(GroupV2Helper
.class);
53 private final ProfileKeyCredentialProvider profileKeyCredentialProvider
;
55 private final ProfileProvider profileProvider
;
57 private final SelfRecipientIdProvider selfRecipientIdProvider
;
59 private final GroupsV2Operations groupsV2Operations
;
61 private final GroupsV2Api groupsV2Api
;
63 private final SignalServiceAddressResolver addressResolver
;
66 final ProfileKeyCredentialProvider profileKeyCredentialProvider
,
67 final ProfileProvider profileProvider
,
68 final SelfRecipientIdProvider selfRecipientIdProvider
,
69 final GroupsV2Operations groupsV2Operations
,
70 final GroupsV2Api groupsV2Api
,
71 final SignalServiceAddressResolver addressResolver
73 this.profileKeyCredentialProvider
= profileKeyCredentialProvider
;
74 this.profileProvider
= profileProvider
;
75 this.selfRecipientIdProvider
= selfRecipientIdProvider
;
76 this.groupsV2Operations
= groupsV2Operations
;
77 this.groupsV2Api
= groupsV2Api
;
78 this.addressResolver
= addressResolver
;
81 public DecryptedGroup
getDecryptedGroup(final GroupSecretParams groupSecretParams
) {
83 final var groupsV2AuthorizationString
= getGroupAuthForToday(groupSecretParams
);
84 return groupsV2Api
.getGroup(groupSecretParams
, groupsV2AuthorizationString
);
85 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
86 logger
.warn("Failed to retrieve Group V2 info, ignoring: {}", e
.getMessage());
91 public DecryptedGroupJoinInfo
getDecryptedGroupJoinInfo(
92 GroupMasterKey groupMasterKey
, GroupLinkPassword password
93 ) throws IOException
, GroupLinkNotActiveException
{
94 var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
96 return groupsV2Api
.getGroupJoinInfo(groupSecretParams
,
97 Optional
.fromNullable(password
).transform(GroupLinkPassword
::serialize
),
98 getGroupAuthForToday(groupSecretParams
));
101 public Pair
<GroupInfoV2
, DecryptedGroup
> createGroup(
102 String name
, Set
<RecipientId
> members
, File avatarFile
103 ) throws IOException
{
104 final var avatarBytes
= readAvatarBytes(avatarFile
);
105 final var newGroup
= buildNewGroup(name
, members
, avatarBytes
);
106 if (newGroup
== null) {
110 final var groupSecretParams
= newGroup
.getGroupSecretParams();
112 final GroupsV2AuthorizationString groupAuthForToday
;
113 final DecryptedGroup decryptedGroup
;
115 groupAuthForToday
= getGroupAuthForToday(groupSecretParams
);
116 groupsV2Api
.putNewGroup(newGroup
, groupAuthForToday
);
117 decryptedGroup
= groupsV2Api
.getGroup(groupSecretParams
, groupAuthForToday
);
118 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
119 logger
.warn("Failed to create V2 group: {}", e
.getMessage());
122 if (decryptedGroup
== null) {
123 logger
.warn("Failed to create V2 group, unknown error!");
127 final var groupId
= GroupUtils
.getGroupIdV2(groupSecretParams
);
128 final var masterKey
= groupSecretParams
.getMasterKey();
129 var g
= new GroupInfoV2(groupId
, masterKey
);
131 return new Pair
<>(g
, decryptedGroup
);
134 private byte[] readAvatarBytes(final File avatarFile
) throws IOException
{
135 final byte[] avatarBytes
;
136 try (InputStream avatar
= avatarFile
== null ?
null : new FileInputStream(avatarFile
)) {
137 avatarBytes
= avatar
== null ?
null : IOUtils
.readFully(avatar
);
142 private GroupsV2Operations
.NewGroup
buildNewGroup(
143 String name
, Set
<RecipientId
> members
, byte[] avatar
145 final var profileKeyCredential
= profileKeyCredentialProvider
.getProfileKeyCredential(selfRecipientIdProvider
.getSelfRecipientId());
146 if (profileKeyCredential
== null) {
147 logger
.warn("Cannot create a V2 group as self does not have a versioned profile");
151 if (!areMembersValid(members
)) return null;
153 var self
= new GroupCandidate(addressResolver
.resolveSignalServiceAddress(selfRecipientIdProvider
.getSelfRecipientId())
155 .orNull(), Optional
.fromNullable(profileKeyCredential
));
156 var candidates
= members
.stream()
157 .map(member
-> new GroupCandidate(addressResolver
.resolveSignalServiceAddress(member
).getUuid().get(),
158 Optional
.fromNullable(profileKeyCredentialProvider
.getProfileKeyCredential(member
))))
159 .collect(Collectors
.toSet());
161 final var groupSecretParams
= GroupSecretParams
.generate();
162 return groupsV2Operations
.createNewGroup(groupSecretParams
,
164 Optional
.fromNullable(avatar
),
171 private boolean areMembersValid(final Set
<RecipientId
> members
) {
172 final var noUuidCapability
= members
.stream()
173 .map(addressResolver
::resolveSignalServiceAddress
)
174 .filter(address
-> !address
.getUuid().isPresent())
175 .map(SignalServiceAddress
::getNumber
)
177 .collect(Collectors
.toSet());
178 if (noUuidCapability
.size() > 0) {
179 logger
.warn("Cannot create a V2 group as some members don't have a UUID: {}",
180 String
.join(", ", noUuidCapability
));
184 final var noGv2Capability
= members
.stream()
185 .map(profileProvider
::getProfile
)
186 .filter(profile
-> profile
!= null && !profile
.getCapabilities().contains(Profile
.Capability
.gv2
))
187 .collect(Collectors
.toSet());
188 if (noGv2Capability
.size() > 0) {
189 logger
.warn("Cannot create a V2 group as some members don't support Groups V2: {}",
190 noGv2Capability
.stream().map(Profile
::getDisplayName
).collect(Collectors
.joining(", ")));
197 public Pair
<DecryptedGroup
, GroupChange
> updateGroup(
198 GroupInfoV2 groupInfoV2
, String name
, String description
, File avatarFile
199 ) throws IOException
{
200 final var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupInfoV2
.getMasterKey());
201 var groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
203 var change
= name
!= null ? groupOperations
.createModifyGroupTitle(name
) : GroupChange
.Actions
.newBuilder();
205 if (description
!= null) {
206 change
.setModifyDescription(groupOperations
.createModifyGroupDescriptionAction(description
));
209 if (avatarFile
!= null) {
210 final var avatarBytes
= readAvatarBytes(avatarFile
);
211 var avatarCdnKey
= groupsV2Api
.uploadAvatar(avatarBytes
,
213 getGroupAuthForToday(groupSecretParams
));
214 change
.setModifyAvatar(GroupChange
.Actions
.ModifyAvatarAction
.newBuilder().setAvatar(avatarCdnKey
));
217 final var uuid
= addressResolver
.resolveSignalServiceAddress(this.selfRecipientIdProvider
.getSelfRecipientId())
219 if (uuid
.isPresent()) {
220 change
.setSourceUuid(UuidUtil
.toByteString(uuid
.get()));
223 return commitChange(groupInfoV2
, change
);
226 public Pair
<DecryptedGroup
, GroupChange
> addMembers(
227 GroupInfoV2 groupInfoV2
, Set
<RecipientId
> newMembers
228 ) throws IOException
{
229 GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
231 if (!areMembersValid(newMembers
)) {
232 throw new IOException("Failed to update group");
235 var candidates
= newMembers
.stream()
236 .map(member
-> new GroupCandidate(addressResolver
.resolveSignalServiceAddress(member
).getUuid().get(),
237 Optional
.fromNullable(profileKeyCredentialProvider
.getProfileKeyCredential(member
))))
238 .collect(Collectors
.toSet());
240 final var uuid
= addressResolver
.resolveSignalServiceAddress(selfRecipientIdProvider
.getSelfRecipientId())
243 final var change
= groupOperations
.createModifyGroupMembershipChange(candidates
, uuid
);
245 change
.setSourceUuid(UuidUtil
.toByteString(uuid
));
247 return commitChange(groupInfoV2
, change
);
250 public Pair
<DecryptedGroup
, GroupChange
> leaveGroup(
251 GroupInfoV2 groupInfoV2
, Set
<RecipientId
> membersToMakeAdmin
252 ) throws IOException
{
253 var pendingMembersList
= groupInfoV2
.getGroup().getPendingMembersList();
254 final var selfUuid
= addressResolver
.resolveSignalServiceAddress(selfRecipientIdProvider
.getSelfRecipientId())
257 var selfPendingMember
= DecryptedGroupUtil
.findPendingByUuid(pendingMembersList
, selfUuid
);
259 if (selfPendingMember
.isPresent()) {
260 return revokeInvites(groupInfoV2
, Set
.of(selfPendingMember
.get()));
263 final var adminUuids
= membersToMakeAdmin
.stream()
264 .map(addressResolver
::resolveSignalServiceAddress
)
265 .map(SignalServiceAddress
::getUuid
)
267 .collect(Collectors
.toList());
268 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
269 return commitChange(groupInfoV2
, groupOperations
.createLeaveAndPromoteMembersToAdmin(selfUuid
, adminUuids
));
272 public Pair
<DecryptedGroup
, GroupChange
> removeMembers(
273 GroupInfoV2 groupInfoV2
, Set
<RecipientId
> members
274 ) throws IOException
{
275 final var memberUuids
= members
.stream()
276 .map(addressResolver
::resolveSignalServiceAddress
)
277 .map(SignalServiceAddress
::getUuid
)
278 .filter(Optional
::isPresent
)
280 .collect(Collectors
.toSet());
281 return ejectMembers(groupInfoV2
, memberUuids
);
284 public Pair
<DecryptedGroup
, GroupChange
> revokeInvitedMembers(
285 GroupInfoV2 groupInfoV2
, Set
<RecipientId
> members
286 ) throws IOException
{
287 var pendingMembersList
= groupInfoV2
.getGroup().getPendingMembersList();
288 final var memberUuids
= members
.stream()
289 .map(addressResolver
::resolveSignalServiceAddress
)
290 .map(SignalServiceAddress
::getUuid
)
291 .filter(Optional
::isPresent
)
293 .map(uuid
-> DecryptedGroupUtil
.findPendingByUuid(pendingMembersList
, uuid
))
294 .filter(Optional
::isPresent
)
296 .collect(Collectors
.toSet());
297 return revokeInvites(groupInfoV2
, memberUuids
);
300 public Pair
<DecryptedGroup
, GroupChange
> resetGroupLinkPassword(GroupInfoV2 groupInfoV2
) throws IOException
{
301 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
302 final var newGroupLinkPassword
= GroupLinkPassword
.createNew().serialize();
303 final var change
= groupOperations
.createModifyGroupLinkPasswordChange(newGroupLinkPassword
);
304 return commitChange(groupInfoV2
, change
);
307 public Pair
<DecryptedGroup
, GroupChange
> setGroupLinkState(
308 GroupInfoV2 groupInfoV2
, GroupLinkState state
309 ) throws IOException
{
310 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
312 final var accessRequired
= toAccessControl(state
);
313 final var requiresNewPassword
= state
!= GroupLinkState
.DISABLED
&& groupInfoV2
.getGroup()
314 .getInviteLinkPassword()
317 final var change
= requiresNewPassword ? groupOperations
.createModifyGroupLinkPasswordAndRightsChange(
318 GroupLinkPassword
.createNew().serialize(),
319 accessRequired
) : groupOperations
.createChangeJoinByLinkRights(accessRequired
);
320 return commitChange(groupInfoV2
, change
);
323 public Pair
<DecryptedGroup
, GroupChange
> setEditDetailsPermission(
324 GroupInfoV2 groupInfoV2
, GroupPermission permission
325 ) throws IOException
{
326 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
328 final var accessRequired
= toAccessControl(permission
);
329 final var change
= groupOperations
.createChangeAttributesRights(accessRequired
);
330 return commitChange(groupInfoV2
, change
);
333 public Pair
<DecryptedGroup
, GroupChange
> setAddMemberPermission(
334 GroupInfoV2 groupInfoV2
, GroupPermission permission
335 ) throws IOException
{
336 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
338 final var accessRequired
= toAccessControl(permission
);
339 final var change
= groupOperations
.createChangeMembershipRights(accessRequired
);
340 return commitChange(groupInfoV2
, change
);
343 public GroupChange
joinGroup(
344 GroupMasterKey groupMasterKey
,
345 GroupLinkPassword groupLinkPassword
,
346 DecryptedGroupJoinInfo decryptedGroupJoinInfo
347 ) throws IOException
{
348 final var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
349 final var groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
351 final var selfRecipientId
= this.selfRecipientIdProvider
.getSelfRecipientId();
352 final var profileKeyCredential
= profileKeyCredentialProvider
.getProfileKeyCredential(selfRecipientId
);
353 if (profileKeyCredential
== null) {
354 throw new IOException("Cannot join a V2 group as self does not have a versioned profile");
357 var requestToJoin
= decryptedGroupJoinInfo
.getAddFromInviteLink() == AccessControl
.AccessRequired
.ADMINISTRATOR
;
358 var change
= requestToJoin
359 ? groupOperations
.createGroupJoinRequest(profileKeyCredential
)
360 : groupOperations
.createGroupJoinDirect(profileKeyCredential
);
362 change
.setSourceUuid(UuidUtil
.toByteString(addressResolver
.resolveSignalServiceAddress(selfRecipientId
)
366 return commitChange(groupSecretParams
, decryptedGroupJoinInfo
.getRevision(), change
, groupLinkPassword
);
369 public Pair
<DecryptedGroup
, GroupChange
> acceptInvite(GroupInfoV2 groupInfoV2
) throws IOException
{
370 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
372 final var selfRecipientId
= this.selfRecipientIdProvider
.getSelfRecipientId();
373 final var profileKeyCredential
= profileKeyCredentialProvider
.getProfileKeyCredential(selfRecipientId
);
374 if (profileKeyCredential
== null) {
375 throw new IOException("Cannot join a V2 group as self does not have a versioned profile");
378 final var change
= groupOperations
.createAcceptInviteChange(profileKeyCredential
);
380 final var uuid
= addressResolver
.resolveSignalServiceAddress(selfRecipientId
).getUuid();
381 if (uuid
.isPresent()) {
382 change
.setSourceUuid(UuidUtil
.toByteString(uuid
.get()));
385 return commitChange(groupInfoV2
, change
);
388 public Pair
<DecryptedGroup
, GroupChange
> setMemberAdmin(
389 GroupInfoV2 groupInfoV2
, RecipientId recipientId
, boolean admin
390 ) throws IOException
{
391 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
392 final var address
= addressResolver
.resolveSignalServiceAddress(recipientId
);
393 final var newRole
= admin ? Member
.Role
.ADMINISTRATOR
: Member
.Role
.DEFAULT
;
394 final var change
= groupOperations
.createChangeMemberRole(address
.getUuid().get(), newRole
);
395 return commitChange(groupInfoV2
, change
);
398 public Pair
<DecryptedGroup
, GroupChange
> setMessageExpirationTimer(
399 GroupInfoV2 groupInfoV2
, int messageExpirationTimer
400 ) throws IOException
{
401 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
402 final var change
= groupOperations
.createModifyGroupTimerChange(messageExpirationTimer
);
403 return commitChange(groupInfoV2
, change
);
406 public Pair
<DecryptedGroup
, GroupChange
> setIsAnnouncementGroup(
407 GroupInfoV2 groupInfoV2
, boolean isAnnouncementGroup
408 ) throws IOException
{
409 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
410 final var change
= groupOperations
.createAnnouncementGroupChange(isAnnouncementGroup
);
411 return commitChange(groupInfoV2
, change
);
414 private AccessControl
.AccessRequired
toAccessControl(final GroupLinkState state
) {
417 return AccessControl
.AccessRequired
.UNSATISFIABLE
;
419 return AccessControl
.AccessRequired
.ANY
;
420 case ENABLED_WITH_APPROVAL
:
421 return AccessControl
.AccessRequired
.ADMINISTRATOR
;
423 throw new AssertionError();
427 private AccessControl
.AccessRequired
toAccessControl(final GroupPermission permission
) {
428 switch (permission
) {
430 return AccessControl
.AccessRequired
.MEMBER
;
432 return AccessControl
.AccessRequired
.ADMINISTRATOR
;
434 throw new AssertionError();
438 private GroupsV2Operations
.GroupOperations
getGroupOperations(final GroupInfoV2 groupInfoV2
) {
439 final var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupInfoV2
.getMasterKey());
440 return groupsV2Operations
.forGroup(groupSecretParams
);
443 private Pair
<DecryptedGroup
, GroupChange
> revokeInvites(
444 GroupInfoV2 groupInfoV2
, Set
<DecryptedPendingMember
> pendingMembers
445 ) throws IOException
{
446 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
447 final var uuidCipherTexts
= pendingMembers
.stream().map(member
-> {
449 return new UuidCiphertext(member
.getUuidCipherText().toByteArray());
450 } catch (InvalidInputException e
) {
451 throw new AssertionError(e
);
453 }).collect(Collectors
.toSet());
454 return commitChange(groupInfoV2
, groupOperations
.createRemoveInvitationChange(uuidCipherTexts
));
457 private Pair
<DecryptedGroup
, GroupChange
> ejectMembers(
458 GroupInfoV2 groupInfoV2
, Set
<UUID
> uuids
459 ) throws IOException
{
460 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
461 return commitChange(groupInfoV2
, groupOperations
.createRemoveMembersChange(uuids
));
464 private Pair
<DecryptedGroup
, GroupChange
> commitChange(
465 GroupInfoV2 groupInfoV2
, GroupChange
.Actions
.Builder change
466 ) throws IOException
{
467 final var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupInfoV2
.getMasterKey());
468 final var groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
469 final var previousGroupState
= groupInfoV2
.getGroup();
470 final var nextRevision
= previousGroupState
.getRevision() + 1;
471 final var changeActions
= change
.setRevision(nextRevision
).build();
472 final DecryptedGroupChange decryptedChange
;
473 final DecryptedGroup decryptedGroupState
;
476 decryptedChange
= groupOperations
.decryptChange(changeActions
,
477 addressResolver
.resolveSignalServiceAddress(selfRecipientIdProvider
.getSelfRecipientId())
480 decryptedGroupState
= DecryptedGroupUtil
.apply(previousGroupState
, decryptedChange
);
481 } catch (VerificationFailedException
| InvalidGroupStateException
| NotAbleToApplyGroupV2ChangeException e
) {
482 throw new IOException(e
);
485 var signedGroupChange
= groupsV2Api
.patchGroup(changeActions
,
486 getGroupAuthForToday(groupSecretParams
),
489 return new Pair
<>(decryptedGroupState
, signedGroupChange
);
492 private GroupChange
commitChange(
493 GroupSecretParams groupSecretParams
,
495 GroupChange
.Actions
.Builder change
,
496 GroupLinkPassword password
497 ) throws IOException
{
498 final var nextRevision
= currentRevision
+ 1;
499 final var changeActions
= change
.setRevision(nextRevision
).build();
501 return groupsV2Api
.patchGroup(changeActions
,
502 getGroupAuthForToday(groupSecretParams
),
503 Optional
.fromNullable(password
).transform(GroupLinkPassword
::serialize
));
506 public DecryptedGroup
getUpdatedDecryptedGroup(
507 DecryptedGroup group
, byte[] signedGroupChange
, GroupMasterKey groupMasterKey
510 final var decryptedGroupChange
= getDecryptedGroupChange(signedGroupChange
, groupMasterKey
);
511 if (decryptedGroupChange
== null) {
514 return DecryptedGroupUtil
.apply(group
, decryptedGroupChange
);
515 } catch (NotAbleToApplyGroupV2ChangeException e
) {
520 private DecryptedGroupChange
getDecryptedGroupChange(byte[] signedGroupChange
, GroupMasterKey groupMasterKey
) {
521 if (signedGroupChange
!= null) {
522 var groupOperations
= groupsV2Operations
.forGroup(GroupSecretParams
.deriveFromMasterKey(groupMasterKey
));
525 return groupOperations
.decryptChange(GroupChange
.parseFrom(signedGroupChange
), true).orNull();
526 } catch (VerificationFailedException
| InvalidGroupStateException
| InvalidProtocolBufferException e
) {
534 private static int currentTimeDays() {
535 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
538 private GroupsV2AuthorizationString
getGroupAuthForToday(
539 final GroupSecretParams groupSecretParams
540 ) throws IOException
{
541 final var today
= currentTimeDays();
542 // Returns credentials for the next 7 days
543 final var credentials
= groupsV2Api
.getCredentials(today
);
544 // TODO cache credentials until they expire
545 var authCredentialResponse
= credentials
.get(today
);
546 final var uuid
= addressResolver
.resolveSignalServiceAddress(this.selfRecipientIdProvider
.getSelfRecipientId())
550 return groupsV2Api
.getGroupsV2AuthorizationString(uuid
, today
, groupSecretParams
, authCredentialResponse
);
551 } catch (VerificationFailedException e
) {
552 throw new IOException(e
);