onDecryptionError/onRetryRequest: Don't run until queue is empty
This commit is contained in:
parent
fe49edce8a
commit
51af6947d4
4 changed files with 114 additions and 75 deletions
|
@ -51,28 +51,30 @@ import { LatestQueue } from './util/LatestQueue';
|
||||||
import { parseIntOrThrow } from './util/parseIntOrThrow';
|
import { parseIntOrThrow } from './util/parseIntOrThrow';
|
||||||
import { getProfile } from './util/getProfile';
|
import { getProfile } from './util/getProfile';
|
||||||
import {
|
import {
|
||||||
TypingEvent,
|
ConfigurationEvent,
|
||||||
ErrorEvent,
|
ContactEvent,
|
||||||
|
DecryptionErrorEvent,
|
||||||
DeliveryEvent,
|
DeliveryEvent,
|
||||||
SentEvent,
|
EnvelopeEvent,
|
||||||
SentEventData,
|
ErrorEvent,
|
||||||
ProfileKeyUpdateEvent,
|
FetchLatestEvent,
|
||||||
|
GroupEvent,
|
||||||
|
KeysEvent,
|
||||||
MessageEvent,
|
MessageEvent,
|
||||||
MessageEventData,
|
MessageEventData,
|
||||||
ReadEvent,
|
|
||||||
ViewEvent,
|
|
||||||
ConfigurationEvent,
|
|
||||||
ViewOnceOpenSyncEvent,
|
|
||||||
MessageRequestResponseEvent,
|
MessageRequestResponseEvent,
|
||||||
FetchLatestEvent,
|
ProfileKeyUpdateEvent,
|
||||||
KeysEvent,
|
ReadEvent,
|
||||||
StickerPackEvent,
|
|
||||||
VerifiedEvent,
|
|
||||||
ReadSyncEvent,
|
ReadSyncEvent,
|
||||||
|
RetryRequestEvent,
|
||||||
|
SentEvent,
|
||||||
|
SentEventData,
|
||||||
|
StickerPackEvent,
|
||||||
|
TypingEvent,
|
||||||
|
VerifiedEvent,
|
||||||
|
ViewEvent,
|
||||||
|
ViewOnceOpenSyncEvent,
|
||||||
ViewSyncEvent,
|
ViewSyncEvent,
|
||||||
ContactEvent,
|
|
||||||
GroupEvent,
|
|
||||||
EnvelopeEvent,
|
|
||||||
} from './textsecure/messageReceiverEvents';
|
} from './textsecure/messageReceiverEvents';
|
||||||
import type { WebAPIType } from './textsecure/WebAPI';
|
import type { WebAPIType } from './textsecure/WebAPI';
|
||||||
import * as KeyChangeListener from './textsecure/KeyChangeListener';
|
import * as KeyChangeListener from './textsecure/KeyChangeListener';
|
||||||
|
@ -258,11 +260,15 @@ export async function startApp(): Promise<void> {
|
||||||
);
|
);
|
||||||
messageReceiver.addEventListener(
|
messageReceiver.addEventListener(
|
||||||
'decryption-error',
|
'decryption-error',
|
||||||
queuedEventListener(onDecryptionError)
|
queuedEventListener((event: DecryptionErrorEvent) => {
|
||||||
|
onDecryptionErrorQueue.add(() => onDecryptionError(event));
|
||||||
|
})
|
||||||
);
|
);
|
||||||
messageReceiver.addEventListener(
|
messageReceiver.addEventListener(
|
||||||
'retry-request',
|
'retry-request',
|
||||||
queuedEventListener(onRetryRequest)
|
queuedEventListener((event: RetryRequestEvent) => {
|
||||||
|
onRetryRequestQueue.add(() => onRetryRequest(event));
|
||||||
|
})
|
||||||
);
|
);
|
||||||
messageReceiver.addEventListener('empty', queuedEventListener(onEmpty));
|
messageReceiver.addEventListener('empty', queuedEventListener(onEmpty));
|
||||||
messageReceiver.addEventListener(
|
messageReceiver.addEventListener(
|
||||||
|
@ -338,6 +344,12 @@ export async function startApp(): Promise<void> {
|
||||||
window.Signal.Services.lightSessionResetQueue = lightSessionResetQueue;
|
window.Signal.Services.lightSessionResetQueue = lightSessionResetQueue;
|
||||||
lightSessionResetQueue.pause();
|
lightSessionResetQueue.pause();
|
||||||
|
|
||||||
|
const onDecryptionErrorQueue = new window.PQueue();
|
||||||
|
onDecryptionErrorQueue.pause();
|
||||||
|
|
||||||
|
const onRetryRequestQueue = new window.PQueue();
|
||||||
|
onRetryRequestQueue.pause();
|
||||||
|
|
||||||
window.Whisper.deliveryReceiptQueue = new window.PQueue({
|
window.Whisper.deliveryReceiptQueue = new window.PQueue({
|
||||||
concurrency: 1,
|
concurrency: 1,
|
||||||
timeout: 1000 * 60 * 2,
|
timeout: 1000 * 60 * 2,
|
||||||
|
@ -2077,6 +2089,8 @@ export async function startApp(): Promise<void> {
|
||||||
// To avoid a flood of operations before we catch up, we pause some queues.
|
// To avoid a flood of operations before we catch up, we pause some queues.
|
||||||
profileKeyResponseQueue.pause();
|
profileKeyResponseQueue.pause();
|
||||||
lightSessionResetQueue.pause();
|
lightSessionResetQueue.pause();
|
||||||
|
onDecryptionErrorQueue.pause();
|
||||||
|
onRetryRequestQueue.pause();
|
||||||
window.Whisper.deliveryReceiptQueue.pause();
|
window.Whisper.deliveryReceiptQueue.pause();
|
||||||
notificationService.disable();
|
notificationService.disable();
|
||||||
|
|
||||||
|
@ -2327,6 +2341,8 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
profileKeyResponseQueue.start();
|
profileKeyResponseQueue.start();
|
||||||
lightSessionResetQueue.start();
|
lightSessionResetQueue.start();
|
||||||
|
onDecryptionErrorQueue.start();
|
||||||
|
onRetryRequestQueue.start();
|
||||||
window.Whisper.deliveryReceiptQueue.start();
|
window.Whisper.deliveryReceiptQueue.start();
|
||||||
notificationService.enable();
|
notificationService.enable();
|
||||||
|
|
||||||
|
@ -2391,6 +2407,8 @@ export async function startApp(): Promise<void> {
|
||||||
// notifications in these scenarios too. So we listen for 'reconnect' events.
|
// notifications in these scenarios too. So we listen for 'reconnect' events.
|
||||||
profileKeyResponseQueue.pause();
|
profileKeyResponseQueue.pause();
|
||||||
lightSessionResetQueue.pause();
|
lightSessionResetQueue.pause();
|
||||||
|
onDecryptionErrorQueue.pause();
|
||||||
|
onRetryRequestQueue.pause();
|
||||||
window.Whisper.deliveryReceiptQueue.pause();
|
window.Whisper.deliveryReceiptQueue.pause();
|
||||||
notificationService.disable();
|
notificationService.disable();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1447,7 +1447,6 @@ export default class MessageReceiver
|
||||||
try {
|
try {
|
||||||
return await this.innerDecrypt(stores, envelope, ciphertext);
|
return await this.innerDecrypt(stores, envelope, ciphertext);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.removeFromCache(envelope);
|
|
||||||
const uuid = envelope.sourceUuid;
|
const uuid = envelope.sourceUuid;
|
||||||
const deviceId = envelope.sourceDevice;
|
const deviceId = envelope.sourceDevice;
|
||||||
|
|
||||||
|
@ -1456,6 +1455,7 @@ export default class MessageReceiver
|
||||||
error?.message?.includes &&
|
error?.message?.includes &&
|
||||||
error.message.includes('message with old counter')
|
error.message.includes('message with old counter')
|
||||||
) {
|
) {
|
||||||
|
this.removeFromCache(envelope);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1465,6 +1465,7 @@ export default class MessageReceiver
|
||||||
error?.message?.includes &&
|
error?.message?.includes &&
|
||||||
error.message.includes('trust root validation failed')
|
error.message.includes('trust root validation failed')
|
||||||
) {
|
) {
|
||||||
|
this.removeFromCache(envelope);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1475,22 +1476,26 @@ export default class MessageReceiver
|
||||||
log.info(
|
log.info(
|
||||||
'MessageReceiver.decrypt: Error from blocked sender; no further processing'
|
'MessageReceiver.decrypt: Error from blocked sender; no further processing'
|
||||||
);
|
);
|
||||||
|
this.removeFromCache(envelope);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uuid && deviceId) {
|
if (uuid && deviceId) {
|
||||||
const { usmc } = envelope;
|
const { usmc } = envelope;
|
||||||
const event = new DecryptionErrorEvent({
|
const event = new DecryptionErrorEvent(
|
||||||
cipherTextBytes: usmc ? usmc.contents() : undefined,
|
{
|
||||||
cipherTextType: usmc ? usmc.msgType() : undefined,
|
cipherTextBytes: usmc ? usmc.contents() : undefined,
|
||||||
contentHint: envelope.contentHint,
|
cipherTextType: usmc ? usmc.msgType() : undefined,
|
||||||
groupId: envelope.groupId,
|
contentHint: envelope.contentHint,
|
||||||
receivedAtCounter: envelope.receivedAtCounter,
|
groupId: envelope.groupId,
|
||||||
receivedAtDate: envelope.receivedAtDate,
|
receivedAtCounter: envelope.receivedAtCounter,
|
||||||
senderDevice: deviceId,
|
receivedAtDate: envelope.receivedAtDate,
|
||||||
senderUuid: uuid,
|
senderDevice: deviceId,
|
||||||
timestamp: envelope.timestamp,
|
senderUuid: uuid,
|
||||||
});
|
timestamp: envelope.timestamp,
|
||||||
|
},
|
||||||
|
() => this.removeFromCache(envelope)
|
||||||
|
);
|
||||||
|
|
||||||
// Avoid deadlocks by scheduling processing on decrypted queue
|
// Avoid deadlocks by scheduling processing on decrypted queue
|
||||||
this.addToQueue(
|
this.addToQueue(
|
||||||
|
@ -1499,6 +1504,7 @@ export default class MessageReceiver
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const envelopeId = this.getEnvelopeId(envelope);
|
const envelopeId = this.getEnvelopeId(envelope);
|
||||||
|
this.removeFromCache(envelope);
|
||||||
log.error(
|
log.error(
|
||||||
`MessageReceiver.decrypt: Envelope ${envelopeId} missing uuid or deviceId`
|
`MessageReceiver.decrypt: Envelope ${envelopeId} missing uuid or deviceId`
|
||||||
);
|
);
|
||||||
|
@ -1801,22 +1807,24 @@ export default class MessageReceiver
|
||||||
const buffer = Buffer.from(decryptionError);
|
const buffer = Buffer.from(decryptionError);
|
||||||
const request = DecryptionErrorMessage.deserialize(buffer);
|
const request = DecryptionErrorMessage.deserialize(buffer);
|
||||||
|
|
||||||
this.removeFromCache(envelope);
|
|
||||||
|
|
||||||
const { sourceUuid, sourceDevice } = envelope;
|
const { sourceUuid, sourceDevice } = envelope;
|
||||||
if (!sourceUuid || !sourceDevice) {
|
if (!sourceUuid || !sourceDevice) {
|
||||||
log.error(`handleDecryptionError/${logId}: Missing uuid or device!`);
|
log.error(`handleDecryptionError/${logId}: Missing uuid or device!`);
|
||||||
|
this.removeFromCache(envelope);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = new RetryRequestEvent({
|
const event = new RetryRequestEvent(
|
||||||
groupId: envelope.groupId,
|
{
|
||||||
requesterDevice: sourceDevice,
|
groupId: envelope.groupId,
|
||||||
requesterUuid: sourceUuid,
|
requesterDevice: sourceDevice,
|
||||||
ratchetKey: request.ratchetKey(),
|
requesterUuid: sourceUuid,
|
||||||
senderDevice: request.deviceId(),
|
ratchetKey: request.ratchetKey(),
|
||||||
sentAt: request.timestamp(),
|
senderDevice: request.deviceId(),
|
||||||
});
|
sentAt: request.timestamp(),
|
||||||
|
},
|
||||||
|
() => this.removeFromCache(envelope)
|
||||||
|
);
|
||||||
await this.dispatchEvent(event);
|
await this.dispatchEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,39 +78,6 @@ export class ErrorEvent extends Event {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DecryptionErrorEventData = Readonly<{
|
|
||||||
cipherTextBytes?: Uint8Array;
|
|
||||||
cipherTextType?: number;
|
|
||||||
contentHint?: number;
|
|
||||||
groupId?: string;
|
|
||||||
receivedAtCounter: number;
|
|
||||||
receivedAtDate: number;
|
|
||||||
senderDevice: number;
|
|
||||||
senderUuid: string;
|
|
||||||
timestamp: number;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export class DecryptionErrorEvent extends Event {
|
|
||||||
constructor(public readonly decryptionError: DecryptionErrorEventData) {
|
|
||||||
super('decryption-error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RetryRequestEventData = Readonly<{
|
|
||||||
groupId?: string;
|
|
||||||
ratchetKey?: PublicKey;
|
|
||||||
requesterUuid: string;
|
|
||||||
requesterDevice: number;
|
|
||||||
senderDevice: number;
|
|
||||||
sentAt: number;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export class RetryRequestEvent extends Event {
|
|
||||||
constructor(public readonly retryRequest: RetryRequestEventData) {
|
|
||||||
super('retry-request');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ContactEvent extends Event {
|
export class ContactEvent extends Event {
|
||||||
constructor(public readonly contactDetails: ModifiedContactDetails) {
|
constructor(public readonly contactDetails: ModifiedContactDetails) {
|
||||||
super('contact');
|
super('contact');
|
||||||
|
@ -175,6 +142,45 @@ export class DeliveryEvent extends ConfirmableEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DecryptionErrorEventData = Readonly<{
|
||||||
|
cipherTextBytes?: Uint8Array;
|
||||||
|
cipherTextType?: number;
|
||||||
|
contentHint?: number;
|
||||||
|
groupId?: string;
|
||||||
|
receivedAtCounter: number;
|
||||||
|
receivedAtDate: number;
|
||||||
|
senderDevice: number;
|
||||||
|
senderUuid: string;
|
||||||
|
timestamp: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export class DecryptionErrorEvent extends ConfirmableEvent {
|
||||||
|
constructor(
|
||||||
|
public readonly decryptionError: DecryptionErrorEventData,
|
||||||
|
confirm: ConfirmCallback
|
||||||
|
) {
|
||||||
|
super('decryption-error', confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RetryRequestEventData = Readonly<{
|
||||||
|
groupId?: string;
|
||||||
|
ratchetKey?: PublicKey;
|
||||||
|
requesterUuid: string;
|
||||||
|
requesterDevice: number;
|
||||||
|
senderDevice: number;
|
||||||
|
sentAt: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export class RetryRequestEvent extends ConfirmableEvent {
|
||||||
|
constructor(
|
||||||
|
public readonly retryRequest: RetryRequestEventData,
|
||||||
|
confirm: ConfirmCallback
|
||||||
|
) {
|
||||||
|
super('retry-request', confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type SentEventData = Readonly<{
|
export type SentEventData = Readonly<{
|
||||||
destination?: string;
|
destination?: string;
|
||||||
destinationUuid?: string;
|
destinationUuid?: string;
|
||||||
|
|
|
@ -34,7 +34,7 @@ import * as log from '../logging/log';
|
||||||
// Entrypoints
|
// Entrypoints
|
||||||
|
|
||||||
export async function onRetryRequest(event: RetryRequestEvent): Promise<void> {
|
export async function onRetryRequest(event: RetryRequestEvent): Promise<void> {
|
||||||
const { retryRequest } = event;
|
const { confirm, retryRequest } = event;
|
||||||
const {
|
const {
|
||||||
groupId: requestGroupId,
|
groupId: requestGroupId,
|
||||||
requesterDevice,
|
requesterDevice,
|
||||||
|
@ -50,6 +50,7 @@ export async function onRetryRequest(event: RetryRequestEvent): Promise<void> {
|
||||||
log.warn(
|
log.warn(
|
||||||
`onRetryRequest/${logId}: Feature flag disabled, returning early.`
|
`onRetryRequest/${logId}: Feature flag disabled, returning early.`
|
||||||
);
|
);
|
||||||
|
confirm();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +81,7 @@ export async function onRetryRequest(event: RetryRequestEvent): Promise<void> {
|
||||||
`onRetryRequest/${logId}: Message is too old, refusing to send again.`
|
`onRetryRequest/${logId}: Message is too old, refusing to send again.`
|
||||||
);
|
);
|
||||||
await sendDistributionMessageOrNullMessage(logId, retryRequest);
|
await sendDistributionMessageOrNullMessage(logId, retryRequest);
|
||||||
|
confirm();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +94,7 @@ export async function onRetryRequest(event: RetryRequestEvent): Promise<void> {
|
||||||
if (!sentProto) {
|
if (!sentProto) {
|
||||||
log.info(`onRetryRequest/${logId}: Did not find sent proto`);
|
log.info(`onRetryRequest/${logId}: Did not find sent proto`);
|
||||||
await sendDistributionMessageOrNullMessage(logId, retryRequest);
|
await sendDistributionMessageOrNullMessage(logId, retryRequest);
|
||||||
|
confirm();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,6 +128,9 @@ export async function onRetryRequest(event: RetryRequestEvent): Promise<void> {
|
||||||
messageIds: [],
|
messageIds: [],
|
||||||
sendType: 'resendFromLog',
|
sendType: 'resendFromLog',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
confirm();
|
||||||
|
log.info(`onRetryRequest/${logId}: Resend complete.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybeShowDecryptionToast(logId: string) {
|
function maybeShowDecryptionToast(logId: string) {
|
||||||
|
@ -141,7 +147,7 @@ function maybeShowDecryptionToast(logId: string) {
|
||||||
export async function onDecryptionError(
|
export async function onDecryptionError(
|
||||||
event: DecryptionErrorEvent
|
event: DecryptionErrorEvent
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { decryptionError } = event;
|
const { confirm, decryptionError } = event;
|
||||||
const { senderUuid, senderDevice, timestamp } = decryptionError;
|
const { senderUuid, senderDevice, timestamp } = decryptionError;
|
||||||
const logId = `${senderUuid}.${senderDevice} ${timestamp}`;
|
const logId = `${senderUuid}.${senderDevice} ${timestamp}`;
|
||||||
|
|
||||||
|
@ -166,6 +172,7 @@ export async function onDecryptionError(
|
||||||
await startAutomaticSessionReset(decryptionError);
|
await startAutomaticSessionReset(decryptionError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
confirm();
|
||||||
log.info(`onDecryptionError/${logId}: ...complete`);
|
log.info(`onDecryptionError/${logId}: ...complete`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue