Message Send Log to enable comprehensive resend

This commit is contained in:
Scott Nonnenberg
2021-07-15 16:48:09 -07:00
committed by GitHub
parent 0fe68b57b1
commit a42c41ed01
37 changed files with 3154 additions and 1266 deletions
+90 -23
View File
@@ -1028,7 +1028,7 @@ class MessageReceiverInner extends EventTarget {
} catch (error) {
const args = [
'queueEncryptedEnvelope error handling envelope',
this.getEnvelopeId(envelope),
this.getEnvelopeId(error.envelope || envelope),
':',
error && error.extra ? JSON.stringify(error.extra) : '',
error && error.stack ? error.stack : error,
@@ -1587,7 +1587,10 @@ class MessageReceiverInner extends EventTarget {
});
// Avoid deadlocks by scheduling processing on decrypted queue
this.addToQueue(() => this.dispatchAndWait(event), TaskType.Decrypted);
this.addToQueue(
async () => this.dispatchEvent(event),
TaskType.Decrypted
);
} else {
const envelopeId = this.getEnvelopeId(newEnvelope);
window.log.error(
@@ -1803,39 +1806,98 @@ class MessageReceiverInner extends EventTarget {
);
assert(envelope.content, 'Should have `content` field');
const result = await this.decrypt(stores, envelope, envelope.content);
if (!result.plaintext) {
window.log.warn('decryptContentMessage: plaintext was falsey');
return result;
}
return result;
}
async innerHandleContentMessage(
envelope: ProcessedEnvelope,
plaintext: Uint8Array
): Promise<void> {
const content = Proto.Content.decode(plaintext);
// Note: a distribution message can be tacked on to any other message, so we
// make sure to process it first. If that fails, we still try to process
// the rest of the message.
// Note: we need to process this as part of decryption, because we might need this
// sender key to decrypt the next message in the queue!
try {
const content = Proto.Content.decode(result.plaintext);
if (
content.senderKeyDistributionMessage &&
Bytes.isNotEmpty(content.senderKeyDistributionMessage)
) {
await this.handleSenderKeyDistributionMessage(
envelope,
stores,
result.envelope,
content.senderKeyDistributionMessage
);
}
} catch (error) {
const errorString = error && error.stack ? error.stack : error;
window.log.error(
`innerHandleContentMessage: Failed to process sender key distribution message: ${errorString}`
`decryptContentMessage: Failed to process sender key distribution message: ${errorString}`
);
}
return result;
}
async maybeUpdateTimestamp(
envelope: ProcessedEnvelope
): Promise<ProcessedEnvelope> {
const { retryPlaceholders } = window.Signal.Services;
if (!retryPlaceholders) {
window.log.warn(
'maybeUpdateTimestamp: retry placeholders not available!'
);
return envelope;
}
const { timestamp } = envelope;
const identifier =
envelope.groupId || envelope.sourceUuid || envelope.source;
const conversation = window.ConversationController.get(identifier);
try {
if (!conversation) {
window.log.info(
`maybeUpdateTimestamp/${timestamp}: No conversation found for identifier ${identifier}`
);
return envelope;
}
const logId = `${conversation.idForLogging()}/${timestamp}`;
const item = await retryPlaceholders.findByMessageAndRemove(
conversation.id,
timestamp
);
if (item && item.wasOpened) {
window.log.info(
`maybeUpdateTimestamp/${logId}: found retry placeholder, but conversation was opened. No updates made.`
);
} else if (item) {
window.log.info(
`maybeUpdateTimestamp/${logId}: found retry placeholder. Updating receivedAtCounter/receivedAtDate`
);
return {
...envelope,
receivedAtCounter: item.receivedAtCounter,
receivedAtDate: item.receivedAt,
};
}
} catch (error) {
const errorString = error && error.stack ? error.stack : error;
window.log.error(
`maybeUpdateTimestamp/${timestamp}: Failed to process sender key distribution message: ${errorString}`
);
}
return envelope;
}
async innerHandleContentMessage(
incomingEnvelope: ProcessedEnvelope,
plaintext: Uint8Array
): Promise<void> {
const content = Proto.Content.decode(plaintext);
const envelope = await this.maybeUpdateTimestamp(incomingEnvelope);
if (
content.decryptionErrorMessage &&
Bytes.isNotEmpty(content.decryptionErrorMessage)
@@ -1908,10 +1970,11 @@ class MessageReceiverInner extends EventTarget {
senderDevice: request.deviceId(),
sentAt: request.timestamp(),
});
await this.dispatchAndWait(event);
await this.dispatchEvent(event);
}
async handleSenderKeyDistributionMessage(
stores: LockedStores,
envelope: ProcessedEnvelope,
distributionMessage: Uint8Array
): Promise<void> {
@@ -1941,12 +2004,15 @@ class MessageReceiverInner extends EventTarget {
const senderKeyStore = new SenderKeys();
const address = `${identifier}.${sourceDevice}`;
await window.textsecure.storage.protocol.enqueueSenderKeyJob(address, () =>
processSenderKeyDistributionMessage(
sender,
senderKeyDistributionMessage,
senderKeyStore
)
await window.textsecure.storage.protocol.enqueueSenderKeyJob(
address,
() =>
processSenderKeyDistributionMessage(
sender,
senderKeyDistributionMessage,
senderKeyStore
),
stores.zone
);
}
@@ -1989,6 +2055,7 @@ class MessageReceiverInner extends EventTarget {
envelopeTimestamp: envelope.timestamp,
source: envelope.source,
sourceUuid: envelope.sourceUuid,
sourceDevice: envelope.sourceDevice,
},
this.removeFromCache.bind(this, envelope)
);
+77 -16
View File
@@ -48,6 +48,11 @@ export const enum SenderCertificateMode {
WithoutE164,
}
export type SendLogCallbackType = (options: {
identifier: string;
deviceIds: Array<number>;
}) => Promise<void>;
type SendMetadata = {
type: number;
destinationDeviceId: number;
@@ -123,11 +128,11 @@ export default class OutgoingMessage {
errors: Array<CustomError>;
successfulIdentifiers: Array<unknown>;
successfulIdentifiers: Array<string>;
failoverIdentifiers: Array<unknown>;
failoverIdentifiers: Array<string>;
unidentifiedDeliveries: Array<unknown>;
unidentifiedDeliveries: Array<string>;
sendMetadata?: SendMetadataType;
@@ -137,16 +142,31 @@ export default class OutgoingMessage {
contentHint: number;
constructor(
server: WebAPIType,
timestamp: number,
identifiers: Array<string>,
message: Proto.Content | Proto.DataMessage | PlaintextContent,
contentHint: number,
groupId: string | undefined,
callback: (result: CallbackResultType) => void,
options: OutgoingMessageOptionsType = {}
) {
recipients: Record<string, Array<number>>;
sendLogCallback?: SendLogCallbackType;
constructor({
callback,
contentHint,
groupId,
identifiers,
message,
options,
sendLogCallback,
server,
timestamp,
}: {
callback: (result: CallbackResultType) => void;
contentHint: number;
groupId: string | undefined;
identifiers: Array<string>;
message: Proto.Content | Proto.DataMessage | PlaintextContent;
options?: OutgoingMessageOptionsType;
sendLogCallback?: SendLogCallbackType;
server: WebAPIType;
timestamp: number;
}) {
if (message instanceof Proto.DataMessage) {
const content = new Proto.Content();
content.dataMessage = message;
@@ -168,20 +188,29 @@ export default class OutgoingMessage {
this.successfulIdentifiers = [];
this.failoverIdentifiers = [];
this.unidentifiedDeliveries = [];
this.recipients = {};
this.sendLogCallback = sendLogCallback;
const { sendMetadata, online } = options;
this.sendMetadata = sendMetadata;
this.online = online;
this.sendMetadata = options?.sendMetadata;
this.online = options?.online;
}
numberCompleted(): void {
this.identifiersCompleted += 1;
if (this.identifiersCompleted >= this.identifiers.length) {
const contentProto = this.getContentProtoBytes();
const { timestamp, contentHint, recipients } = this;
this.callback({
successfulIdentifiers: this.successfulIdentifiers,
failoverIdentifiers: this.failoverIdentifiers,
errors: this.errors,
unidentifiedDeliveries: this.unidentifiedDeliveries,
contentHint,
recipients,
contentProto,
timestamp,
});
}
}
@@ -313,6 +342,14 @@ export default class OutgoingMessage {
return toArrayBuffer(this.plaintext);
}
getContentProtoBytes(): Uint8Array | undefined {
if (this.message instanceof Proto.Content) {
return new Uint8Array(Proto.Content.encode(this.message).finish());
}
return undefined;
}
async getCiphertextMessage({
identityKeyStore,
protocolAddress,
@@ -455,9 +492,21 @@ export default class OutgoingMessage {
accessKey,
}).then(
() => {
this.recipients[identifier] = deviceIds;
this.unidentifiedDeliveries.push(identifier);
this.successfulIdentifiers.push(identifier);
this.numberCompleted();
if (this.sendLogCallback) {
this.sendLogCallback({
identifier,
deviceIds,
});
} else if (this.successfulIdentifiers.length > 1) {
window.log.warn(
`OutgoingMessage.doSendMessage: no sendLogCallback provided for message ${this.timestamp}, but multiple recipients`
);
}
},
async (error: Error) => {
if (error.code === 401 || error.code === 403) {
@@ -481,7 +530,19 @@ export default class OutgoingMessage {
return this.transmitMessage(identifier, jsonData, this.timestamp).then(
() => {
this.successfulIdentifiers.push(identifier);
this.recipients[identifier] = deviceIds;
this.numberCompleted();
if (this.sendLogCallback) {
this.sendLogCallback({
identifier,
deviceIds,
});
} else if (this.successfulIdentifiers.length > 1) {
window.log.warn(
`OutgoingMessage.doSendMessage: no sendLogCallback provided for message ${this.timestamp}, but multiple recipients`
);
}
}
);
})
+326 -225
View File
@@ -28,7 +28,10 @@ import {
MultiRecipient200ResponseType,
} from './WebAPI';
import createTaskWithTimeout from './TaskWithTimeout';
import OutgoingMessage, { SerializedCertificateType } from './OutgoingMessage';
import OutgoingMessage, {
SerializedCertificateType,
SendLogCallbackType,
} from './OutgoingMessage';
import Crypto from './Crypto';
import * as Bytes from '../Bytes';
import {
@@ -48,6 +51,11 @@ import {
LinkPreviewMetadata,
} from '../linkPreviews/linkPreviewFetch';
import { concat } from '../util/iterables';
import {
handleMessageSend,
shouldSaveProto,
SendTypesType,
} from '../util/handleMessageSend';
import { SignalService as Proto } from '../protobuf';
export type SendMetadataType = {
@@ -68,11 +76,17 @@ export type CustomError = Error & {
};
export type CallbackResultType = {
successfulIdentifiers?: Array<any>;
failoverIdentifiers?: Array<any>;
successfulIdentifiers?: Array<string>;
failoverIdentifiers?: Array<string>;
errors?: Array<CustomError>;
unidentifiedDeliveries?: Array<any>;
unidentifiedDeliveries?: Array<string>;
dataMessage?: ArrayBuffer;
// Fields necesary for send log save
contentHint?: number;
contentProto?: Uint8Array;
timestamp?: number;
recipients?: Record<string, Array<number>>;
};
type PreviewType = {
@@ -593,9 +607,12 @@ export default class MessageSender {
try {
const { sticker } = message;
if (!sticker || !sticker.data) {
if (!sticker) {
return;
}
if (!sticker.data) {
throw new Error('uploadSticker: No sticker data to upload!');
}
// eslint-disable-next-line no-param-reassign
message.sticker = {
@@ -824,21 +841,23 @@ export default class MessageSender {
}
sendMessageProto({
timestamp,
recipients,
proto,
callback,
contentHint,
groupId,
callback,
options,
proto,
recipients,
sendLogCallback,
timestamp,
}: {
timestamp: number;
recipients: Array<string>;
proto: Proto.Content | Proto.DataMessage | PlaintextContent;
callback: (result: CallbackResultType) => void;
contentHint: number;
groupId: string | undefined;
callback: (result: CallbackResultType) => void;
options?: SendOptionsType;
proto: Proto.Content | Proto.DataMessage | PlaintextContent;
recipients: Array<string>;
sendLogCallback?: SendLogCallbackType;
timestamp: number;
}): void {
const rejections = window.textsecure.storage.get(
'signedKeyRotationRejected',
@@ -848,16 +867,17 @@ export default class MessageSender {
throw new SignedPreKeyRotationError();
}
const outgoing = new OutgoingMessage(
this.server,
timestamp,
recipients,
proto,
const outgoing = new OutgoingMessage({
callback,
contentHint,
groupId,
callback,
options
);
identifiers: recipients,
message: proto,
options,
sendLogCallback,
server: this.server,
timestamp,
});
recipients.forEach(identifier => {
this.queueJobForIdentifier(identifier, async () =>
@@ -992,6 +1012,8 @@ export default class MessageSender {
// Support for sync messages
// Note: this is used for sending real messages to your other devices after sending a
// message to others.
async sendSyncMessage({
encodedDataMessage,
timestamp,
@@ -1012,14 +1034,9 @@ export default class MessageSender {
unidentifiedDeliveries?: Array<string>;
isUpdate?: boolean;
options?: SendOptionsType;
}): Promise<CallbackResultType | void> {
}): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myDevice = window.textsecure.storage.user.getDeviceId();
if (myDevice === 1) {
return Promise.resolve();
}
const dataMessage = Proto.DataMessage.decode(
new FIXMEU8(encodedDataMessage)
@@ -1082,134 +1099,112 @@ export default class MessageSender {
identifier: myUuid || myNumber,
proto: contentMessage,
timestamp,
contentHint: ContentHint.IMPLICIT,
contentHint: ContentHint.RESENDABLE,
options,
});
}
async sendRequestBlockSyncMessage(
options?: SendOptionsType
): Promise<CallbackResultType | void> {
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myDevice = window.textsecure.storage.user.getDeviceId();
if (myDevice !== 1) {
const request = new Proto.SyncMessage.Request();
request.type = Proto.SyncMessage.Request.Type.BLOCKED;
const syncMessage = this.createSyncMessage();
syncMessage.request = request;
const contentMessage = new Proto.Content();
contentMessage.syncMessage = syncMessage;
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
const request = new Proto.SyncMessage.Request();
request.type = Proto.SyncMessage.Request.Type.BLOCKED;
const syncMessage = this.createSyncMessage();
syncMessage.request = request;
const contentMessage = new Proto.Content();
contentMessage.syncMessage = syncMessage;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
options,
});
}
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return Promise.resolve();
return this.sendIndividualProto({
identifier: myUuid || myNumber,
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
options,
});
}
async sendRequestConfigurationSyncMessage(
options?: SendOptionsType
): Promise<CallbackResultType | void> {
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myDevice = window.textsecure.storage.user.getDeviceId();
if (myDevice !== 1) {
const request = new Proto.SyncMessage.Request();
request.type = Proto.SyncMessage.Request.Type.CONFIGURATION;
const syncMessage = this.createSyncMessage();
syncMessage.request = request;
const contentMessage = new Proto.Content();
contentMessage.syncMessage = syncMessage;
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
const request = new Proto.SyncMessage.Request();
request.type = Proto.SyncMessage.Request.Type.CONFIGURATION;
const syncMessage = this.createSyncMessage();
syncMessage.request = request;
const contentMessage = new Proto.Content();
contentMessage.syncMessage = syncMessage;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
options,
});
}
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return Promise.resolve();
return this.sendIndividualProto({
identifier: myUuid || myNumber,
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
options,
});
}
async sendRequestGroupSyncMessage(
options?: SendOptionsType
): Promise<CallbackResultType | void> {
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myDevice = window.textsecure.storage.user.getDeviceId();
if (myDevice !== 1) {
const request = new Proto.SyncMessage.Request();
request.type = Proto.SyncMessage.Request.Type.GROUPS;
const syncMessage = this.createSyncMessage();
syncMessage.request = request;
const contentMessage = new Proto.Content();
contentMessage.syncMessage = syncMessage;
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
const request = new Proto.SyncMessage.Request();
request.type = Proto.SyncMessage.Request.Type.GROUPS;
const syncMessage = this.createSyncMessage();
syncMessage.request = request;
const contentMessage = new Proto.Content();
contentMessage.syncMessage = syncMessage;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
options,
});
}
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return Promise.resolve();
return this.sendIndividualProto({
identifier: myUuid || myNumber,
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
options,
});
}
async sendRequestContactSyncMessage(
options?: SendOptionsType
): Promise<CallbackResultType | void> {
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myDevice = window.textsecure.storage.user.getDeviceId();
if (myDevice !== 1) {
const request = new Proto.SyncMessage.Request();
request.type = Proto.SyncMessage.Request.Type.CONTACTS;
const syncMessage = this.createSyncMessage();
syncMessage.request = request;
const contentMessage = new Proto.Content();
contentMessage.syncMessage = syncMessage;
const request = new Proto.SyncMessage.Request();
request.type = Proto.SyncMessage.Request.Type.CONTACTS;
const syncMessage = this.createSyncMessage();
syncMessage.request = request;
const contentMessage = new Proto.Content();
contentMessage.syncMessage = syncMessage;
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
options,
});
}
return Promise.resolve();
return this.sendIndividualProto({
identifier: myUuid || myNumber,
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
options,
});
}
async sendFetchManifestSyncMessage(
options?: SendOptionsType
): Promise<CallbackResultType | void> {
): Promise<CallbackResultType> {
const myUuid = window.textsecure.storage.user.getUuid();
const myNumber = window.textsecure.storage.user.getNumber();
const myDevice = window.textsecure.storage.user.getDeviceId();
if (myDevice === 1) {
return;
}
const fetchLatest = new Proto.SyncMessage.FetchLatest();
fetchLatest.type = Proto.SyncMessage.FetchLatest.Type.STORAGE_MANIFEST;
@@ -1221,7 +1216,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
await this.sendIndividualProto({
return this.sendIndividualProto({
identifier: myUuid || myNumber,
proto: contentMessage,
timestamp: Date.now(),
@@ -1232,14 +1227,9 @@ export default class MessageSender {
async sendRequestKeySyncMessage(
options?: SendOptionsType
): Promise<CallbackResultType | void> {
): Promise<CallbackResultType> {
const myUuid = window.textsecure.storage.user.getUuid();
const myNumber = window.textsecure.storage.user.getNumber();
const myDevice = window.textsecure.storage.user.getDeviceId();
if (myDevice === 1) {
return;
}
const request = new Proto.SyncMessage.Request();
request.type = Proto.SyncMessage.Request.Type.KEYS;
@@ -1251,7 +1241,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
await this.sendIndividualProto({
return this.sendIndividualProto({
identifier: myUuid || myNumber,
proto: contentMessage,
timestamp: Date.now(),
@@ -1267,13 +1257,10 @@ export default class MessageSender {
timestamp: number;
}>,
options?: SendOptionsType
): Promise<CallbackResultType | void> {
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myDevice = window.textsecure.storage.user.getDeviceId();
if (myDevice === 1) {
return;
}
const syncMessage = this.createSyncMessage();
syncMessage.read = [];
for (let i = 0; i < reads.length; i += 1) {
@@ -1290,7 +1277,7 @@ export default class MessageSender {
identifier: myUuid || myNumber,
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
contentHint: ContentHint.RESENDABLE,
options,
});
}
@@ -1300,13 +1287,9 @@ export default class MessageSender {
senderUuid: string,
timestamp: number,
options?: SendOptionsType
): Promise<CallbackResultType | null> {
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myDevice = window.textsecure.storage.user.getDeviceId();
if (myDevice === 1) {
return null;
}
const syncMessage = this.createSyncMessage();
@@ -1327,7 +1310,7 @@ export default class MessageSender {
identifier: myUuid || myNumber,
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
contentHint: ContentHint.RESENDABLE,
options,
});
}
@@ -1340,13 +1323,9 @@ export default class MessageSender {
type: number;
},
options?: SendOptionsType
): Promise<CallbackResultType | null> {
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myDevice = window.textsecure.storage.user.getDeviceId();
if (myDevice === 1) {
return null;
}
const syncMessage = this.createSyncMessage();
@@ -1372,7 +1351,7 @@ export default class MessageSender {
identifier: myUuid || myNumber,
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
contentHint: ContentHint.RESENDABLE,
options,
});
}
@@ -1384,12 +1363,7 @@ export default class MessageSender {
installed: boolean;
}>,
options?: SendOptionsType
): Promise<CallbackResultType | null> {
const myDevice = window.textsecure.storage.user.getDeviceId();
if (myDevice === 1) {
return null;
}
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const ENUM = Proto.SyncMessage.StickerPackOperation.Type;
@@ -1423,57 +1397,60 @@ export default class MessageSender {
}
async syncVerification(
destinationE164: string,
destinationUuid: string,
destinationE164: string | undefined,
destinationUuid: string | undefined,
state: number,
identityKey: ArrayBuffer,
options?: SendOptionsType
): Promise<CallbackResultType | void> {
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myDevice = window.textsecure.storage.user.getDeviceId();
const now = Date.now();
if (myDevice === 1) {
return Promise.resolve();
if (!destinationE164 && !destinationUuid) {
throw new Error('syncVerification: Neither e164 nor UUID were provided');
}
// Get padding which we can share between null message and verified sync
const padding = this.getRandomPadding();
// First send a null message to mask the sync message.
const promise = this.sendNullMessage(
{ uuid: destinationUuid, e164: destinationE164, padding },
options
await handleMessageSend(
this.sendNullMessage(
{ uuid: destinationUuid, e164: destinationE164, padding },
options
),
{
messageIds: [],
sendType: 'nullMessage',
}
);
return promise.then(async () => {
const verified = new Proto.Verified();
verified.state = state;
if (destinationE164) {
verified.destination = destinationE164;
}
if (destinationUuid) {
verified.destinationUuid = destinationUuid;
}
verified.identityKey = new FIXMEU8(identityKey);
verified.nullMessage = padding;
const verified = new Proto.Verified();
verified.state = state;
if (destinationE164) {
verified.destination = destinationE164;
}
if (destinationUuid) {
verified.destinationUuid = destinationUuid;
}
verified.identityKey = new FIXMEU8(identityKey);
verified.nullMessage = padding;
const syncMessage = this.createSyncMessage();
syncMessage.verified = verified;
const syncMessage = this.createSyncMessage();
syncMessage.verified = verified;
const secondMessage = new Proto.Content();
secondMessage.syncMessage = syncMessage;
const secondMessage = new Proto.Content();
secondMessage.syncMessage = syncMessage;
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
await this.sendIndividualProto({
identifier: myUuid || myNumber,
proto: secondMessage,
timestamp: now,
contentHint: ContentHint.IMPLICIT,
options,
});
return this.sendIndividualProto({
identifier: myUuid || myNumber,
proto: secondMessage,
timestamp: now,
contentHint: ContentHint.RESENDABLE,
options,
});
}
@@ -1512,7 +1489,7 @@ export default class MessageSender {
recipientId: string,
callingMessage: Proto.ICallingMessage,
options?: SendOptionsType
): Promise<void> {
): Promise<CallbackResultType> {
const recipients = [recipientId];
const finalTimestamp = Date.now();
@@ -1521,7 +1498,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
await this.sendMessageProtoAndWait({
return this.sendMessageProtoAndWait({
timestamp: finalTimestamp,
recipients,
proto: contentMessage,
@@ -1537,16 +1514,15 @@ export default class MessageSender {
timestamps,
options,
}: {
e164: string;
uuid: string;
e164?: string;
uuid?: string;
timestamps: Array<number>;
options?: SendOptionsType;
}): Promise<CallbackResultType | void> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myDevice = window.textsecure.storage.user.getDeviceId();
if ((myNumber === e164 || myUuid === uuid) && myDevice === 1) {
return Promise.resolve();
}): Promise<CallbackResultType> {
if (!uuid && !e164) {
throw new Error(
'sendDeliveryReceipt: Neither uuid nor e164 was provided!'
);
}
const receiptMessage = new Proto.ReceiptMessage();
@@ -1562,7 +1538,7 @@ export default class MessageSender {
identifier: uuid || e164,
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
contentHint: ContentHint.RESENDABLE,
options,
});
}
@@ -1591,7 +1567,7 @@ export default class MessageSender {
identifier: senderUuid || senderE164,
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
contentHint: ContentHint.RESENDABLE,
options,
});
}
@@ -1634,9 +1610,7 @@ export default class MessageSender {
e164: string,
timestamp: number,
options?: SendOptionsType
): Promise<
CallbackResultType | void | Array<CallbackResultType | void | Array<void>>
> {
): Promise<CallbackResultType> {
window.log.info('resetSession: start');
const proto = new Proto.DataMessage();
proto.body = 'TERMINATE';
@@ -1659,19 +1633,27 @@ export default class MessageSender {
window.log.info(
'resetSession: finished closing local sessions, now sending to contact'
);
return this.sendIndividualProto({
identifier,
proto,
timestamp,
contentHint: ContentHint.DEFAULT,
options,
}).catch(logError('resetSession/sendToContact error:'));
return handleMessageSend(
this.sendIndividualProto({
identifier,
proto,
timestamp,
contentHint: ContentHint.RESENDABLE,
options,
}),
{
messageIds: [],
sendType: 'resetSession',
}
).catch(logError('resetSession/sendToContact error:'));
})
.then(async () =>
window.textsecure.storage.protocol
.then(async result => {
await window.textsecure.storage.protocol
.archiveAllSessions(identifier)
.catch(logError('resetSession/archiveAllSessions2 error:'))
);
.catch(logError('resetSession/archiveAllSessions2 error:'));
return result;
});
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
@@ -1694,7 +1676,12 @@ export default class MessageSender {
options,
}).catch(logError('resetSession/sendSync error:'));
return Promise.all([sendToContactPromise, sendSyncPromise]);
const responses = await Promise.all([
sendToContactPromise,
sendSyncPromise,
]);
return responses[0];
}
async sendExpirationTimerUpdateToIdentifier(
@@ -1714,17 +1701,19 @@ export default class MessageSender {
profileKey,
flags: Proto.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
},
contentHint: ContentHint.DEFAULT,
contentHint: ContentHint.RESENDABLE,
groupId: undefined,
options,
});
}
async sendRetryRequest({
groupId,
options,
plaintext,
uuid,
}: {
groupId?: string;
options?: SendOptionsType;
plaintext: PlaintextContent;
uuid: string;
@@ -1735,29 +1724,99 @@ export default class MessageSender {
timestamp: Date.now(),
recipients: [uuid],
proto: plaintext,
contentHint: ContentHint.IMPLICIT,
groupId: undefined,
contentHint: ContentHint.DEFAULT,
groupId,
options,
});
}
// Group sends
// Used to ensure that when we send to a group the old way, we save to the send log as
// we send to each recipient. Then we don't have a long delay between the first send
// and the final save to the database with all recipients.
makeSendLogCallback({
contentHint,
messageId,
proto,
sendType,
timestamp,
}: {
contentHint: number;
messageId?: string;
proto: Buffer;
sendType: SendTypesType;
timestamp: number;
}): SendLogCallbackType {
let initialSavePromise: Promise<number>;
return async ({
identifier,
deviceIds,
}: {
identifier: string;
deviceIds: Array<number>;
}) => {
if (!shouldSaveProto(sendType)) {
return;
}
const conversation = window.ConversationController.get(identifier);
if (!conversation) {
window.log.warn(
`makeSendLogCallback: Unable to find conversation for identifier ${identifier}`
);
return;
}
const recipientUuid = conversation.get('uuid');
if (!recipientUuid) {
window.log.warn(
`makeSendLogCallback: Conversation ${conversation.idForLogging()} had no UUID`
);
return;
}
if (!initialSavePromise) {
initialSavePromise = window.Signal.Data.insertSentProto(
{
timestamp,
proto,
contentHint,
},
{
recipients: { [recipientUuid]: deviceIds },
messageIds: messageId ? [messageId] : [],
}
);
await initialSavePromise;
} else {
const id = await initialSavePromise;
await window.Signal.Data.insertProtoRecipients({
id,
recipientUuid,
deviceIds,
});
}
};
}
// No functions should really call this; since most group sends are now via Sender Key
async sendGroupProto({
recipients,
proto,
timestamp = Date.now(),
contentHint,
groupId,
options,
proto,
recipients,
sendLogCallback,
timestamp = Date.now(),
}: {
recipients: Array<string>;
proto: Proto.Content;
timestamp: number;
contentHint: number;
groupId: string | undefined;
options?: SendOptionsType;
proto: Proto.Content;
recipients: Array<string>;
sendLogCallback?: SendLogCallbackType;
timestamp: number;
}): Promise<CallbackResultType> {
const dataMessage = proto.dataMessage
? typedArrayToArrayBuffer(
@@ -1790,13 +1849,14 @@ export default class MessageSender {
};
this.sendMessageProto({
timestamp,
recipients: identifiers,
proto,
callback,
contentHint,
groupId,
callback,
options,
proto,
recipients: identifiers,
sendLogCallback,
timestamp,
});
});
}
@@ -1846,19 +1906,31 @@ export default class MessageSender {
options?: SendOptionsType
): Promise<CallbackResultType> {
const contentMessage = new Proto.Content();
const timestamp = Date.now();
const senderKeyDistributionMessage = await this.getSenderKeyDistributionMessage(
distributionId
);
contentMessage.senderKeyDistributionMessage = senderKeyDistributionMessage.serialize();
const sendLogCallback =
identifiers.length > 1
? this.makeSendLogCallback({
contentHint,
proto: Buffer.from(Proto.Content.encode(contentMessage).finish()),
sendType: 'senderKeyDistributionMessage',
timestamp,
})
: undefined;
return this.sendGroupProto({
recipients: identifiers,
proto: contentMessage,
timestamp: Date.now(),
contentHint,
groupId,
options,
proto: contentMessage,
recipients: identifiers,
sendLogCallback,
timestamp,
});
}
@@ -1869,6 +1941,7 @@ export default class MessageSender {
groupIdentifiers: Array<string>,
options?: SendOptionsType
): Promise<CallbackResultType> {
const timestamp = Date.now();
const proto = new Proto.Content({
dataMessage: {
group: {
@@ -1879,13 +1952,26 @@ export default class MessageSender {
});
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,
})
: undefined;
return this.sendGroupProto({
recipients: groupIdentifiers,
proto,
timestamp: Date.now(),
contentHint: ContentHint.DEFAULT,
contentHint,
groupId: undefined, // only for GV2 ids
options,
proto,
recipients: groupIdentifiers,
sendLogCallback,
timestamp,
});
}
@@ -1913,6 +1999,7 @@ export default class MessageSender {
type: Proto.GroupContext.Type.DELIVER,
},
};
const proto = await this.getContentMessage(messageOptions);
if (recipients.length === 0) {
return Promise.resolve({
@@ -1925,11 +2012,25 @@ export default class MessageSender {
}
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return this.sendMessage({
messageOptions,
contentHint: ContentHint.DEFAULT,
const contentHint = ContentHint.RESENDABLE;
const sendLogCallback =
groupIdentifiers.length > 1
? this.makeSendLogCallback({
contentHint,
proto: Buffer.from(Proto.Content.encode(proto).finish()),
sendType: 'expirationTimerUpdate',
timestamp,
})
: undefined;
return this.sendGroupProto({
contentHint,
groupId: undefined, // only for GV2 ids
options,
proto,
recipients,
sendLogCallback,
timestamp,
});
}
+27 -9
View File
@@ -11,6 +11,8 @@ import MessageReceiver from './MessageReceiver';
import { ContactSyncEvent, GroupSyncEvent } from './messageReceiverEvents';
import MessageSender from './SendMessage';
import { assert } from '../util/assert';
import { getSendOptions } from '../util/getSendOptions';
import { handleMessageSend } from '../util/handleMessageSend';
class SyncRequestInner extends EventTarget {
private started = false;
@@ -61,25 +63,41 @@ class SyncRequestInner extends EventTarget {
const { sender } = this;
const ourNumber = window.textsecure.storage.user.getNumber();
const {
wrap,
sendOptions,
} = await window.ConversationController.prepareForSend(ourNumber, {
const ourConversation = window.ConversationController.getOurConversationOrThrow();
const sendOptions = await getSendOptions(ourConversation.attributes, {
syncMessage: true,
});
if (window.ConversationController.areWePrimaryDevice()) {
window.log.warn(
'SyncRequest.start: We are primary device; returning early'
);
return;
}
window.log.info('SyncRequest created. Sending config sync request...');
wrap(sender.sendRequestConfigurationSyncMessage(sendOptions));
handleMessageSend(sender.sendRequestConfigurationSyncMessage(sendOptions), {
messageIds: [],
sendType: 'otherSync',
});
window.log.info('SyncRequest now sending block sync request...');
wrap(sender.sendRequestBlockSyncMessage(sendOptions));
handleMessageSend(sender.sendRequestBlockSyncMessage(sendOptions), {
messageIds: [],
sendType: 'otherSync',
});
window.log.info('SyncRequest now sending contact sync message...');
wrap(sender.sendRequestContactSyncMessage(sendOptions))
handleMessageSend(sender.sendRequestContactSyncMessage(sendOptions), {
messageIds: [],
sendType: 'otherSync',
})
.then(() => {
window.log.info('SyncRequest now sending group sync message...');
return wrap(sender.sendRequestGroupSyncMessage(sendOptions));
return handleMessageSend(
sender.sendRequestGroupSyncMessage(sendOptions),
{ messageIds: [], sendType: 'otherSync' }
);
})
.catch((error: Error) => {
window.log.error(
+1
View File
@@ -75,6 +75,7 @@ export type ProcessedEnvelope = Readonly<{
content?: Uint8Array;
serverGuid: string;
serverTimestamp: number;
groupId?: string;
}>;
export type ProcessedAttachment = {
+1
View File
@@ -219,6 +219,7 @@ export type ReadEventData = Readonly<{
envelopeTimestamp: number;
source?: string;
sourceUuid?: string;
sourceDevice?: number;
}>;
export class ReadEvent extends ConfirmableEvent {