Add group terminate support.

This commit is contained in:
Cody Henthorne
2026-03-19 16:10:26 -04:00
parent 0896718e5c
commit a0c0acb8fc
130 changed files with 1312 additions and 146 deletions

View File

@@ -50,4 +50,6 @@ public interface ChangeSetModifier {
void removeModifyMemberLabels(int i);
void clearModifyMemberLabelAccess();
void clearTerminateGroup();
}

View File

@@ -118,6 +118,10 @@ internal class DecryptedGroupChangeActionsBuilderChangeSetModifier(private val r
result.newMemberLabelAccess = AccessControl.AccessRequired.UNKNOWN
}
override fun clearTerminateGroup() {
result.terminateGroup = false
}
private fun <T> List<T>.removeIndex(i: Int): List<T> {
val modifiedList = this.toMutableList()
modifiedList.removeAt(i)

View File

@@ -89,6 +89,7 @@ fun DecryptedGroupChange.getChangedFields(): Set<GroupChangeField> {
if (newRequestingMembers.isNotEmpty()) add(GroupChangeField.REQUESTING_MEMBERS)
if (newTimer != null) add(GroupChangeField.TIMER)
if (newTitle != null) add(GroupChangeField.TITLE)
if (terminateGroup) add(GroupChangeField.TERMINATE_GROUP)
}
}
@@ -129,6 +130,7 @@ enum class GroupChangeField(val changeSilently: Boolean = false) {
REQUESTING_MEMBER_APPROVALS,
REQUESTING_MEMBER_REMOVALS,
REQUESTING_MEMBERS,
TERMINATE_GROUP,
TIMER,
TITLE;

View File

@@ -341,6 +341,8 @@ public final class DecryptedGroupUtil {
DecryptedGroupExtensions.setModifyMemberLabelActions(builder, change.modifyMemberLabels);
applyTerminateGroup(builder, change);
return builder.build();
}
@@ -535,6 +537,12 @@ public final class DecryptedGroupUtil {
}
}
private static void applyTerminateGroup(DecryptedGroup.Builder builder, DecryptedGroupChange change) {
if (change.terminateGroup) {
builder.terminated(true);
}
}
private static void applyAddRequestingMembers(DecryptedGroup.Builder builder, List<DecryptedRequestingMember> newRequestingMembers) {
List<DecryptedRequestingMember> requestingMembers = new ArrayList<>(builder.requestingMembers);
requestingMembers.addAll(newRequestingMembers);

View File

@@ -109,6 +109,10 @@ internal class GroupChangeActionsBuilderChangeSetModifier(private val result: Gr
result.modifyMemberLabelAccess = null
}
override fun clearTerminateGroup() {
result.terminate_group = null
}
private fun <T> List<T>.removeIndex(i: Int): List<T> {
val modifiedList = this.toMutableList()
modifiedList.removeAt(i)

View File

@@ -186,6 +186,10 @@ public final class GroupChangeReconstruct {
})
.collect(Collectors.toList()));
if (!fromState.terminated && toState.terminated) {
builder.terminateGroup(true);
}
return builder.build();
}

View File

