From 68ae25f5cd08bfccb09041d4d06ff45d1a51ba0d Mon Sep 17 00:00:00 2001 From: Josh Perez <60019601+josh-signal@users.noreply.github.com> Date: Fri, 14 Apr 2023 20:52:50 -0400 Subject: [PATCH] Remove GroupContext proto Co-authored-by: Scott Nonnenberg --- protos/SignalService.proto | 19 +- ts/background.ts | 72 +------- .../conversation/ConversationHeader.tsx | 47 ++++- ts/jobs/helpers/sendDeleteForEveryone.ts | 1 - ts/jobs/helpers/sendNormalMessage.ts | 3 - ts/jobs/helpers/sendProfileKey.ts | 1 - ts/jobs/helpers/sendReaction.ts | 3 - ts/messageModifiers/MessageRequests.ts | 21 +-- ts/models/conversations.ts | 84 +-------- ts/models/messages.ts | 174 +----------------- ts/test-both/processDataMessage_test.ts | 62 ------- ts/textsecure/MessageReceiver.ts | 160 +++------------- ts/textsecure/SendMessage.ts | 66 +------ ts/textsecure/Types.d.ts | 12 -- ts/textsecure/processDataMessage.ts | 59 ------ ts/util/sendToGroup.ts | 3 - 16 files changed, 74 insertions(+), 713 deletions(-) diff --git a/protos/SignalService.proto b/protos/SignalService.proto index efed4a78a..2691b7678 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -354,7 +354,7 @@ message DataMessage { optional string body = 1; repeated AttachmentPointer attachments = 2; - optional GroupContext group = 3; + reserved /*groupV1*/ 3; optional GroupContextV2 groupV2 = 15; optional uint32 flags = 4; optional uint32 expireTimer = 5; @@ -664,23 +664,6 @@ message AttachmentPointer { // Next ID: 16 } -message GroupContext { - enum Type { - UNKNOWN = 0; - UPDATE = 1; - DELIVER = 2; - QUIT = 3; - REQUEST_INFO = 4; - } - - optional bytes id = 1; - optional Type type = 2; - optional string name = 3; - repeated string membersE164 = 4; - // field 6 was removed; do not use - optional AttachmentPointer avatar = 5; -} - message GroupContextV2 { optional bytes masterKey = 1; optional uint32 revision = 2; diff --git a/ts/background.ts b/ts/background.ts index 934d3ea36..a5e4b34c4 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -2702,7 +2702,7 @@ export async function startApp(): Promise { // Note: this type of message is automatically removed from cache in MessageReceiver const { typing, sender, senderUuid, senderDevice } = ev; - const { groupId, groupV2Id, started } = typing || {}; + const { groupV2Id, started } = typing || {}; // We don't do anything with incoming typing messages if the setting is disabled if (!window.storage.get('typingIndicators')) { @@ -2721,11 +2721,7 @@ export async function startApp(): Promise { // We multiplex between GV1/GV2 groups here, but we don't kick off migrations if (groupV2Id) { conversation = window.ConversationController.get(groupV2Id); - } - if (!conversation && groupId) { - conversation = window.ConversationController.get(groupId); - } - if (!groupV2Id && !groupId) { + } else { conversation = senderConversation; } @@ -2737,7 +2733,7 @@ export async function startApp(): Promise { } if (!conversation) { log.warn( - `onTyping: Did not find conversation for typing indicator (groupv2(${groupV2Id}), group(${groupId}), ${sender}, ${senderUuid})` + `onTyping: Did not find conversation for typing indicator (groupv2(${groupV2Id}), ${sender}, ${senderUuid})` ); return; } @@ -2988,8 +2984,6 @@ export async function startApp(): Promise { const messageDescriptor = getMessageDescriptor({ message: data.message, - source: data.source, - sourceUuid: data.sourceUuid, // 'message' event: for 1:1 converations, the conversation is same as sender destination: data.source, destinationUuid: data.sourceUuid, @@ -3290,18 +3284,13 @@ export async function startApp(): Promise { return new window.Whisper.Message(partialMessage); } - // Works with 'sent' and 'message' data sent from MessageReceiver, with a little massage - // at callsites to make sure both source and destination are populated. + // Works with 'sent' and 'message' data sent from MessageReceiver const getMessageDescriptor = ({ message, - source, - sourceUuid, destination, destinationUuid, }: { message: ProcessedDataMessage; - source?: string; - sourceUuid?: string; destination?: string; destinationUuid?: string; }): MessageDescriptor => { @@ -3342,44 +3331,6 @@ export async function startApp(): Promise { id: conversationId, }; } - if (message.group) { - const { id, derivedGroupV2Id } = message.group; - if (!id) { - throw new Error('getMessageDescriptor: GroupV1 data was missing id'); - } - if (!derivedGroupV2Id) { - log.warn( - 'getMessageDescriptor: GroupV1 data was missing derivedGroupV2Id' - ); - } else { - // First we check for an already-migrated GroupV2 group - const migratedGroup = - window.ConversationController.get(derivedGroupV2Id); - if (migratedGroup) { - return { - type: Message.GROUP, - id: migratedGroup.id, - }; - } - } - - // If we can't find one, we treat this as a normal GroupV1 group - const { conversation: fromContact } = - window.ConversationController.maybeMergeContacts({ - aci: sourceUuid, - e164: source, - reason: `getMessageDescriptor(${message.timestamp}): group v1`, - }); - - const conversationId = window.ConversationController.ensureGroup(id, { - addedBy: fromContact.id, - }); - - return { - type: Message.GROUP, - id: conversationId, - }; - } const conversation = window.ConversationController.lookupOrCreate({ uuid: destinationUuid, @@ -3406,10 +3357,6 @@ export async function startApp(): Promise { const messageDescriptor = getMessageDescriptor({ ...data, - - // 'sent' event: the sender is always us! - source, - sourceUuid, }); const { PROFILE_KEY_UPDATE } = Proto.DataMessage.Flags; @@ -3765,18 +3712,12 @@ export async function startApp(): Promise { function onMessageRequestResponse(ev: MessageRequestResponseEvent): void { ev.confirm(); - const { - threadE164, - threadUuid, - groupId, - groupV2Id, - messageRequestResponseType, - } = ev; + const { threadE164, threadUuid, groupV2Id, messageRequestResponseType } = + ev; log.info('onMessageRequestResponse', { threadE164, threadUuid, - groupId: `group(${groupId})`, groupV2Id: `groupv2(${groupV2Id})`, messageRequestResponseType, }); @@ -3789,7 +3730,6 @@ export async function startApp(): Promise { const attributes: MessageRequestAttributesType = { threadE164, threadUuid, - groupId, groupV2Id, type: messageRequestResponseType, }; diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index 8ec35e218..2cf77a356 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -404,6 +404,44 @@ export class ConversationHeader extends React.Component { const hasGV2AdminEnabled = isGroup && groupVersion === 2; + if (isGroup && groupVersion !== 2) { + return ( + + + pushPanelForConversation({ type: PanelType.GroupV1Members }) + } + > + {i18n('icu:showMembers')} + + + pushPanelForConversation({ type: PanelType.AllMedia }) + } + > + {i18n('icu:viewRecentMedia')} + + + {isArchived ? ( + onMoveToInbox(id)}> + {i18n('icu:moveConversationToInbox')} + + ) : ( + onArchive(id)}> + {i18n('icu:archiveConversation')} + + )} + + this.setState({ hasDeleteMessagesConfirmation: true }) + } + > + {i18n('icu:deleteMessages')} + + + ); + } + const isActiveExpireTimer = (value: number): boolean => { if (!expireTimer) { return value === 0; @@ -487,15 +525,6 @@ export class ConversationHeader extends React.Component { : i18n('icu:showConversationDetails--direct')} ) : null} - {isGroup && !hasGV2AdminEnabled ? ( - - pushPanelForConversation({ type: PanelType.GroupV1Members }) - } - > - {i18n('icu:showMembers')} - - ) : null} pushPanelForConversation({ type: PanelType.AllMedia })} > diff --git a/ts/jobs/helpers/sendDeleteForEveryone.ts b/ts/jobs/helpers/sendDeleteForEveryone.ts index 2a4315b58..3e7e4b6e6 100644 --- a/ts/jobs/helpers/sendDeleteForEveryone.ts +++ b/ts/jobs/helpers/sendDeleteForEveryone.ts @@ -230,7 +230,6 @@ export async function sendDeleteForEveryone( abortSignal, contentHint, groupSendOptions: { - groupV1: conversation.getGroupV1Info(recipients), groupV2: groupV2Info, deletedForEveryoneTimestamp: targetTimestamp, timestamp, diff --git a/ts/jobs/helpers/sendNormalMessage.ts b/ts/jobs/helpers/sendNormalMessage.ts index aaa74f321..e2a7ce538 100644 --- a/ts/jobs/helpers/sendNormalMessage.ts +++ b/ts/jobs/helpers/sendNormalMessage.ts @@ -257,9 +257,6 @@ export async function sendNormalMessage( contact, deletedForEveryoneTimestamp, expireTimer, - groupV1: conversation.getGroupV1Info( - recipientIdentifiersWithoutMe - ), groupV2: groupV2Info, messageText: body, preview, diff --git a/ts/jobs/helpers/sendProfileKey.ts b/ts/jobs/helpers/sendProfileKey.ts index 470c92c8a..a718180db 100644 --- a/ts/jobs/helpers/sendProfileKey.ts +++ b/ts/jobs/helpers/sendProfileKey.ts @@ -152,7 +152,6 @@ export async function sendProfileKey( contentHint, groupSendOptions: { flags: Proto.DataMessage.Flags.PROFILE_KEY_UPDATE, - groupV1: conversation.getGroupV1Info(), groupV2: groupV2Info, profileKey, timestamp, diff --git a/ts/jobs/helpers/sendReaction.ts b/ts/jobs/helpers/sendReaction.ts index dfc74c789..f9f05f929 100644 --- a/ts/jobs/helpers/sendReaction.ts +++ b/ts/jobs/helpers/sendReaction.ts @@ -255,9 +255,6 @@ export async function sendReaction( abortSignal, contentHint: ContentHint.RESENDABLE, groupSendOptions: { - groupV1: conversation.getGroupV1Info( - recipientIdentifiersWithoutMe - ), groupV2: groupV2Info, reaction: reactionForSend, timestamp: pendingReaction.timestamp, diff --git a/ts/messageModifiers/MessageRequests.ts b/ts/messageModifiers/MessageRequests.ts index 93d5788ea..b5dff9aaa 100644 --- a/ts/messageModifiers/MessageRequests.ts +++ b/ts/messageModifiers/MessageRequests.ts @@ -11,7 +11,6 @@ import * as Errors from '../types/errors'; export type MessageRequestAttributesType = { threadE164?: string; threadUuid?: string; - groupId?: string; groupV2Id?: string; type: number; }; @@ -56,20 +55,6 @@ export class MessageRequests extends Collection { } } - // V1 Group - if (conversation.get('groupId')) { - const syncByGroupId = this.findWhere({ - groupId: conversation.get('groupId'), - }); - if (syncByGroupId) { - log.info( - `Found early message request response for group v1 ID ${conversation.idForLogging()}` - ); - this.remove(syncByGroupId); - return syncByGroupId; - } - } - // V2 group if (conversation.get('groupId')) { const syncByGroupId = this.findWhere({ @@ -91,7 +76,6 @@ export class MessageRequests extends Collection { try { const threadE164 = sync.get('threadE164'); const threadUuid = sync.get('threadUuid'); - const groupId = sync.get('groupId'); const groupV2Id = sync.get('groupV2Id'); let conversation; @@ -100,9 +84,6 @@ export class MessageRequests extends Collection { if (groupV2Id) { conversation = window.ConversationController.get(groupV2Id); } - if (!conversation && groupId) { - conversation = window.ConversationController.get(groupId); - } if (!conversation && (threadE164 || threadUuid)) { conversation = window.ConversationController.lookupOrCreate({ e164: threadE164, @@ -113,7 +94,7 @@ export class MessageRequests extends Collection { if (!conversation) { log.warn( - `Received message request response for unknown conversation: groupv2(${groupV2Id}) group(${groupId}) ${threadUuid} ${threadE164}` + `Received message request response for unknown conversation: groupv2(${groupV2Id}) ${threadUuid} ${threadE164}` ); return; } diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index cfd16bee7..1b0fc5b92 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -40,7 +40,6 @@ import * as Stickers from '../types/Stickers'; import { StorySendMode } from '../types/Stories'; import type { ContactWithHydratedAvatar, - GroupV1InfoType, GroupV2InfoType, } from '../textsecure/SendMessage'; import createTaskWithTimeout from '../textsecure/TaskWithTimeout'; @@ -1307,24 +1306,6 @@ export class ConversationModel extends window.Backbone }; } - getGroupV1Info(members?: Array): GroupV1InfoType | undefined { - const groupId = this.get('groupId'); - const groupVersion = this.get('groupVersion'); - - if ( - isDirectConversation(this.attributes) || - !groupId || - (groupVersion && groupVersion > 0) - ) { - return undefined; - } - - return { - id: groupId, - members: members || this.getRecipients(), - }; - } - getGroupIdBuffer(): Uint8Array | undefined { const groupIdString = this.get('groupId'); @@ -2457,9 +2438,7 @@ export class ConversationModel extends window.Backbone this.disableProfileSharing({ viaStorageServiceSync }); if (isLocalAction) { - if (isGroupV1(this.attributes)) { - await this.leaveGroup(); - } else if (isGroupV2(this.attributes)) { + if (isGroupV2(this.attributes)) { await this.leaveGroupV2(); } } @@ -2477,9 +2456,7 @@ export class ConversationModel extends window.Backbone 'deleted from message request' ); - if (isGroupV1(this.attributes)) { - await this.leaveGroup(); - } else if (isGroupV2(this.attributes)) { + if (isGroupV2(this.attributes)) { await this.leaveGroupV2(); } } @@ -2499,9 +2476,7 @@ export class ConversationModel extends window.Backbone 'blocked and deleted from message request' ); - if (isGroupV1(this.attributes)) { - await this.leaveGroup(); - } else if (isGroupV2(this.attributes)) { + if (isGroupV2(this.attributes)) { await this.leaveGroupV2(); } } @@ -4875,59 +4850,6 @@ export class ConversationModel extends window.Backbone return !this.get('left'); } - // Deprecated: only applies to GroupV1 - async leaveGroup(): Promise { - const { messaging } = window.textsecure; - if (!messaging) { - throw new Error('leaveGroup: Cannot leave v1 group when offline!'); - } - - if (!isGroupV1(this.attributes)) { - throw new Error( - `leaveGroup: Group ${this.idForLogging()} is not GroupV1!` - ); - } - - const now = Date.now(); - const groupId = this.get('groupId'); - - if (!groupId) { - throw new Error(`leaveGroup/${this.idForLogging()}: No groupId!`); - } - - const groupIdentifiers = this.getRecipients(); - this.set({ left: true }); - window.Signal.Data.updateConversation(this.attributes); - - const model = new window.Whisper.Message({ - conversationId: this.id, - group_update: { left: 'You' }, - readStatus: ReadStatus.Read, - received_at_ms: now, - received_at: incrementMessageCounter(), - seenStatus: SeenStatus.NotApplicable, - sent_at: now, - type: 'group', - // TODO: DESKTOP-722 - } as unknown as MessageAttributesType); - - const id = await window.Signal.Data.saveMessage(model.attributes, { - ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(), - }); - model.set({ id }); - - const message = window.MessageController.register(model.id, model); - void this.addSingleMessage(message); - - const options = await getSendOptions(this.attributes); - void message.send( - handleMessageSend( - messaging.leaveGroup(groupId, groupIdentifiers, options), - { messageIds: [], sendType: 'legacyGroupChange' } - ) - ); - } - async markRead( newestUnreadAt: number, options: { diff --git a/ts/models/messages.ts b/ts/models/messages.ts index b119bbe35..2b848483b 100644 --- a/ts/models/messages.ts +++ b/ts/models/messages.ts @@ -2,7 +2,6 @@ // SPDX-License-Identifier: AGPL-3.0-only import { - difference, isEmpty, isEqual, isNumber, @@ -14,11 +13,9 @@ import { partition, pick, union, - without, } from 'lodash'; import type { CustomError, - GroupV1Update, MessageAttributesType, MessageReactionType, QuotedMessageType, @@ -85,7 +82,6 @@ import { isDirectConversation, isGroup, isGroupV1, - isGroupV2, isMe, } from '../util/whatTypeOfConversation'; import { handleMessageSend } from '../util/handleMessageSend'; @@ -145,11 +141,9 @@ import { notificationService } from '../services/notifications'; import type { LinkPreviewType } from '../types/message/LinkPreviews'; import * as log from '../logging/log'; import * as Bytes from '../Bytes'; -import { computeHash } from '../Crypto'; import { cleanupMessage, deleteMessageData } from '../util/cleanup'; import { getContact, - getContactId, getSource, getSourceUuid, isCustomError, @@ -173,7 +167,6 @@ import { SeenStatus } from '../MessageSeenStatus'; import { isNewReactionReplacingPrevious } from '../reactions/util'; import { parseBoostBadgeListFromServer } from '../badges/parseBadgesFromServer'; import { GiftBadgeStates } from '../components/conversation/Message'; -import { downloadAttachment } from '../util/downloadAttachment'; import type { StickerWithHydratedData } from '../types/Stickers'; import { getStringForConversationMerge } from '../util/getStringForConversationMerge'; import { @@ -2139,7 +2132,6 @@ export class MessageModel extends window.Backbone.Model { const sourceUuid = message.get('sourceUuid'); const type = message.get('type'); const conversationId = message.get('conversationId'); - const GROUP_TYPES = Proto.GroupContext.Type; const fromContact = getContact(this.attributes); if (fromContact) { @@ -2352,9 +2344,6 @@ export class MessageModel extends window.Backbone.Model { reason: 'handleDataMessage', })!; const hasGroupV2Prop = Boolean(initialMessage.groupV2); - const isV1GroupUpdate = - initialMessage.group && - initialMessage.group.type !== Proto.GroupContext.Type.DELIVER; // Drop if from blocked user. Only GroupV2 messages should need to be dropped here. const isBlocked = @@ -2397,7 +2386,6 @@ export class MessageModel extends window.Backbone.Model { type === 'incoming' && !isDirectConversation(conversation.attributes) && !hasGroupV2Prop && - !isV1GroupUpdate && conversation.get('members') && !areWeMember ) { @@ -2408,16 +2396,6 @@ export class MessageModel extends window.Backbone.Model { return; } - // Because GroupV1 messages can now be multiplexed into GroupV2 conversations, we - // drop GroupV1 updates in GroupV2 groups. - if (isV1GroupUpdate && isGroupV2(conversation.attributes)) { - log.warn( - `Received GroupV1 update in GroupV2 conversation ${conversation.idForLogging()}. Dropping.` - ); - confirm(); - return; - } - // Drop incoming messages to announcement only groups where sender is not admin if ( conversation.get('announcementsOnly') && @@ -2615,160 +2593,10 @@ export class MessageModel extends window.Backbone.Model { } if (isSupported) { - let attributes = { + const attributes = { ...conversation.attributes, }; - // GroupV1 - if (!hasGroupV2Prop && initialMessage.group) { - const pendingGroupUpdate: GroupV1Update = {}; - - const memberConversations: Array = - await Promise.all( - initialMessage.group.membersE164.map((e164: string) => - window.ConversationController.getOrCreateAndWait( - e164, - 'private' - ) - ) - ); - const members = memberConversations.map(c => c.get('id')); - attributes = { - ...attributes, - type: 'group', - groupId: initialMessage.group.id, - }; - if (initialMessage.group.type === GROUP_TYPES.UPDATE) { - attributes = { - ...attributes, - name: initialMessage.group.name, - members: union(members, conversation.get('members')), - }; - - if (initialMessage.group.name !== conversation.get('name')) { - pendingGroupUpdate.name = initialMessage.group.name; - } - - const avatarAttachment = initialMessage.group.avatar; - - let downloadedAvatar; - let hash; - if (avatarAttachment) { - try { - downloadedAvatar = await downloadAttachment(avatarAttachment); - - if (downloadedAvatar) { - const loadedAttachment = - await window.Signal.Migrations.loadAttachmentData( - downloadedAvatar - ); - - hash = computeHash(loadedAttachment.data); - } - } catch (err) { - log.info(`${idLog}: group avatar download failed`); - } - } - - const existingAvatar = conversation.get('avatar'); - - if ( - // Avatar added - (!existingAvatar && avatarAttachment) || - // Avatar changed - (existingAvatar && existingAvatar.hash !== hash) || - // Avatar removed - (existingAvatar && !avatarAttachment) - ) { - // Removes existing avatar from disk - if (existingAvatar && existingAvatar.path) { - await window.Signal.Migrations.deleteAttachmentData( - existingAvatar.path - ); - } - - let avatar = null; - if (downloadedAvatar && avatarAttachment != null) { - const onDiskAttachment = - await Attachment.migrateDataToFileSystem(downloadedAvatar, { - writeNewAttachmentData: - window.Signal.Migrations.writeNewAttachmentData, - logger: log, - }); - avatar = { - ...onDiskAttachment, - hash, - }; - } - - if (!avatar) { - attributes.avatar = avatar; - } else { - const { url, path } = avatar; - strictAssert(url, 'Avatar needs url'); - strictAssert(path, 'Avatar needs path'); - attributes.avatar = { - url, - path, - ...avatar, - }; - } - - pendingGroupUpdate.avatarUpdated = true; - } else { - log.info( - `${idLog}: Group avatar hash matched; not replacing group avatar` - ); - } - - const differentMembers = difference( - members, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - conversation.get('members')! - ); - if (differentMembers.length > 0) { - // Because GroupV1 groups are based on e164 only - const maybeE164s = map(differentMembers, id => - window.ConversationController.get(id)?.get('e164') - ); - const e164s = filter(maybeE164s, isNotNil); - pendingGroupUpdate.joined = [...e164s]; - } - if (conversation.get('left')) { - log.warn('re-added to a left group'); - attributes.left = false; - conversation.set({ addedBy: getContactId(message.attributes) }); - } - } else if (initialMessage.group.type === GROUP_TYPES.QUIT) { - const inGroup = Boolean( - sender && - (conversation.get('members') || []).includes(sender.id) - ); - if (!inGroup) { - const senderString = sender ? sender.idForLogging() : null; - log.info( - `${idLog}: Got 'left' message from someone not in group: ${senderString}. Dropping.` - ); - return; - } - - if (isMe(sender.attributes)) { - attributes.left = true; - pendingGroupUpdate.left = 'You'; - } else { - pendingGroupUpdate.left = sender.get('id'); - } - attributes.members = without( - conversation.get('members'), - sender.get('id') - ); - } - - if (!isEmpty(pendingGroupUpdate)) { - message.set('group_update', pendingGroupUpdate); - } - } - // Drop empty messages after. This needs to happen after the initial // message.set call and after GroupV1 processing to make sure all possible // properties are set before we determine that a message is empty. diff --git a/ts/test-both/processDataMessage_test.ts b/ts/test-both/processDataMessage_test.ts index db3088490..74c06bbd3 100644 --- a/ts/test-both/processDataMessage_test.ts +++ b/ts/test-both/processDataMessage_test.ts @@ -32,10 +32,6 @@ const PROCESSED_ATTACHMENT: ProcessedAttachment = { size: 34, }; -const GROUP_ID = new Uint8Array([0x68, 0x65, 0x79]); - -const DERIVED_GROUPV2_ID = '7qQUi8Wa6Jm3Rl+l63saATGeciEqokbHpP+lV3F5t9o='; - describe('processDataMessage', () => { const check = (message: Proto.IDataMessage) => processDataMessage( @@ -85,59 +81,6 @@ describe('processDataMessage', () => { ); }); - it('should process group context UPDATE/QUIT message', () => { - const { UPDATE, QUIT } = Proto.GroupContext.Type; - - for (const type of [UPDATE, QUIT]) { - const out = check({ - body: 'should be deleted', - attachments: [UNPROCESSED_ATTACHMENT], - group: { - id: GROUP_ID, - name: 'Group', - avatar: UNPROCESSED_ATTACHMENT, - type, - membersE164: ['+1'], - }, - }); - - assert.isUndefined(out.body); - assert.strictEqual(out.attachments.length, 0); - assert.deepStrictEqual(out.group, { - id: 'hey', - name: 'Group', - avatar: PROCESSED_ATTACHMENT, - type, - membersE164: ['+1'], - derivedGroupV2Id: DERIVED_GROUPV2_ID, - }); - } - }); - - it('should process group context DELIVER message', () => { - const out = check({ - body: 'should not be deleted', - attachments: [UNPROCESSED_ATTACHMENT], - group: { - id: GROUP_ID, - name: 'should be deleted', - membersE164: ['should be deleted'], - type: Proto.GroupContext.Type.DELIVER, - }, - }); - - assert.strictEqual(out.body, 'should not be deleted'); - assert.strictEqual(out.attachments.length, 1); - assert.deepStrictEqual(out.group, { - id: 'hey', - type: Proto.GroupContext.Type.DELIVER, - membersE164: [], - derivedGroupV2Id: DERIVED_GROUPV2_ID, - avatar: undefined, - name: undefined, - }); - }); - it('should process groupv2 context', () => { const out = check({ groupV2: { @@ -312,15 +255,10 @@ describe('processDataMessage', () => { const out = check({ flags: FLAGS.END_SESSION, body: 'should be deleted', - group: { - id: GROUP_ID, - type: Proto.GroupContext.Type.DELIVER, - }, attachments: [UNPROCESSED_ATTACHMENT], }); assert.isUndefined(out.body); - assert.isUndefined(out.group); assert.deepStrictEqual(out.attachments, []); }); diff --git a/ts/textsecure/MessageReceiver.ts b/ts/textsecure/MessageReceiver.ts index bf545553c..c03741358 100644 --- a/ts/textsecure/MessageReceiver.ts +++ b/ts/textsecure/MessageReceiver.ts @@ -47,7 +47,7 @@ import { parseIntOrThrow } from '../util/parseIntOrThrow'; import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; import { Zone } from '../util/Zone'; import { DurationInSeconds } from '../util/durations'; -import { deriveMasterKeyFromGroupV1, bytesToUuid } from '../Crypto'; +import { bytesToUuid } from '../Crypto'; import type { DownloadedAttachmentType } from '../types/Attachment'; import { Address } from '../types/Address'; import { QualifiedAddress } from '../types/QualifiedAddress'; @@ -127,7 +127,6 @@ import { inspectUnknownFieldTags } from '../util/inspectProtobufs'; import { incrementMessageCounter } from '../util/incrementMessageCounter'; import { filterAndClean } from '../types/BodyRange'; -const GROUPV1_ID_LENGTH = 16; const GROUPV2_ID_LENGTH = 32; const RETRY_TIMEOUT = 2 * 60 * 1000; @@ -2007,19 +2006,8 @@ export default class MessageReceiver const message = this.processDecrypted(envelope, msg); const groupId = this.getProcessedGroupId(message); const isBlocked = groupId ? this.isGroupBlocked(groupId) : false; - const { source, sourceUuid } = envelope; - const ourE164 = this.storage.user.getNumber(); - const ourUuid = this.storage.user.getCheckedUuid().toString(); - const isMe = - (source && ourE164 && source === ourE164) || - (sourceUuid && ourUuid && sourceUuid === ourUuid); - const isLeavingGroup = Boolean( - !message.groupV2 && - message.group && - message.group.type === Proto.GroupContext.Type.QUIT - ); - if (groupId && isBlocked && !(isMe && isLeavingGroup)) { + if (groupId && isBlocked) { log.warn( `Message ${getEnvelopeId(envelope)} ignored; destined for blocked group` ); @@ -2282,19 +2270,8 @@ export default class MessageReceiver const message = this.processDecrypted(envelope, msg.dataMessage); const groupId = this.getProcessedGroupId(message); const isBlocked = groupId ? this.isGroupBlocked(groupId) : false; - const { source, sourceUuid } = envelope; - const ourE164 = this.storage.user.getNumber(); - const ourUuid = this.storage.user.getCheckedUuid().toString(); - const isMe = - (source && ourE164 && source === ourE164) || - (sourceUuid && ourUuid && sourceUuid === ourUuid); - const isLeavingGroup = Boolean( - !message.groupV2 && - message.group && - message.group.type === Proto.GroupContext.Type.QUIT - ); - if (groupId && isBlocked && !(isMe && isLeavingGroup)) { + if (groupId && isBlocked) { log.warn( `Message ${getEnvelopeId(envelope)} ignored; destined for blocked group` ); @@ -2354,8 +2331,6 @@ export default class MessageReceiver return undefined; } - this.checkGroupV1Data(msg); - if (msg.flags && msg.flags & Proto.DataMessage.Flags.END_SESSION) { p = this.handleEndSession(envelope, new UUID(destination)); } @@ -2393,8 +2368,6 @@ export default class MessageReceiver msg.flags & Proto.DataMessage.Flags.EXPIRATION_TIMER_UPDATE ) { type = 'expirationTimerUpdate'; - } else if (msg.group) { - type = 'legacyGroupChange'; } // Note: other data messages without any of these attributes will fall into the // 'message' bucket - like stickers, gift badges, etc. @@ -2404,19 +2377,8 @@ export default class MessageReceiver const message = this.processDecrypted(envelope, msg); const groupId = this.getProcessedGroupId(message); const isBlocked = groupId ? this.isGroupBlocked(groupId) : false; - const { source, sourceUuid } = envelope; - const ourE164 = this.storage.user.getNumber(); - const ourUuid = this.storage.user.getCheckedUuid().toString(); - const isMe = - (source && ourE164 && source === ourE164) || - (sourceUuid && ourUuid && sourceUuid === ourUuid); - const isLeavingGroup = Boolean( - !message.groupV2 && - message.group && - message.group.type === Proto.GroupContext.Type.QUIT - ); - if (groupId && isBlocked && !(isMe && isLeavingGroup)) { + if (groupId && isBlocked) { log.warn( `Message ${getEnvelopeId(envelope)} ignored; destined for blocked group` ); @@ -2780,17 +2742,11 @@ export default class MessageReceiver const { groupId, timestamp, action } = typingMessage; - let groupIdString: string | undefined; let groupV2IdString: string | undefined; - if (groupId && groupId.byteLength > 0) { - if (groupId.byteLength === GROUPV1_ID_LENGTH) { - groupIdString = Bytes.toBinary(groupId); - groupV2IdString = this.deriveGroupV2FromV1(groupId); - } else if (groupId.byteLength === GROUPV2_ID_LENGTH) { - groupV2IdString = Bytes.toBase64(groupId); - } else { - log.error('handleTypingMessage: Received invalid groupId value'); - } + if (groupId && groupId.byteLength === GROUPV2_ID_LENGTH) { + groupV2IdString = Bytes.toBase64(groupId); + } else { + log.error('handleTypingMessage: Received invalid groupId value'); } this.dispatchEvent( @@ -2799,13 +2755,11 @@ export default class MessageReceiver senderUuid: envelope.sourceUuid, senderDevice: envelope.sourceDevice, typing: { + groupV2Id: groupV2IdString, typingMessage, timestamp: timestamp?.toNumber() ?? Date.now(), started: action === Proto.TypingMessage.Action.STARTED, stopped: action === Proto.TypingMessage.Action.STOPPED, - - groupId: groupIdString, - groupV2Id: groupV2IdString, }, }) ); @@ -2823,22 +2777,7 @@ export default class MessageReceiver message: Proto.IDataMessage, envelope: ProcessedEnvelope ): boolean { - const { group, groupV2 } = message; - - if (group) { - const { id } = group; - strictAssert(id, 'Group data has no id'); - const isInvalid = id.byteLength !== GROUPV1_ID_LENGTH; - - if (isInvalid) { - log.info( - 'isInvalidGroupData: invalid GroupV1 message from', - getEnvelopeId(envelope) - ); - } - - return isInvalid; - } + const { groupV2 } = message; if (groupV2) { const { masterKey } = groupV2; @@ -2857,46 +2796,12 @@ export default class MessageReceiver return false; } - private deriveGroupV2FromV1(groupId: Uint8Array): string { - if (groupId.byteLength !== GROUPV1_ID_LENGTH) { - throw new Error( - `deriveGroupV2FromV1: had id with wrong byteLength: ${groupId.byteLength}` - ); - } - const masterKey = deriveMasterKeyFromGroupV1(groupId); - const data = deriveGroupFields(masterKey); - - return Bytes.toBase64(data.id); - } - - private checkGroupV1Data(message: Readonly): void { - const { group } = message; - - if (!group) { - return; - } - - if (!group.id) { - throw new Error('deriveGroupV1Data: had falsey id'); - } - - const { id } = group; - if (id.byteLength !== GROUPV1_ID_LENGTH) { - throw new Error( - `deriveGroupV1Data: had id with wrong byteLength: ${id.byteLength}` - ); - } - } - private getProcessedGroupId( message: ProcessedDataMessage ): string | undefined { if (message.groupV2) { return message.groupV2.id; } - if (message.group && message.group.id) { - return message.group.id; - } return undefined; } @@ -2906,9 +2811,6 @@ export default class MessageReceiver const { id } = deriveGroupFields(message.groupV2.masterKey); return Bytes.toBase64(id); } - if (message.group && message.group.id) { - return Bytes.toBinary(message.group.id); - } return undefined; } @@ -2917,10 +2819,6 @@ export default class MessageReceiver if (sentMessage.message && sentMessage.message.groupV2) { return `groupv2(${this.getGroupId(sentMessage.message)})`; } - if (sentMessage.message && sentMessage.message.group) { - strictAssert(sentMessage.message.group.id, 'group without id'); - return `group(${this.getGroupId(sentMessage.message)})`; - } return sentMessage.destination || sentMessage.destinationUuid; } @@ -2998,8 +2896,6 @@ export default class MessageReceiver return; } - this.checkGroupV1Data(sentMessage.message); - strictAssert(sentMessage.timestamp, 'sent message without timestamp'); log.info( @@ -3192,19 +3088,13 @@ export default class MessageReceiver const { groupId } = sync; - let groupIdString: string | undefined; let groupV2IdString: string | undefined; - if (groupId && groupId.byteLength > 0) { - if (groupId.byteLength === GROUPV1_ID_LENGTH) { - groupIdString = Bytes.toBinary(groupId); - groupV2IdString = this.deriveGroupV2FromV1(groupId); - } else if (groupId.byteLength === GROUPV2_ID_LENGTH) { - groupV2IdString = Bytes.toBase64(groupId); - } else { - this.removeFromCache(envelope); - log.error('Received message request with invalid groupId'); - return undefined; - } + if (groupId && groupId.byteLength === GROUPV2_ID_LENGTH) { + groupV2IdString = Bytes.toBase64(groupId); + } else { + this.removeFromCache(envelope); + log.error('Received message request with invalid groupId'); + return undefined; } const ev = new MessageRequestResponseEvent( @@ -3217,7 +3107,6 @@ export default class MessageReceiver ) : undefined, messageRequestResponseType: sync.type, - groupId: groupIdString, groupV2Id: groupV2IdString, }, this.removeFromCache.bind(this, envelope) @@ -3583,14 +3472,10 @@ export default class MessageReceiver if (blocked.groupIds) { const previous = this.storage.get('blocked-groups', []); - const groupV1Ids: Array = []; const groupIds: Array = []; blocked.groupIds.forEach(groupId => { - if (groupId.byteLength === GROUPV1_ID_LENGTH) { - groupV1Ids.push(Bytes.toBinary(groupId)); - groupIds.push(this.deriveGroupV2FromV1(groupId)); - } else if (groupId.byteLength === GROUPV2_ID_LENGTH) { + if (groupId.byteLength === GROUPV2_ID_LENGTH) { groupIds.push(Bytes.toBase64(groupId)); } else { log.error('handleBlocked: Received invalid groupId value'); @@ -3598,18 +3483,15 @@ export default class MessageReceiver }); log.info( 'handleBlocked: Blocking these groups - v2:', - groupIds.map(groupId => `groupv2(${groupId})`), - 'v1:', - groupV1Ids.map(groupId => `group(${groupId})`) + groupIds.map(groupId => `groupv2(${groupId})`) ); - const ids = [...groupIds, ...groupV1Ids]; - await this.storage.put('blocked-groups', ids); + await this.storage.put('blocked-groups', groupIds); - if (!areArraysMatchingSets(previous, ids)) { + if (!areArraysMatchingSets(previous, groupIds)) { changed = true; allIdentifiers.push(...previous); - allIdentifiers.push(...ids); + allIdentifiers.push(...groupIds); } } diff --git a/ts/textsecure/SendMessage.ts b/ts/textsecure/SendMessage.ts index 648e54cb8..9d2663130 100644 --- a/ts/textsecure/SendMessage.ts +++ b/ts/textsecure/SendMessage.ts @@ -103,10 +103,6 @@ export type GroupV2InfoType = { revision: number; members: ReadonlyArray; }; -export type GroupV1InfoType = { - id: string; - members: ReadonlyArray; -}; type GroupCallUpdateType = { eraId: string; @@ -207,7 +203,6 @@ export type GroupSendOptionsType = { expireTimer?: DurationInSeconds; flags?: number; groupCallUpdate?: GroupCallUpdateType; - groupV1?: GroupV1InfoType; groupV2?: GroupV2InfoType; messageText?: string; preview?: ReadonlyArray; @@ -381,10 +376,6 @@ class Message { proto.groupV2.masterKey = this.groupV2.masterKey; proto.groupV2.revision = this.groupV2.revision; proto.groupV2.groupChange = this.groupV2.groupChange || null; - } else if (this.group) { - proto.group = new Proto.GroupContext(); - proto.group.id = Bytes.fromString(this.group.id); - proto.group.type = this.group.type; } if (this.sticker) { proto.sticker = new Proto.DataMessage.Sticker(); @@ -1106,7 +1097,6 @@ export default class MessageSender { expireTimer, flags, groupCallUpdate, - groupV1, groupV2, messageText, preview, @@ -1118,16 +1108,16 @@ export default class MessageSender { timestamp, } = options; - if (!groupV1 && !groupV2) { + if (!groupV2) { throw new Error( - 'getAttrsFromGroupOptions: Neither group1 nor groupv2 information provided!' + 'getAttrsFromGroupOptions: No groupv2 information provided!' ); } const myE164 = window.textsecure.storage.user.getNumber(); const myUuid = window.textsecure.storage.user.getUuid()?.toString(); - const groupMembers = groupV2?.members || groupV1?.members || []; + const groupMembers = groupV2?.members || []; // We should always have a UUID but have this check just in case we don't. let isNotMe: (recipient: string) => boolean; @@ -1158,12 +1148,6 @@ export default class MessageSender { flags, groupCallUpdate, groupV2, - group: groupV1 - ? { - id: groupV1.id, - type: Proto.GroupContext.Type.DELIVER, - } - : undefined, preview, profileKey, quote, @@ -2424,50 +2408,6 @@ export default class MessageSender { }); } - // GroupV1-only functions; not to be used in the future - - async leaveGroup( - groupId: string, - groupIdentifiers: Array, - options?: SendOptionsType - ): Promise { - const timestamp = Date.now(); - const proto = new Proto.Content({ - dataMessage: { - group: { - id: Bytes.fromString(groupId), - type: Proto.GroupContext.Type.QUIT, - }, - }, - }); - - const { ContentHint } = Proto.UnidentifiedSenderMessage.Message; - - const contentHint = ContentHint.RESENDABLE; - const sendLogCallback = - groupIdentifiers.length > 1 - ? this.makeSendLogCallback({ - contentHint, - proto: Buffer.from(Proto.Content.encode(proto).finish()), - sendType: 'legacyGroupChange', - timestamp, - urgent: false, - hasPniSignatureMessage: false, - }) - : undefined; - - return this.sendGroupProto({ - contentHint, - groupId: undefined, // only for GV2 ids - options, - proto, - recipients: groupIdentifiers, - sendLogCallback, - timestamp, - urgent: false, - }); - } - // Simple pass-throughs // Note: instead of updating these functions, or adding new ones, remove these and go diff --git a/ts/textsecure/Types.d.ts b/ts/textsecure/Types.d.ts index 66d9a791d..c0ef65a61 100644 --- a/ts/textsecure/Types.d.ts +++ b/ts/textsecure/Types.d.ts @@ -118,17 +118,6 @@ export type ProcessedAttachment = { textAttachment?: Omit; }; -export type ProcessedGroupContext = { - id: string; - type: Proto.GroupContext.Type; - name?: string; - membersE164: ReadonlyArray; - avatar?: ProcessedAttachment; - - // Computed fields - derivedGroupV2Id: string; -}; - export type ProcessedGroupV2Context = { masterKey: string; revision?: number; @@ -208,7 +197,6 @@ export type ProcessedGiftBadge = { export type ProcessedDataMessage = { body?: string; attachments: ReadonlyArray; - group?: ProcessedGroupContext; groupV2?: ProcessedGroupV2Context; flags: number; expireTimer: DurationInSeconds; diff --git a/ts/textsecure/processDataMessage.ts b/ts/textsecure/processDataMessage.ts index 0ce2ef444..e475ae321 100644 --- a/ts/textsecure/processDataMessage.ts +++ b/ts/textsecure/processDataMessage.ts @@ -10,12 +10,10 @@ import { dropNull, shallowDropNull } from '../util/dropNull'; import { SignalService as Proto } from '../protobuf'; import { deriveGroupFields } from '../groups'; import * as Bytes from '../Bytes'; -import { deriveMasterKeyFromGroupV1 } from '../Crypto'; import type { ProcessedAttachment, ProcessedDataMessage, - ProcessedGroupContext, ProcessedGroupV2Context, ProcessedQuote, ProcessedContact, @@ -25,7 +23,6 @@ import type { ProcessedDelete, ProcessedGiftBadge, } from './Types.d'; -import { WarnOnlyError } from './Errors'; import { GiftBadgeStates } from '../components/conversation/Message'; import { APPLICATION_OCTET_STREAM, stringToMIMEType } from '../types/MIME'; import { SECOND, DurationInSeconds } from '../util/durations'; @@ -71,39 +68,6 @@ export function processAttachment( }; } -function processGroupContext( - group?: Proto.IGroupContext | null -): ProcessedGroupContext | undefined { - if (!group) { - return undefined; - } - - strictAssert(group.id, 'group context without id'); - strictAssert(group.type != null, 'group context without type'); - - const masterKey = deriveMasterKeyFromGroupV1(group.id); - const data = deriveGroupFields(masterKey); - - const derivedGroupV2Id = Bytes.toBase64(data.id); - - const result: ProcessedGroupContext = { - id: Bytes.toBinary(group.id), - type: group.type, - name: dropNull(group.name), - membersE164: group.membersE164 ?? [], - avatar: processAttachment(group.avatar), - derivedGroupV2Id, - }; - - if (result.type === Proto.GroupContext.Type.DELIVER) { - result.name = undefined; - result.membersE164 = []; - result.avatar = undefined; - } - - return result; -} - export function processGroupV2Context( groupV2?: Proto.IGroupContextV2 | null ): ProcessedGroupV2Context | undefined { @@ -331,7 +295,6 @@ export function processDataMessage( attachments: (message.attachments ?? []).map( (attachment: Proto.IAttachmentPointer) => processAttachment(attachment) ), - group: processGroupContext(message.group), groupV2: processGroupV2Context(message.groupV2), flags: message.flags ?? 0, expireTimer: DurationInSeconds.fromSeconds(message.expireTimer ?? 0), @@ -375,7 +338,6 @@ export function processDataMessage( if (isEndSession) { result.body = undefined; result.attachments = []; - result.group = undefined; return result; } @@ -389,27 +351,6 @@ export function processDataMessage( throw new Error(`Unknown flags in message: ${result.flags}`); } - if (result.group) { - switch (result.group.type) { - case Proto.GroupContext.Type.UPDATE: - result.body = undefined; - result.attachments = []; - break; - case Proto.GroupContext.Type.QUIT: - result.body = undefined; - result.attachments = []; - break; - case Proto.GroupContext.Type.DELIVER: - // Cleaned up in `processGroupContext` - break; - default: { - throw new WarnOnlyError( - `Unknown group message type: ${result.group.type}` - ); - } - } - } - const attachmentCount = result.attachments.length; if (attachmentCount > ATTACHMENT_MAX) { throw new Error( diff --git a/ts/util/sendToGroup.ts b/ts/util/sendToGroup.ts index 5c1945f4f..c23fabcfb 100644 --- a/ts/util/sendToGroup.ts +++ b/ts/util/sendToGroup.ts @@ -859,9 +859,6 @@ function getRecipients(options: GroupSendOptionsType): ReadonlyArray { if (options.groupV2) { return options.groupV2.members; } - if (options.groupV1) { - return options.groupV1.members; - } throw new Error('getRecipients: Unable to extract recipients!'); }