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
.groups
.NotAGroupMemberException
;
10 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfoV2
;
11 import org
.asamk
.signal
.manager
.storage
.recipients
.Profile
;
12 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
13 import org
.asamk
.signal
.manager
.util
.IOUtils
;
14 import org
.signal
.storageservice
.protos
.groups
.AccessControl
;
15 import org
.signal
.storageservice
.protos
.groups
.GroupChange
;
16 import org
.signal
.storageservice
.protos
.groups
.Member
;
17 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroup
;
18 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroupChange
;
19 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroupJoinInfo
;
20 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedPendingMember
;
21 import org
.signal
.zkgroup
.InvalidInputException
;
22 import org
.signal
.zkgroup
.VerificationFailedException
;
23 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
24 import org
.signal
.zkgroup
.groups
.GroupSecretParams
;
25 import org
.signal
.zkgroup
.groups
.UuidCiphertext
;
26 import org
.slf4j
.Logger
;
27 import org
.slf4j
.LoggerFactory
;
28 import org
.whispersystems
.libsignal
.util
.Pair
;
29 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
30 import org
.whispersystems
.signalservice
.api
.groupsv2
.DecryptedGroupUtil
;
31 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupCandidate
;
32 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
33 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Api
;
34 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2AuthorizationString
;
35 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
36 import org
.whispersystems
.signalservice
.api
.groupsv2
.InvalidGroupStateException
;
37 import org
.whispersystems
.signalservice
.api
.groupsv2
.NotAbleToApplyGroupV2ChangeException
;
38 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
39 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NonSuccessfulResponseCodeException
;
40 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
43 import java
.io
.FileInputStream
;
44 import java
.io
.IOException
;
45 import java
.io
.InputStream
;
47 import java
.util
.UUID
;
48 import java
.util
.concurrent
.TimeUnit
;
49 import java
.util
.stream
.Collectors
;
51 public class GroupV2Helper
{
53 private final static Logger logger
= LoggerFactory
.getLogger(GroupV2Helper
.class);
55 private final ProfileKeyCredentialProvider profileKeyCredentialProvider
;
57 private final ProfileProvider profileProvider
;
59 private final SelfRecipientIdProvider selfRecipientIdProvider
;
61 private final GroupsV2Operations groupsV2Operations
;
63 private final GroupsV2Api groupsV2Api
;
65 private final SignalServiceAddressResolver addressResolver
;
68 final ProfileKeyCredentialProvider profileKeyCredentialProvider
,
69 final ProfileProvider profileProvider
,
70 final SelfRecipientIdProvider selfRecipientIdProvider
,
71 final GroupsV2Operations groupsV2Operations
,
72 final GroupsV2Api groupsV2Api
,
73 final SignalServiceAddressResolver addressResolver
75 this.profileKeyCredentialProvider
= profileKeyCredentialProvider
;
76 this.profileProvider
= profileProvider
;
77 this.selfRecipientIdProvider
= selfRecipientIdProvider
;
78 this.groupsV2Operations
= groupsV2Operations
;
79 this.groupsV2Api
= groupsV2Api
;
80 this.addressResolver
= addressResolver
;
83 public DecryptedGroup
getDecryptedGroup(final GroupSecretParams groupSecretParams
) throws NotAGroupMemberException
{
85 final var groupsV2AuthorizationString
= getGroupAuthForToday(groupSecretParams
);
86 return groupsV2Api
.getGroup(groupSecretParams
, groupsV2AuthorizationString
);
87 } catch (NonSuccessfulResponseCodeException e
) {
88 if (e
.getCode() == 403) {
89 throw new NotAGroupMemberException(GroupUtils
.getGroupIdV2(groupSecretParams
), null);
91 logger
.warn("Failed to retrieve Group V2 info, ignoring: {}", e
.getMessage());
93 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
94 logger
.warn("Failed to retrieve Group V2 info, ignoring: {}", e
.getMessage());
99 public DecryptedGroupJoinInfo
getDecryptedGroupJoinInfo(
100 GroupMasterKey groupMasterKey
, GroupLinkPassword password
101 ) throws IOException
, GroupLinkNotActiveException
{
102 var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
104 return groupsV2Api
.getGroupJoinInfo(groupSecretParams
,
105 Optional
.fromNullable(password
).transform(GroupLinkPassword
::serialize
),
106 getGroupAuthForToday(groupSecretParams
));
109 public Pair
<GroupInfoV2
, DecryptedGroup
> createGroup(
110 String name
, Set
<RecipientId
> members
, File avatarFile
111 ) throws IOException
{
112 final var avatarBytes
= readAvatarBytes(avatarFile
);
113 final var newGroup
= buildNewGroup(name
, members
, avatarBytes
);
114 if (newGroup
== null) {
118 final var groupSecretParams
= newGroup
.getGroupSecretParams();
120 final GroupsV2AuthorizationString groupAuthForToday
;
121 final DecryptedGroup decryptedGroup
;
123 groupAuthForToday
= getGroupAuthForToday(groupSecretParams
);
124 groupsV2Api
.putNewGroup(newGroup
, groupAuthForToday
);
125 decryptedGroup
= groupsV2Api
.getGroup(groupSecretParams
, groupAuthForToday
);
126 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
127 logger
.warn("Failed to create V2 group: {}", e
.getMessage());
130 if (decryptedGroup
== null) {
131 logger
.warn("Failed to create V2 group, unknown error!");
135 final var groupId
= GroupUtils
.getGroupIdV2(groupSecretParams
);
136 final var masterKey
= groupSecretParams
.getMasterKey();
137 var g
= new GroupInfoV2(groupId
, masterKey
);
139 return new Pair
<>(g
, decryptedGroup
);
142 private byte[] readAvatarBytes(final File avatarFile
) throws IOException
{
143 final byte[] avatarBytes
;
144 try (InputStream avatar
= avatarFile
== null ?
null : new FileInputStream(avatarFile
)) {
145 avatarBytes
= avatar
== null ?
null : IOUtils
.readFully(avatar
);
150 private GroupsV2Operations
.NewGroup
buildNewGroup(
151 String name
, Set
<RecipientId
> members
, byte[] avatar
153 final var profileKeyCredential
= profileKeyCredentialProvider
.getProfileKeyCredential(selfRecipientIdProvider
.getSelfRecipientId());
154 if (profileKeyCredential
== null) {
155 logger
.warn("Cannot create a V2 group as self does not have a versioned profile");
159 if (!areMembersValid(members
)) return null;
161 var self
= new GroupCandidate(getSelfUuid(), Optional
.fromNullable(profileKeyCredential
));
162 var candidates
= members
.stream()
163 .map(member
-> new GroupCandidate(addressResolver
.resolveSignalServiceAddress(member
).getUuid(),
164 Optional
.fromNullable(profileKeyCredentialProvider
.getProfileKeyCredential(member
))))
165 .collect(Collectors
.toSet());
167 final var groupSecretParams
= GroupSecretParams
.generate();
168 return groupsV2Operations
.createNewGroup(groupSecretParams
,
170 Optional
.fromNullable(avatar
),
177 private boolean areMembersValid(final Set
<RecipientId
> members
) {
178 final var noGv2Capability
= members
.stream()
179 .map(profileProvider
::getProfile
)
180 .filter(profile
-> profile
!= null && !profile
.getCapabilities().contains(Profile
.Capability
.gv2
))
181 .collect(Collectors
.toSet());
182 if (noGv2Capability
.size() > 0) {
183 logger
.warn("Cannot create a V2 group as some members don't support Groups V2: {}",
184 noGv2Capability
.stream().map(Profile
::getDisplayName
).collect(Collectors
.joining(", ")));
191 public Pair
<DecryptedGroup
, GroupChange
> updateGroup(
192 GroupInfoV2 groupInfoV2
, String name
, String description
, File avatarFile
193 ) throws IOException
{
194 final var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupInfoV2
.getMasterKey());
195 var groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
197 var change
= name
!= null ? groupOperations
.createModifyGroupTitle(name
) : GroupChange
.Actions
.newBuilder();
199 if (description
!= null) {
200 change
.setModifyDescription(groupOperations
.createModifyGroupDescriptionAction(description
));
203 if (avatarFile
!= null) {
204 final var avatarBytes
= readAvatarBytes(avatarFile
);
205 var avatarCdnKey
= groupsV2Api
.uploadAvatar(avatarBytes
,
207 getGroupAuthForToday(groupSecretParams
));
208 change
.setModifyAvatar(GroupChange
.Actions
.ModifyAvatarAction
.newBuilder().setAvatar(avatarCdnKey
));
211 final var uuid
= getSelfUuid();
212 change
.setSourceUuid(UuidUtil
.toByteString(uuid
));
214 return commitChange(groupInfoV2
, change
);
217 public Pair
<DecryptedGroup
, GroupChange
> addMembers(
218 GroupInfoV2 groupInfoV2
, Set
<RecipientId
> newMembers
219 ) throws IOException
{
220 GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
222 if (!areMembersValid(newMembers
)) {
223 throw new IOException("Failed to update group");
226 var candidates
= newMembers
.stream()
227 .map(member
-> new GroupCandidate(addressResolver
.resolveSignalServiceAddress(member
).getUuid(),
228 Optional
.fromNullable(profileKeyCredentialProvider
.getProfileKeyCredential(member
))))
229 .collect(Collectors
.toSet());
231 final var uuid
= getSelfUuid();
232 final var change
= groupOperations
.createModifyGroupMembershipChange(candidates
, uuid
);
234 change
.setSourceUuid(UuidUtil
.toByteString(uuid
));
236 return commitChange(groupInfoV2
, change
);
239 public Pair
<DecryptedGroup
, GroupChange
> leaveGroup(
240 GroupInfoV2 groupInfoV2
, Set
<RecipientId
> membersToMakeAdmin
241 ) throws IOException
{
242 var pendingMembersList
= groupInfoV2
.getGroup().getPendingMembersList();
243 final var selfUuid
= getSelfUuid();
244 var selfPendingMember
= DecryptedGroupUtil
.findPendingByUuid(pendingMembersList
, selfUuid
);
246 if (selfPendingMember
.isPresent()) {
247 return revokeInvites(groupInfoV2
, Set
.of(selfPendingMember
.get()));
250 final var adminUuids
= membersToMakeAdmin
.stream()
251 .map(addressResolver
::resolveSignalServiceAddress
)
252 .map(SignalServiceAddress
::getUuid
)
253 .collect(Collectors
.toList());
254 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
255 return commitChange(groupInfoV2
, groupOperations
.createLeaveAndPromoteMembersToAdmin(selfUuid
, adminUuids
));
258 public Pair
<DecryptedGroup
, GroupChange
> removeMembers(
259 GroupInfoV2 groupInfoV2
, Set
<RecipientId
> members
260 ) throws IOException
{
261 final var memberUuids
= members
.stream()
262 .map(addressResolver
::resolveSignalServiceAddress
)
263 .map(SignalServiceAddress
::getUuid
)
264 .collect(Collectors
.toSet());
265 return ejectMembers(groupInfoV2
, memberUuids
);
268 public Pair
<DecryptedGroup
, GroupChange
> revokeInvitedMembers(
269 GroupInfoV2 groupInfoV2
, Set
<RecipientId
> members
270 ) throws IOException
{
271 var pendingMembersList
= groupInfoV2
.getGroup().getPendingMembersList();
272 final var memberUuids
= members
.stream()
273 .map(addressResolver
::resolveSignalServiceAddress
)
274 .map(SignalServiceAddress
::getUuid
)
275 .map(uuid
-> DecryptedGroupUtil
.findPendingByUuid(pendingMembersList
, uuid
))
276 .filter(Optional
::isPresent
)
278 .collect(Collectors
.toSet());
279 return revokeInvites(groupInfoV2
, memberUuids
);
282 public Pair
<DecryptedGroup
, GroupChange
> resetGroupLinkPassword(GroupInfoV2 groupInfoV2
) throws IOException
{
283 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
284 final var newGroupLinkPassword
= GroupLinkPassword
.createNew().serialize();
285 final var change
= groupOperations
.createModifyGroupLinkPasswordChange(newGroupLinkPassword
);
286 return commitChange(groupInfoV2
, change
);
289 public Pair
<DecryptedGroup
, GroupChange
> setGroupLinkState(
290 GroupInfoV2 groupInfoV2
, GroupLinkState state
291 ) throws IOException
{
292 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
294 final var accessRequired
= toAccessControl(state
);
295 final var requiresNewPassword
= state
!= GroupLinkState
.DISABLED
&& groupInfoV2
.getGroup()
296 .getInviteLinkPassword()
299 final var change
= requiresNewPassword ? groupOperations
.createModifyGroupLinkPasswordAndRightsChange(
300 GroupLinkPassword
.createNew().serialize(),
301 accessRequired
) : groupOperations
.createChangeJoinByLinkRights(accessRequired
);
302 return commitChange(groupInfoV2
, change
);
305 public Pair
<DecryptedGroup
, GroupChange
> setEditDetailsPermission(
306 GroupInfoV2 groupInfoV2
, GroupPermission permission
307 ) throws IOException
{
308 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
310 final var accessRequired
= toAccessControl(permission
);
311 final var change
= groupOperations
.createChangeAttributesRights(accessRequired
);
312 return commitChange(groupInfoV2
, change
);
315 public Pair
<DecryptedGroup
, GroupChange
> setAddMemberPermission(
316 GroupInfoV2 groupInfoV2
, GroupPermission permission
317 ) throws IOException
{
318 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
320 final var accessRequired
= toAccessControl(permission
);
321 final var change
= groupOperations
.createChangeMembershipRights(accessRequired
);
322 return commitChange(groupInfoV2
, change
);
325 public GroupChange
joinGroup(
326 GroupMasterKey groupMasterKey
,
327 GroupLinkPassword groupLinkPassword
,
328 DecryptedGroupJoinInfo decryptedGroupJoinInfo
329 ) throws IOException
{
330 final var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
331 final var groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
333 final var selfRecipientId
= this.selfRecipientIdProvider
.getSelfRecipientId();
334 final var profileKeyCredential
= profileKeyCredentialProvider
.getProfileKeyCredential(selfRecipientId
);
335 if (profileKeyCredential
== null) {
336 throw new IOException("Cannot join a V2 group as self does not have a versioned profile");
339 var requestToJoin
= decryptedGroupJoinInfo
.getAddFromInviteLink() == AccessControl
.AccessRequired
.ADMINISTRATOR
;
340 var change
= requestToJoin
341 ? groupOperations
.createGroupJoinRequest(profileKeyCredential
)
342 : groupOperations
.createGroupJoinDirect(profileKeyCredential
);
344 change
.setSourceUuid(UuidUtil
.toByteString(addressResolver
.resolveSignalServiceAddress(selfRecipientId
)
347 return commitChange(groupSecretParams
, decryptedGroupJoinInfo
.getRevision(), change
, groupLinkPassword
);
350 public Pair
<DecryptedGroup
, GroupChange
> acceptInvite(GroupInfoV2 groupInfoV2
) throws IOException
{
351 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
353 final var selfRecipientId
= this.selfRecipientIdProvider
.getSelfRecipientId();
354 final var profileKeyCredential
= profileKeyCredentialProvider
.getProfileKeyCredential(selfRecipientId
);
355 if (profileKeyCredential
== null) {
356 throw new IOException("Cannot join a V2 group as self does not have a versioned profile");
359 final var change
= groupOperations
.createAcceptInviteChange(profileKeyCredential
);
361 final var uuid
= addressResolver
.resolveSignalServiceAddress(selfRecipientId
).getUuid();
362 change
.setSourceUuid(UuidUtil
.toByteString(uuid
));
364 return commitChange(groupInfoV2
, change
);
367 public Pair
<DecryptedGroup
, GroupChange
> setMemberAdmin(
368 GroupInfoV2 groupInfoV2
, RecipientId recipientId
, boolean admin
369 ) throws IOException
{
370 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
371 final var address
= addressResolver
.resolveSignalServiceAddress(recipientId
);
372 final var newRole
= admin ? Member
.Role
.ADMINISTRATOR
: Member
.Role
.DEFAULT
;
373 final var change
= groupOperations
.createChangeMemberRole(address
.getUuid(), newRole
);
374 return commitChange(groupInfoV2
, change
);
377 public Pair
<DecryptedGroup
, GroupChange
> setMessageExpirationTimer(
378 GroupInfoV2 groupInfoV2
, int messageExpirationTimer
379 ) throws IOException
{
380 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
381 final var change
= groupOperations
.createModifyGroupTimerChange(messageExpirationTimer
);
382 return commitChange(groupInfoV2
, change
);
385 public Pair
<DecryptedGroup
, GroupChange
> setIsAnnouncementGroup(
386 GroupInfoV2 groupInfoV2
, boolean isAnnouncementGroup
387 ) throws IOException
{
388 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
389 final var change
= groupOperations
.createAnnouncementGroupChange(isAnnouncementGroup
);
390 return commitChange(groupInfoV2
, change
);
393 private AccessControl
.AccessRequired
toAccessControl(final GroupLinkState state
) {
396 return AccessControl
.AccessRequired
.UNSATISFIABLE
;
398 return AccessControl
.AccessRequired
.ANY
;
399 case ENABLED_WITH_APPROVAL
:
400 return AccessControl
.AccessRequired
.ADMINISTRATOR
;
402 throw new AssertionError();
406 private AccessControl
.AccessRequired
toAccessControl(final GroupPermission permission
) {
407 switch (permission
) {
409 return AccessControl
.AccessRequired
.MEMBER
;
411 return AccessControl
.AccessRequired
.ADMINISTRATOR
;
413 throw new AssertionError();
417 private GroupsV2Operations
.GroupOperations
getGroupOperations(final GroupInfoV2 groupInfoV2
) {
418 final var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupInfoV2
.getMasterKey());
419 return groupsV2Operations
.forGroup(groupSecretParams
);
422 private Pair
<DecryptedGroup
, GroupChange
> revokeInvites(
423 GroupInfoV2 groupInfoV2
, Set
<DecryptedPendingMember
> pendingMembers
424 ) throws IOException
{
425 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
426 final var uuidCipherTexts
= pendingMembers
.stream().map(member
-> {
428 return new UuidCiphertext(member
.getUuidCipherText().toByteArray());
429 } catch (InvalidInputException e
) {
430 throw new AssertionError(e
);
432 }).collect(Collectors
.toSet());
433 return commitChange(groupInfoV2
, groupOperations
.createRemoveInvitationChange(uuidCipherTexts
));
436 private Pair
<DecryptedGroup
, GroupChange
> ejectMembers(
437 GroupInfoV2 groupInfoV2
, Set
<UUID
> uuids
438 ) throws IOException
{
439 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
440 return commitChange(groupInfoV2
, groupOperations
.createRemoveMembersChange(uuids
));
443 private Pair
<DecryptedGroup
, GroupChange
> commitChange(
444 GroupInfoV2 groupInfoV2
, GroupChange
.Actions
.Builder change
445 ) throws IOException
{
446 final var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupInfoV2
.getMasterKey());
447 final var groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
448 final var previousGroupState
= groupInfoV2
.getGroup();
449 final var nextRevision
= previousGroupState
.getRevision() + 1;
450 final var changeActions
= change
.setRevision(nextRevision
).build();
451 final DecryptedGroupChange decryptedChange
;
452 final DecryptedGroup decryptedGroupState
;
455 decryptedChange
= groupOperations
.decryptChange(changeActions
, getSelfUuid());
456 decryptedGroupState
= DecryptedGroupUtil
.apply(previousGroupState
, decryptedChange
);
457 } catch (VerificationFailedException
| InvalidGroupStateException
| NotAbleToApplyGroupV2ChangeException e
) {
458 throw new IOException(e
);
461 var signedGroupChange
= groupsV2Api
.patchGroup(changeActions
,
462 getGroupAuthForToday(groupSecretParams
),
465 return new Pair
<>(decryptedGroupState
, signedGroupChange
);
468 private GroupChange
commitChange(
469 GroupSecretParams groupSecretParams
,
471 GroupChange
.Actions
.Builder change
,
472 GroupLinkPassword password
473 ) throws IOException
{
474 final var nextRevision
= currentRevision
+ 1;
475 final var changeActions
= change
.setRevision(nextRevision
).build();
477 return groupsV2Api
.patchGroup(changeActions
,
478 getGroupAuthForToday(groupSecretParams
),
479 Optional
.fromNullable(password
).transform(GroupLinkPassword
::serialize
));
482 public DecryptedGroup
getUpdatedDecryptedGroup(
483 DecryptedGroup group
, byte[] signedGroupChange
, GroupMasterKey groupMasterKey
486 final var decryptedGroupChange
= getDecryptedGroupChange(signedGroupChange
, groupMasterKey
);
487 if (decryptedGroupChange
== null) {
490 return DecryptedGroupUtil
.apply(group
, decryptedGroupChange
);
491 } catch (NotAbleToApplyGroupV2ChangeException e
) {
496 private DecryptedGroupChange
getDecryptedGroupChange(byte[] signedGroupChange
, GroupMasterKey groupMasterKey
) {
497 if (signedGroupChange
!= null) {
498 var groupOperations
= groupsV2Operations
.forGroup(GroupSecretParams
.deriveFromMasterKey(groupMasterKey
));
501 return groupOperations
.decryptChange(GroupChange
.parseFrom(signedGroupChange
), true).orNull();
502 } catch (VerificationFailedException
| InvalidGroupStateException
| InvalidProtocolBufferException e
) {
510 private static int currentTimeDays() {
511 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
514 private GroupsV2AuthorizationString
getGroupAuthForToday(
515 final GroupSecretParams groupSecretParams
516 ) throws IOException
{
517 final var today
= currentTimeDays();
518 // Returns credentials for the next 7 days
519 final var credentials
= groupsV2Api
.getCredentials(today
);
520 // TODO cache credentials until they expire
521 var authCredentialResponse
= credentials
.get(today
);
522 final var uuid
= getSelfUuid();
524 return groupsV2Api
.getGroupsV2AuthorizationString(uuid
, today
, groupSecretParams
, authCredentialResponse
);
525 } catch (VerificationFailedException e
) {
526 throw new IOException(e
);
530 private UUID
getSelfUuid() {
531 return addressResolver
.resolveSignalServiceAddress(this.selfRecipientIdProvider
.getSelfRecipientId()).getUuid();