@@ -52,7 +52,8 @@ public final class GroupChangeUtil {
change.delete_members_banned.isEmpty() && // field 23
change.promote_members_pending_pni_aci_profile_key.isEmpty() && // field 24
change.modifyMemberLabels.isEmpty() && // field 26
change.modifyMemberLabelAccess == null; // field 27
change.modifyMemberLabelAccess == null && // field 27
change.terminate_group == null; // field 28
}
/**
@@ -157,6 +158,7 @@ public final class GroupChangeUtil {
resolveField24PromotePendingPniAciMembers (conflictingChange, changeSetModifier, fullMembersByUuid);
resolveField26ModifyMemberLabels (conflictingChange, changeSetModifier, fullMembersByUuid);
resolveField27ModifyMemberLabelAccess (groupState, conflictingChange, changeSetModifier);
resolveField28TerminateGroup (groupState, conflictingChange, changeSetModifier);
}
private static void resolveField3AddMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap<ByteString, DecryptedMember> fullMembersByUuid, HashMap<ByteString, DecryptedPendingMember> pendingMembersByServiceId) {
@@ -403,4 +405,13 @@ public final class GroupChangeUtil {
result.clearModifyMemberLabelAccess();
}
}
private static void resolveField28TerminateGroup(@Nonnull DecryptedGroup groupState,
@Nonnull DecryptedGroupChange conflictingChange,
@Nonnull ChangeSetModifier result)
{
if (groupState.terminated && conflictingChange.terminateGroup) {
result.clearTerminateGroup();
}
}
}

View File

@@ -75,7 +75,7 @@ public final class GroupsV2Operations {
public static final UUID UNKNOWN_UUID = UuidUtil.UNKNOWN_UUID;
/** Highest change epoch this class knows now to decrypt */
public static final int HIGHEST_KNOWN_EPOCH = 6;
public static final int HIGHEST_KNOWN_EPOCH = 7;
private final ServerPublicParams serverPublicParams;
private final ClientZkProfileOperations clientZkProfileOperations;
@@ -350,6 +350,12 @@ public final class GroupsV2Operations {
);
}
public GroupChange.Actions.Builder createTerminateGroup() {
return new GroupChange.Actions.Builder().terminate_group(
new GroupChange.Actions.TerminateGroupAction.Builder().build()
);
}
public GroupChange.Actions.Builder createAnnouncementGroupChange(boolean isAnnouncementGroup) {
return new GroupChange.Actions.Builder().modify_announcements_only(
new GroupChange.Actions.ModifyAnnouncementsOnlyAction.Builder().announcements_only(isAnnouncementGroup).build()
@@ -493,6 +499,7 @@ public final class GroupsV2Operations {
.disappearingMessagesTimer(new DecryptedTimer.Builder().duration(decryptDisappearingMessagesTimer(group.disappearingMessagesTimer)).build())
.inviteLinkPassword(group.inviteLinkPassword)
.bannedMembers(decryptedBannedMembers)
.terminated(group.terminated)
.build();
}
@@ -781,6 +788,11 @@ public final class GroupsV2Operations {
builder.newMemberLabelAccess(actions.modifyMemberLabelAccess.memberLabelAccess);
}
// Field 28
if (actions.terminate_group != null) {
builder.terminateGroup(true);
}
if (editorServiceId instanceof ServiceId.PNI) {
if (actions.addMembers.size() == 1 && builder.newMembers.size() == 1) {
GroupChange.Actions.AddMemberAction addMemberAction = actions.addMembers.get(0);

View File

@@ -79,6 +79,7 @@ message DecryptedGroup {
string description = 11;
EnabledState isAnnouncementGroup = 12;
repeated DecryptedBannedMember bannedMembers = 13;
bool terminated = 14;
bool isPlaceholderGroup = 64;
}
@@ -112,6 +113,7 @@ message DecryptedGroupChange {
repeated DecryptedMember promotePendingPniAciMembers = 24;
repeated DecryptedModifyMemberLabel modifyMemberLabels = 26;
AccessControl.AccessRequired newMemberLabelAccess = 27;
bool terminateGroup = 28;
}
message DecryptedString {

View File

@@ -88,7 +88,8 @@ message Group {
bytes inviteLinkPassword = 10;
bool announcements_only = 12;
repeated MemberBanned members_banned = 13;
// next: 14
bool terminated = 14;
// next: 15
}
message GroupAttributeBlob {
@@ -238,6 +239,8 @@ message GroupChange {
bool announcements_only = 1;
}
message TerminateGroupAction {}
bytes sourceUserId = 1;
// clients should not provide this value; the server will provide it in the response buffer to ensure the signature is binding to a particular group
// if clients set it during a request the server will respond with 400.
@@ -268,7 +271,8 @@ message GroupChange {
repeated PromoteMemberPendingPniAciProfileKeyAction promote_members_pending_pni_aci_profile_key = 24; // change epoch = 5
repeated ModifyMemberLabelAction modifyMemberLabels = 26; // change epoch = 6;
ModifyMemberLabelAccessControlAction modifyMemberLabelAccess = 27; // change epoch = 6
// next: 28
TerminateGroupAction terminate_group = 28; // change epoch = 7
// next: 29
}
bytes actions = 1;