Fix GSE for delivery receipts in inactive groups
Co-authored-by: Jamie Kyle <113370520+jamiebuilds-signal@users.noreply.github.com>
This commit is contained in:
parent
08e8a7c6e9
commit
80367143cc
3 changed files with 33 additions and 36 deletions
|
@ -458,7 +458,9 @@ async function doGetProfile(
|
||||||
c: ConversationModel,
|
c: ConversationModel,
|
||||||
groupId: string | null
|
groupId: string | null
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const logId = `getProfile(${c.idForLogging()})`;
|
const logId = groupId
|
||||||
|
? `getProfile(${c.idForLogging()} in groupv2(${groupId}))`
|
||||||
|
: `getProfile(${c.idForLogging()})`;
|
||||||
const { messaging } = window.textsecure;
|
const { messaging } = window.textsecure;
|
||||||
strictAssert(
|
strictAssert(
|
||||||
messaging,
|
messaging,
|
||||||
|
|
|
@ -30,7 +30,7 @@ import { isTestOrMockEnvironment } from '../environment';
|
||||||
import { isAlpha } from './version';
|
import { isAlpha } from './version';
|
||||||
import { parseStrict } from './schemas';
|
import { parseStrict } from './schemas';
|
||||||
import { DataReader } from '../sql/Client';
|
import { DataReader } from '../sql/Client';
|
||||||
import { maybeUpdateGroup } from '../groups';
|
import { waitThenMaybeUpdateGroup } from '../groups';
|
||||||
import { isGroupV2 } from './whatTypeOfConversation';
|
import { isGroupV2 } from './whatTypeOfConversation';
|
||||||
|
|
||||||
export function decodeGroupSendEndorsementResponse({
|
export function decodeGroupSendEndorsementResponse({
|
||||||
|
@ -139,6 +139,7 @@ export function isValidGroupSendEndorsementsExpiration(
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GroupSendEndorsementState {
|
export class GroupSendEndorsementState {
|
||||||
|
#logId: string;
|
||||||
#combinedEndorsement: GroupSendCombinedEndorsementRecord;
|
#combinedEndorsement: GroupSendCombinedEndorsementRecord;
|
||||||
#memberEndorsements = new Map<
|
#memberEndorsements = new Map<
|
||||||
ServiceIdString,
|
ServiceIdString,
|
||||||
|
@ -153,6 +154,7 @@ export class GroupSendEndorsementState {
|
||||||
data: GroupSendEndorsementsData,
|
data: GroupSendEndorsementsData,
|
||||||
groupSecretParamsBase64: string
|
groupSecretParamsBase64: string
|
||||||
) {
|
) {
|
||||||
|
this.#logId = `GroupSendEndorsementState/groupv2(${data.combinedEndorsement.groupId})`;
|
||||||
this.#combinedEndorsement = data.combinedEndorsement;
|
this.#combinedEndorsement = data.combinedEndorsement;
|
||||||
this.#groupSecretParamsBase64 = groupSecretParamsBase64;
|
this.#groupSecretParamsBase64 = groupSecretParamsBase64;
|
||||||
this.#ourAci = window.textsecure.storage.user.getCheckedAci();
|
this.#ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||||
|
@ -194,7 +196,7 @@ export class GroupSendEndorsementState {
|
||||||
|
|
||||||
strictAssert(
|
strictAssert(
|
||||||
isValidGroupSendEndorsementsExpiration(expiration.getTime()),
|
isValidGroupSendEndorsementsExpiration(expiration.getTime()),
|
||||||
'Cannot build token with invalid expiration'
|
`${this.#logId}: toToken: Cannot build token with invalid expiration`
|
||||||
);
|
);
|
||||||
|
|
||||||
const fullToken = endorsement.toFullToken(groupSecretParams, expiration);
|
const fullToken = endorsement.toFullToken(groupSecretParams, expiration);
|
||||||
|
@ -218,7 +220,7 @@ export class GroupSendEndorsementState {
|
||||||
const memberEndorsement = this.#memberEndorsements.get(serviceId);
|
const memberEndorsement = this.#memberEndorsements.get(serviceId);
|
||||||
strictAssert(
|
strictAssert(
|
||||||
memberEndorsement,
|
memberEndorsement,
|
||||||
'subtractMemberEndorsements: Missing endorsement'
|
`${this.#logId}: getMemberEndorsement: Missing endorsement for ${serviceId}`
|
||||||
);
|
);
|
||||||
return this.#toEndorsement(memberEndorsement.endorsement);
|
return this.#toEndorsement(memberEndorsement.endorsement);
|
||||||
}
|
}
|
||||||
|
@ -231,7 +233,7 @@ export class GroupSendEndorsementState {
|
||||||
): GroupSendEndorsement {
|
): GroupSendEndorsement {
|
||||||
strictAssert(
|
strictAssert(
|
||||||
!otherMembersServiceIds.has(this.#ourAci),
|
!otherMembersServiceIds.has(this.#ourAci),
|
||||||
'subtractMemberEndorsements: Cannot subtract our own aci from the combined endorsement'
|
`${this.#logId}: subtractMemberEndorsements: Cannot subtract our own aci from the combined endorsement`
|
||||||
);
|
);
|
||||||
return this.#getCombinedEndorsement(includesOurs).byRemoving(
|
return this.#getCombinedEndorsement(includesOurs).byRemoving(
|
||||||
this.#combineMemberEndorsements(otherMembersServiceIds)
|
this.#combineMemberEndorsements(otherMembersServiceIds)
|
||||||
|
@ -253,7 +255,7 @@ export class GroupSendEndorsementState {
|
||||||
#buildToken(serviceIds: Set<ServiceIdString>): GroupSendEndorsement {
|
#buildToken(serviceIds: Set<ServiceIdString>): GroupSendEndorsement {
|
||||||
const sendCount = serviceIds.size;
|
const sendCount = serviceIds.size;
|
||||||
const memberCount = this.#memberEndorsements.size;
|
const memberCount = this.#memberEndorsements.size;
|
||||||
const logId = `GroupSendEndorsementState.buildToken(${sendCount} of ${memberCount})`;
|
const logId = `${this.#logId}: buildToken(${sendCount} of ${memberCount})`;
|
||||||
|
|
||||||
// Fast path sending to one person
|
// Fast path sending to one person
|
||||||
if (serviceIds.size === 1) {
|
if (serviceIds.size === 1) {
|
||||||
|
@ -351,6 +353,12 @@ export async function maybeCreateGroupSendEndorsementState(
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||||
if (conversation.isMember(ourAci)) {
|
if (conversation.isMember(ourAci)) {
|
||||||
|
if (!alreadyRefreshedGroupState) {
|
||||||
|
log.info(`${logId}: Missing endorsements for group, refreshing group`);
|
||||||
|
await waitThenMaybeUpdateGroup({ conversation, force: true });
|
||||||
|
return { state: null, didRefreshGroupState: true };
|
||||||
|
}
|
||||||
|
|
||||||
onFailedToSendWithEndorsements(
|
onFailedToSendWithEndorsements(
|
||||||
new Error(`${logId}: Missing all endorsements for group`)
|
new Error(`${logId}: Missing all endorsements for group`)
|
||||||
);
|
);
|
||||||
|
@ -374,7 +382,7 @@ export async function maybeCreateGroupSendEndorsementState(
|
||||||
log.info(
|
log.info(
|
||||||
`${logId}: Endorsements close to expiration (${groupSendEndorsementState.getExpiration().getTime()}, ${Date.now()}), refreshing group`
|
`${logId}: Endorsements close to expiration (${groupSendEndorsementState.getExpiration().getTime()}, ${Date.now()}), refreshing group`
|
||||||
);
|
);
|
||||||
await maybeUpdateGroup({ conversation });
|
await waitThenMaybeUpdateGroup({ conversation, force: true });
|
||||||
return { state: null, didRefreshGroupState: true };
|
return { state: null, didRefreshGroupState: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -296,22 +296,20 @@ export async function sendToGroupViaSenderKey(
|
||||||
} = options;
|
} = options;
|
||||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
const logId = sendTarget.idForLogging();
|
const logId = `sendToGroupViaSenderKey/${sendTarget.idForLogging()}`;
|
||||||
log.info(
|
log.info(
|
||||||
`sendToGroupViaSenderKey/${logId}: Starting ${timestamp}, recursion count ${recursion.count}, reason: ${recursion.reason}...`
|
`${logId}: Starting ${timestamp}, recursion count ${recursion.count}, reason: ${recursion.reason}...`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (recursion.count > MAX_RECURSION) {
|
if (recursion.count > MAX_RECURSION) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`sendToGroupViaSenderKey/${logId}: Too much recursion! Count is at ${recursion.count}`
|
`${logId}: Too much recursion! Count is at ${recursion.count}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupId = sendTarget.getGroupId();
|
const groupId = sendTarget.getGroupId();
|
||||||
if (!sendTarget.isValid()) {
|
if (!sendTarget.isValid()) {
|
||||||
throw new Error(
|
throw new Error(`${logId}: sendTarget is not valid!`);
|
||||||
`sendToGroupViaSenderKey/${logId}: sendTarget is not valid!`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -319,9 +317,7 @@ export async function sendToGroupViaSenderKey(
|
||||||
contentHint !== ContentHint.RESENDABLE &&
|
contentHint !== ContentHint.RESENDABLE &&
|
||||||
contentHint !== ContentHint.IMPLICIT
|
contentHint !== ContentHint.IMPLICIT
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(`${logId}: Invalid contentHint ${contentHint}`);
|
||||||
`sendToGroupViaSenderKey/${logId}: Invalid contentHint ${contentHint}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
strictAssert(
|
strictAssert(
|
||||||
|
@ -334,9 +330,7 @@ export async function sendToGroupViaSenderKey(
|
||||||
const senderKeyInfo = sendTarget.getSenderKeyInfo();
|
const senderKeyInfo = sendTarget.getSenderKeyInfo();
|
||||||
|
|
||||||
if (!senderKeyInfo) {
|
if (!senderKeyInfo) {
|
||||||
log.info(
|
log.info(`${logId}: Adding initial sender key info`);
|
||||||
`sendToGroupViaSenderKey/${logId}: Adding initial sender key info`
|
|
||||||
);
|
|
||||||
await sendTarget.saveSenderKeyInfo({
|
await sendTarget.saveSenderKeyInfo({
|
||||||
createdAtDate: Date.now(),
|
createdAtDate: Date.now(),
|
||||||
distributionId: generateUuid(),
|
distributionId: generateUuid(),
|
||||||
|
@ -350,9 +344,7 @@ export async function sendToGroupViaSenderKey(
|
||||||
const EXPIRE_DURATION = getSenderKeyExpireDuration();
|
const EXPIRE_DURATION = getSenderKeyExpireDuration();
|
||||||
if (isOlderThan(senderKeyInfo.createdAtDate, EXPIRE_DURATION)) {
|
if (isOlderThan(senderKeyInfo.createdAtDate, EXPIRE_DURATION)) {
|
||||||
const { createdAtDate } = senderKeyInfo;
|
const { createdAtDate } = senderKeyInfo;
|
||||||
log.info(
|
log.info(`${logId}: Resetting sender key; ${createdAtDate} is too old`);
|
||||||
`sendToGroupViaSenderKey/${logId}: Resetting sender key; ${createdAtDate} is too old`
|
|
||||||
);
|
|
||||||
await resetSenderKey(sendTarget);
|
await resetSenderKey(sendTarget);
|
||||||
|
|
||||||
// Restart here because we updated senderKeyInfo
|
// Restart here because we updated senderKeyInfo
|
||||||
|
@ -419,7 +411,7 @@ export async function sendToGroupViaSenderKey(
|
||||||
const senderKeyRecipients = getServiceIdsFromDevices(devicesForSenderKey);
|
const senderKeyRecipients = getServiceIdsFromDevices(devicesForSenderKey);
|
||||||
const normalSendRecipients = getServiceIdsFromDevices(devicesForNormalSend);
|
const normalSendRecipients = getServiceIdsFromDevices(devicesForNormalSend);
|
||||||
log.info(
|
log.info(
|
||||||
`sendToGroupViaSenderKey/${logId}:` +
|
`${logId}:` +
|
||||||
` ${senderKeyRecipients.length} accounts for sender key (${devicesForSenderKey.length} devices),` +
|
` ${senderKeyRecipients.length} accounts for sender key (${devicesForSenderKey.length} devices),` +
|
||||||
` ${normalSendRecipients.length} accounts for normal send (${devicesForNormalSend.length} devices)`
|
` ${normalSendRecipients.length} accounts for normal send (${devicesForNormalSend.length} devices)`
|
||||||
);
|
);
|
||||||
|
@ -427,7 +419,7 @@ export async function sendToGroupViaSenderKey(
|
||||||
// 5. Ensure we have enough recipients
|
// 5. Ensure we have enough recipients
|
||||||
if (senderKeyRecipients.length < 2) {
|
if (senderKeyRecipients.length < 2) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`sendToGroupViaSenderKey/${logId}: Not enough recipients for Sender Key message. Failing over.`
|
`${logId}: Not enough recipients for Sender Key message. Failing over.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,7 +452,7 @@ export async function sendToGroupViaSenderKey(
|
||||||
// have our sender key before we send sender key messages to them.
|
// have our sender key before we send sender key messages to them.
|
||||||
if (newToMemberServiceIds.length > 0) {
|
if (newToMemberServiceIds.length > 0) {
|
||||||
log.info(
|
log.info(
|
||||||
`sendToGroupViaSenderKey/${logId}: Sending sender key to ${
|
`${logId}: Sending sender key to ${
|
||||||
newToMemberServiceIds.length
|
newToMemberServiceIds.length
|
||||||
} members: ${JSON.stringify(newToMemberServiceIds)}`
|
} members: ${JSON.stringify(newToMemberServiceIds)}`
|
||||||
);
|
);
|
||||||
|
@ -583,7 +575,7 @@ export async function sendToGroupViaSenderKey(
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
log.error(
|
log.error(
|
||||||
`sendToGroupViaSenderKey/${logId}: Server returned unexpected 200 response ${JSON.stringify(
|
`${logId}: Server returned unexpected 200 response ${JSON.stringify(
|
||||||
parsed.error.flatten()
|
parsed.error.flatten()
|
||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
|
@ -637,7 +629,7 @@ export async function sendToGroupViaSenderKey(
|
||||||
const brokenAccount = window.ConversationController.get(name);
|
const brokenAccount = window.ConversationController.get(name);
|
||||||
if (brokenAccount) {
|
if (brokenAccount) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`sendToGroupViaSenderKey/${logId}: Disabling sealed sender for ${brokenAccount.idForLogging()}`
|
`${logId}: Disabling sealed sender for ${brokenAccount.idForLogging()}`
|
||||||
);
|
);
|
||||||
brokenAccount.set({ sealedSender: SEALED_SENDER.DISABLED });
|
brokenAccount.set({ sealedSender: SEALED_SENDER.DISABLED });
|
||||||
await DataWriter.updateConversation(brokenAccount.attributes);
|
await DataWriter.updateConversation(brokenAccount.attributes);
|
||||||
|
@ -652,7 +644,7 @@ export async function sendToGroupViaSenderKey(
|
||||||
}
|
}
|
||||||
|
|
||||||
log.error(
|
log.error(
|
||||||
`sendToGroupViaSenderKey/${logId}: Returned unexpected error code: ${
|
`${logId}: Returned unexpected error code: ${
|
||||||
error.code
|
error.code
|
||||||
}, error class: ${typeof error}`
|
}, error class: ${typeof error}`
|
||||||
);
|
);
|
||||||
|
@ -1381,11 +1373,8 @@ async function fetchKeysForServiceId(
|
||||||
devices: Array<number> | null,
|
devices: Array<number> | null,
|
||||||
groupSendEndorsementState: GroupSendEndorsementState | null
|
groupSendEndorsementState: GroupSendEndorsementState | null
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
log.info(
|
const logId = `fetchKeysForServiceId/${serviceId}`;
|
||||||
`fetchKeysForServiceId: Fetching ${
|
log.info(`${logId}: Fetching ${devices || 'all'} devices`);
|
||||||
devices || 'all'
|
|
||||||
} devices for ${serviceId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!window.textsecure?.messaging?.server) {
|
if (!window.textsecure?.messaging?.server) {
|
||||||
throw new Error('fetchKeysForServiceId: No server available!');
|
throw new Error('fetchKeysForServiceId: No server available!');
|
||||||
|
@ -1442,9 +1431,7 @@ async function fetchKeysForServiceId(
|
||||||
onFailedToSendWithEndorsements(error as Error);
|
onFailedToSendWithEndorsements(error as Error);
|
||||||
}
|
}
|
||||||
log.error(
|
log.error(
|
||||||
`fetchKeysForServiceId: Error fetching ${
|
`${logId}: Error fetching ${devices || 'all'} devices`,
|
||||||
devices || 'all'
|
|
||||||
} devices for ${serviceId}`,
|
|
||||||
Errors.toLogFormat(error)
|
Errors.toLogFormat(error)
|
||||||
);
|
);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
Loading…
Reference in a new issue