Use new compact representations in protobufs
Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
This commit is contained in:
parent
157496f822
commit
8251720444
64 changed files with 1000 additions and 459 deletions
|
@ -222,7 +222,7 @@
|
||||||
"@indutny/parallel-prettier": "3.0.0",
|
"@indutny/parallel-prettier": "3.0.0",
|
||||||
"@indutny/rezip-electron": "2.0.1",
|
"@indutny/rezip-electron": "2.0.1",
|
||||||
"@napi-rs/canvas": "0.1.61",
|
"@napi-rs/canvas": "0.1.61",
|
||||||
"@signalapp/mock-server": "12.0.0",
|
"@signalapp/mock-server": "13.0.0",
|
||||||
"@storybook/addon-a11y": "8.4.4",
|
"@storybook/addon-a11y": "8.4.4",
|
||||||
"@storybook/addon-actions": "8.4.4",
|
"@storybook/addon-actions": "8.4.4",
|
||||||
"@storybook/addon-controls": "8.4.4",
|
"@storybook/addon-controls": "8.4.4",
|
||||||
|
|
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
|
@ -430,8 +430,8 @@ importers:
|
||||||
specifier: 0.1.61
|
specifier: 0.1.61
|
||||||
version: 0.1.61
|
version: 0.1.61
|
||||||
'@signalapp/mock-server':
|
'@signalapp/mock-server':
|
||||||
specifier: 12.0.0
|
specifier: 13.0.0
|
||||||
version: 12.0.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
|
version: 13.0.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
|
||||||
'@storybook/addon-a11y':
|
'@storybook/addon-a11y':
|
||||||
specifier: 8.4.4
|
specifier: 8.4.4
|
||||||
version: 8.4.4(storybook@8.4.4(bufferutil@4.0.9)(prettier@3.3.3)(utf-8-validate@5.0.10))
|
version: 8.4.4(storybook@8.4.4(bufferutil@4.0.9)(prettier@3.3.3)(utf-8-validate@5.0.10))
|
||||||
|
@ -2770,8 +2770,8 @@ packages:
|
||||||
'@signalapp/libsignal-client@0.74.1':
|
'@signalapp/libsignal-client@0.74.1':
|
||||||
resolution: {integrity: sha512-PEJou0yrBvxaAGg7JjONlRNM/t3PCBuY96wu7W6+57e38/7Mibo9kAMfE5B8DgVv+DUNMW9AgJhx5McCoIXYew==}
|
resolution: {integrity: sha512-PEJou0yrBvxaAGg7JjONlRNM/t3PCBuY96wu7W6+57e38/7Mibo9kAMfE5B8DgVv+DUNMW9AgJhx5McCoIXYew==}
|
||||||
|
|
||||||
'@signalapp/mock-server@12.0.0':
|
'@signalapp/mock-server@13.0.0':
|
||||||
resolution: {integrity: sha512-5Ebu2c3/BViNsZ4yId8zfHyazMGUmsSfjMXXXFwNn7IYw0M0l/u+FFiR8SJdFnLoBbcxHG+KC3P+QqPdn91FIQ==}
|
resolution: {integrity: sha512-2HsUu8CDtf6g4yt34hDO7tfWVqZ4lPdXLAPM8NElKr/OjO1KiUGXljZFfxNIC5UDySVHaC2//ROcRE+qrhqCyw==}
|
||||||
|
|
||||||
'@signalapp/parchment-cjs@3.0.1':
|
'@signalapp/parchment-cjs@3.0.1':
|
||||||
resolution: {integrity: sha512-hSBMQ1M7wE4GcC8ZeNtvpJF+DAJg3eIRRf1SiHS3I3Algav/sgJJNm6HIYm6muHuK7IJmuEjkL3ILSXgmu0RfQ==}
|
resolution: {integrity: sha512-hSBMQ1M7wE4GcC8ZeNtvpJF+DAJg3eIRRf1SiHS3I3Algav/sgJJNm6HIYm6muHuK7IJmuEjkL3ILSXgmu0RfQ==}
|
||||||
|
@ -12466,7 +12466,7 @@ snapshots:
|
||||||
type-fest: 4.26.1
|
type-fest: 4.26.1
|
||||||
uuid: 11.0.2
|
uuid: 11.0.2
|
||||||
|
|
||||||
'@signalapp/mock-server@12.0.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)':
|
'@signalapp/mock-server@13.0.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@indutny/parallel-prettier': 3.0.0(prettier@3.3.3)
|
'@indutny/parallel-prettier': 3.0.0(prettier@3.3.3)
|
||||||
'@signalapp/libsignal-client': 0.60.2
|
'@signalapp/libsignal-client': 0.60.2
|
||||||
|
|
|
@ -1,41 +1,56 @@
|
||||||
// Copyright 2014 Signal Messenger, LLC
|
/*
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
* Copyright 2020 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
syntax = "proto2";
|
||||||
|
|
||||||
package signalservice;
|
package signalservice;
|
||||||
|
|
||||||
message ProvisioningUuid {
|
option java_package = "org.whispersystems.signalservice.internal.push";
|
||||||
optional string uuid = 1;
|
option java_outer_classname = "ProvisioningProtos";
|
||||||
}
|
|
||||||
|
|
||||||
|
// An opaque address sent by the server when clients first open a provisioning
|
||||||
|
// WebSocket
|
||||||
|
message ProvisioningAddress {
|
||||||
|
|
||||||
|
// The opaque provisioning address for the active provisioning WebSocket
|
||||||
|
// session; clients should not attempt to interpret or modify the contents
|
||||||
|
// of the address string
|
||||||
|
optional string address = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message ProvisionEnvelope {
|
message ProvisionEnvelope {
|
||||||
optional bytes publicKey = 1;
|
optional bytes publicKey = 1;
|
||||||
optional bytes body = 2; // Encrypted ProvisionMessage
|
optional bytes body = 2; // Encrypted ProvisionMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
message ProvisionMessage {
|
message ProvisionMessage {
|
||||||
optional bytes aciIdentityKeyPublic = 1;
|
optional bytes aciIdentityKeyPublic = 1;
|
||||||
optional bytes aciIdentityKeyPrivate = 2;
|
optional bytes aciIdentityKeyPrivate = 2;
|
||||||
optional bytes pniIdentityKeyPublic = 11;
|
optional bytes pniIdentityKeyPublic = 11;
|
||||||
optional bytes pniIdentityKeyPrivate = 12;
|
optional bytes pniIdentityKeyPrivate = 12;
|
||||||
optional string aci = 8;
|
optional string aci = 8;
|
||||||
optional string pni = 10;
|
optional string pni = 10;
|
||||||
optional string number = 3;
|
optional string number = 3;
|
||||||
optional string provisioningCode = 4;
|
optional string provisioningCode = 4;
|
||||||
optional string userAgent = 5;
|
optional string userAgent = 5;
|
||||||
optional bytes profileKey = 6;
|
optional bytes profileKey = 6;
|
||||||
optional bool readReceipts = 7;
|
optional bool readReceipts = 7;
|
||||||
optional uint32 ProvisioningVersion = 9;
|
optional uint32 provisioningVersion = 9;
|
||||||
optional bytes masterKey = 13;
|
optional bytes masterKey = 13; // Deprecated, but required by linked devices
|
||||||
optional bytes ephemeralBackupKey = 14; // 32 bytes
|
optional bytes ephemeralBackupKey = 14; // 32 bytes
|
||||||
optional string accountEntropyPool = 15;
|
optional string accountEntropyPool = 15;
|
||||||
optional bytes mediaRootBackupKey = 16; // 32-bytes
|
optional bytes mediaRootBackupKey = 16; // 32-bytes
|
||||||
|
optional bytes aciBinary = 17; // 16-byte UUID
|
||||||
|
optional bytes pniBinary = 18; // 16-byte UUID
|
||||||
|
// NEXT ID: 19
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ProvisioningVersion {
|
enum ProvisioningVersion {
|
||||||
option allow_alias = true;
|
option allow_alias = true;
|
||||||
|
|
||||||
INITIAL = 0;
|
INITIAL = 0;
|
||||||
TABLET_SUPPORT = 1;
|
TABLET_SUPPORT = 1;
|
||||||
CURRENT = 1;
|
CURRENT = 1;
|
||||||
}
|
}
|
||||||
|
|
41
protos/Migrations.proto
Normal file
41
protos/Migrations.proto
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright 2025 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package migrations;
|
||||||
|
|
||||||
|
// Snapshot made at 9f22445e9
|
||||||
|
|
||||||
|
message Envelope {
|
||||||
|
// Our parser does not handle reserved in enums: DESKTOP-1569
|
||||||
|
enum Type {
|
||||||
|
UNKNOWN = 0;
|
||||||
|
CIPHERTEXT = 1; // content => (version byte | SignalMessage{Content})
|
||||||
|
// reserved 2;
|
||||||
|
// reserved "KEY_EXCHANGE";
|
||||||
|
PREKEY_BUNDLE = 3; // content => (version byte | PreKeySignalMessage{Content})
|
||||||
|
SERVER_DELIVERY_RECEIPT = 5; // legacyMessage => [] AND content => []
|
||||||
|
UNIDENTIFIED_SENDER = 6; // legacyMessage => [] AND content => ((version byte | UnidentifiedSenderMessage) OR (version byte | Multi-Recipient Sealed Sender Format))
|
||||||
|
SENDERKEY_MESSAGE = 7; // legacyMessage => [] AND content => (version byte | SenderKeyMessage)
|
||||||
|
PLAINTEXT_CONTENT = 8; // legacyMessage => [] AND content => (marker byte | Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
optional Type type = 1;
|
||||||
|
reserved 2; // formerly optional string sourceE164 = 2;
|
||||||
|
optional string sourceServiceId = 11;
|
||||||
|
optional uint32 sourceDevice = 7;
|
||||||
|
optional string destinationServiceId = 13;
|
||||||
|
reserved 3; // formerly optional string relay = 3;
|
||||||
|
optional uint64 timestamp = 5;
|
||||||
|
reserved 6; // formerly optional bytes legacyMessage = 6; // Contains an encrypted DataMessage; this field could have been set historically for type 1 or 3 messages; no longer in use
|
||||||
|
optional bytes content = 8; // Contains an encrypted Content
|
||||||
|
optional string serverGuid = 9;
|
||||||
|
optional uint64 serverTimestamp = 10;
|
||||||
|
optional bool ephemeral = 12; // indicates that the message should not be persisted if the recipient is offline
|
||||||
|
optional bool urgent = 14 [default = true]; // indicates that the content is considered timely by the sender; defaults to true so senders have to opt-out to say something isn't time critical
|
||||||
|
optional string updatedPni = 15; // for number-change synchronization messages, provides the new server-assigned phone number identifier associated with the changed number
|
||||||
|
optional bool story = 16; // indicates that the content is a story.
|
||||||
|
optional bytes report_spam_token = 17; // token sent when reporting spam
|
||||||
|
reserved 18; // internal server use
|
||||||
|
// next: 19
|
||||||
|
}
|
|
@ -3,32 +3,89 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
syntax = "proto2";
|
||||||
|
|
||||||
package signalservice;
|
package signalservice;
|
||||||
|
|
||||||
option java_package = "org.whispersystems.signalservice.internal.push";
|
option java_package = "org.whispersystems.signalservice.internal.push";
|
||||||
option java_outer_classname = "SignalServiceProtos";
|
option java_outer_classname = "SignalServiceProtos";
|
||||||
|
|
||||||
message Envelope {
|
message Envelope {
|
||||||
// Our parser does not handle reserved in enums: DESKTOP-1569
|
|
||||||
enum Type {
|
enum Type {
|
||||||
UNKNOWN = 0;
|
UNKNOWN = 0;
|
||||||
CIPHERTEXT = 1; // content => (version byte | SignalMessage{Content})
|
|
||||||
// reserved 2;
|
/**
|
||||||
// reserved "KEY_EXCHANGE";
|
* A double-ratchet message represents a "normal," "unsealed-sender" message
|
||||||
PREKEY_BUNDLE = 3; // content => (version byte | PreKeySignalMessage{Content})
|
* encrypted using the Double Ratchet within an established Signal session.
|
||||||
SERVER_DELIVERY_RECEIPT = 5; // legacyMessage => [] AND content => []
|
* Double-ratchet messages include sender information in the plaintext
|
||||||
UNIDENTIFIED_SENDER = 6; // legacyMessage => [] AND content => ((version byte | UnidentifiedSenderMessage) OR (version byte | Multi-Recipient Sealed Sender Format))
|
* portion of the `Envelope`.
|
||||||
SENDERKEY_MESSAGE = 7; // legacyMessage => [] AND content => (version byte | SenderKeyMessage)
|
*/
|
||||||
PLAINTEXT_CONTENT = 8; // legacyMessage => [] AND content => (marker byte | Content)
|
DOUBLE_RATCHET = 1; // content => (version byte | SignalMessage{Content})
|
||||||
|
|
||||||
|
reserved 2;
|
||||||
|
reserved "KEY_EXCHANGE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A prekey message begins a new Signal session. The `content` of a prekey
|
||||||
|
* message is a superset of a double-ratchet message's `content` and
|
||||||
|
* contains the sender's identity public key and information identifying the
|
||||||
|
* pre-keys used in the message's ciphertext. Like double-ratchet messages,
|
||||||
|
* prekey messages contain sender information in the plaintext portion of
|
||||||
|
* the `Envelope`.
|
||||||
|
*/
|
||||||
|
PREKEY_MESSAGE = 3; // content => (version byte | PreKeySignalMessage{Content})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server delivery receipts are generated by the server when
|
||||||
|
* "unsealed-sender" messages are delivered to and acknowledged by the
|
||||||
|
* destination device. Server delivery receipts identify the sender in the
|
||||||
|
* plaintext portion of the `Envelope` and have no `content`. Note that
|
||||||
|
* receipts for sealed-sender messages are generated by clients as
|
||||||
|
* `UNIDENTIFIED_SENDER` messages.
|
||||||
|
*
|
||||||
|
* Note that, with server delivery receipts, the "client timestamp" on
|
||||||
|
* the envelope refers to the timestamp of the original message (i.e. the
|
||||||
|
* message the server just delivered) and not to the time of delivery. The
|
||||||
|
* "server timestamp" refers to the time of delivery.
|
||||||
|
*/
|
||||||
|
SERVER_DELIVERY_RECEIPT = 5; // content => []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An unidentified sender message represents a message with no sender
|
||||||
|
* information in the plaintext portion of the `Envelope`. Unidentified
|
||||||
|
* sender messages always contain an additional `subtype` in their
|
||||||
|
* `content`. They may or may not be part of an existing Signal session
|
||||||
|
* (i.e. an unidentified sender message may have a "prekey message"
|
||||||
|
* subtype or may indicate an encryption error).
|
||||||
|
*/
|
||||||
|
UNIDENTIFIED_SENDER = 6; // content => ((version byte | UnidentifiedSenderMessage) OR (version byte | Multi-Recipient Sealed Sender Format))
|
||||||
|
|
||||||
|
reserved 7;
|
||||||
|
reserved "SENDERKEY_MESSAGE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A plaintext message is used solely to convey encryption error receipts
|
||||||
|
* and never contains encrypted message content. Encryption error receipts
|
||||||
|
* must be delivered in plaintext because, encryption/decryption of a prior
|
||||||
|
* message failed and there is no reason to believe that
|
||||||
|
* encryption/decryption of subsequent messages with the same key material
|
||||||
|
* would succeed.
|
||||||
|
*
|
||||||
|
* Critically, plaintext messages never have "real" message content
|
||||||
|
* generated by users. Plaintext messages include sender information.
|
||||||
|
*/
|
||||||
|
PLAINTEXT_CONTENT = 8; // content => (marker byte | Content)
|
||||||
|
|
||||||
|
// next: 9
|
||||||
}
|
}
|
||||||
|
|
||||||
optional Type type = 1;
|
optional Type type = 1;
|
||||||
reserved 2; // formerly optional string sourceE164 = 2;
|
reserved 2; // formerly optional string sourceE164 = 2;
|
||||||
optional string sourceServiceId = 11;
|
optional string sourceServiceId = 11;
|
||||||
optional uint32 sourceDevice = 7;
|
optional uint32 sourceDeviceId = 7;
|
||||||
optional string destinationServiceId = 13;
|
optional string destinationServiceId = 13;
|
||||||
reserved 3; // formerly optional string relay = 3;
|
reserved 3; // formerly optional string relay = 3;
|
||||||
optional uint64 timestamp = 5;
|
optional uint64 clientTimestamp = 5;
|
||||||
reserved 6; // formerly optional bytes legacyMessage = 6; // Contains an encrypted DataMessage; this field could have been set historically for type 1 or 3 messages; no longer in use
|
reserved 6; // formerly optional bytes legacyMessage = 6; // Contains an encrypted DataMessage; this field could have been set historically for type 1 or 3 messages; no longer in use
|
||||||
optional bytes content = 8; // Contains an encrypted Content
|
optional bytes content = 8; // Contains an encrypted Content
|
||||||
optional string serverGuid = 9;
|
optional string serverGuid = 9;
|
||||||
|
@ -39,7 +96,11 @@ message Envelope {
|
||||||
optional bool story = 16; // indicates that the content is a story.
|
optional bool story = 16; // indicates that the content is a story.
|
||||||
optional bytes report_spam_token = 17; // token sent when reporting spam
|
optional bytes report_spam_token = 17; // token sent when reporting spam
|
||||||
reserved 18; // internal server use
|
reserved 18; // internal server use
|
||||||
// next: 19
|
optional bytes sourceServiceIdBinary = 19; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||||
|
optional bytes destinationServiceIdBinary = 20; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||||
|
optional bytes serverGuidBinary = 21; // 16-byte UUID
|
||||||
|
optional bytes updatedPniBinary = 22; // 16-byte UUID
|
||||||
|
// next: 22
|
||||||
}
|
}
|
||||||
|
|
||||||
message Content {
|
message Content {
|
||||||
|
@ -191,6 +252,7 @@ message DataMessage {
|
||||||
repeated QuotedAttachment attachments = 4;
|
repeated QuotedAttachment attachments = 4;
|
||||||
repeated BodyRange bodyRanges = 6;
|
repeated BodyRange bodyRanges = 6;
|
||||||
optional Type type = 7;
|
optional Type type = 7;
|
||||||
|
optional bytes authorAciBinary = 8; // 16-byte UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
message Contact {
|
message Contact {
|
||||||
|
@ -275,6 +337,7 @@ message DataMessage {
|
||||||
reserved /* targetAuthorE164 */ 3;
|
reserved /* targetAuthorE164 */ 3;
|
||||||
optional string targetAuthorAci = 4;
|
optional string targetAuthorAci = 4;
|
||||||
optional uint64 targetSentTimestamp = 5;
|
optional uint64 targetSentTimestamp = 5;
|
||||||
|
optional bytes targetAuthorAciBinary = 6; // 16-byte UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
message Delete {
|
message Delete {
|
||||||
|
@ -288,6 +351,7 @@ message DataMessage {
|
||||||
message StoryContext {
|
message StoryContext {
|
||||||
optional string authorAci = 1;
|
optional string authorAci = 1;
|
||||||
optional uint64 sentTimestamp = 2;
|
optional uint64 sentTimestamp = 2;
|
||||||
|
optional bytes authorAciBinary = 3; // 16-byte UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ProtocolVersion {
|
enum ProtocolVersion {
|
||||||
|
@ -419,6 +483,7 @@ message Verified {
|
||||||
optional bytes identityKey = 2;
|
optional bytes identityKey = 2;
|
||||||
optional State state = 3;
|
optional State state = 3;
|
||||||
optional bytes nullMessage = 4;
|
optional bytes nullMessage = 4;
|
||||||
|
optional bytes destinationAciBinary = 6; // 16-byte UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
message SyncMessage {
|
message SyncMessage {
|
||||||
|
@ -429,6 +494,7 @@ message SyncMessage {
|
||||||
optional bool unidentified = 2;
|
optional bool unidentified = 2;
|
||||||
reserved /*destinationPni */ 4;
|
reserved /*destinationPni */ 4;
|
||||||
optional bytes destinationPniIdentityKey = 5; // Only set for PNI destinations
|
optional bytes destinationPniIdentityKey = 5; // Only set for PNI destinations
|
||||||
|
optional bytes destinationServiceIdBinary = 6; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||||
}
|
}
|
||||||
|
|
||||||
message StoryMessageRecipient {
|
message StoryMessageRecipient {
|
||||||
|
@ -436,6 +502,7 @@ message SyncMessage {
|
||||||
repeated string distributionListIds = 2;
|
repeated string distributionListIds = 2;
|
||||||
optional bool isAllowedToReply = 3;
|
optional bool isAllowedToReply = 3;
|
||||||
reserved /*destinationPni */ 4;
|
reserved /*destinationPni */ 4;
|
||||||
|
optional bytes destinationServiceIdBinary = 5; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||||
}
|
}
|
||||||
|
|
||||||
optional string destinationE164 = 1;
|
optional string destinationE164 = 1;
|
||||||
|
@ -449,7 +516,8 @@ message SyncMessage {
|
||||||
repeated StoryMessageRecipient storyMessageRecipients = 9;
|
repeated StoryMessageRecipient storyMessageRecipients = 9;
|
||||||
optional EditMessage editMessage = 10;
|
optional EditMessage editMessage = 10;
|
||||||
reserved /*destinationPni */ 11;
|
reserved /*destinationPni */ 11;
|
||||||
// Next ID: 12
|
optional bytes destinationServiceIdBinary = 12; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||||
|
// Next ID: 13
|
||||||
}
|
}
|
||||||
|
|
||||||
message Contacts {
|
message Contacts {
|
||||||
|
@ -461,6 +529,7 @@ message SyncMessage {
|
||||||
repeated string numbers = 1;
|
repeated string numbers = 1;
|
||||||
repeated string acis = 3;
|
repeated string acis = 3;
|
||||||
repeated bytes groupIds = 2;
|
repeated bytes groupIds = 2;
|
||||||
|
repeated bytes acisBinary = 4; // 16-byte UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
message Request {
|
message Request {
|
||||||
|
@ -481,12 +550,14 @@ message SyncMessage {
|
||||||
reserved /*senderE164*/ 1;
|
reserved /*senderE164*/ 1;
|
||||||
optional string senderAci = 3;
|
optional string senderAci = 3;
|
||||||
optional uint64 timestamp = 2;
|
optional uint64 timestamp = 2;
|
||||||
|
optional bytes senderAciBinary = 4; // 16-byte UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
message Viewed {
|
message Viewed {
|
||||||
reserved /*senderE164*/ 1;
|
reserved /*senderE164*/ 1;
|
||||||
optional string senderAci = 3;
|
optional string senderAci = 3;
|
||||||
optional uint64 timestamp = 2;
|
optional uint64 timestamp = 2;
|
||||||
|
optional bytes senderAciBinary = 4; // 16-byte UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
message Configuration {
|
message Configuration {
|
||||||
|
@ -513,6 +584,7 @@ message SyncMessage {
|
||||||
reserved /*senderE164*/ 1;
|
reserved /*senderE164*/ 1;
|
||||||
optional string senderAci = 3;
|
optional string senderAci = 3;
|
||||||
optional uint64 timestamp = 2;
|
optional uint64 timestamp = 2;
|
||||||
|
optional bytes senderAciBinary = 4; // 16-byte UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
message FetchLatest {
|
message FetchLatest {
|
||||||
|
@ -553,6 +625,25 @@ message SyncMessage {
|
||||||
optional string threadAci = 2;
|
optional string threadAci = 2;
|
||||||
optional bytes groupId = 3;
|
optional bytes groupId = 3;
|
||||||
optional Type type = 4;
|
optional Type type = 4;
|
||||||
|
optional bytes threadAciBinary = 5; // 16-byte UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
message OutgoingPayment {
|
||||||
|
message MobileCoin {
|
||||||
|
optional bytes recipientAddress = 1;
|
||||||
|
optional uint64 amountPicoMob = 2;
|
||||||
|
optional uint64 feePicoMob = 3;
|
||||||
|
optional bytes receipt = 4;
|
||||||
|
optional uint64 ledgerBlockTimestamp = 5;
|
||||||
|
optional uint64 ledgerBlockIndex = 6;
|
||||||
|
repeated bytes spentKeyImages = 7;
|
||||||
|
repeated bytes outputPublicKeys = 8;
|
||||||
|
}
|
||||||
|
optional string recipientServiceId = 1;
|
||||||
|
optional string note = 2;
|
||||||
|
oneof attachment_identifier {
|
||||||
|
MobileCoin mobileCoin = 3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message PniChangeNumber {
|
message PniChangeNumber {
|
||||||
|
@ -719,6 +810,7 @@ message SyncMessage {
|
||||||
optional FetchLatest fetchLatest = 12;
|
optional FetchLatest fetchLatest = 12;
|
||||||
optional Keys keys = 13;
|
optional Keys keys = 13;
|
||||||
optional MessageRequestResponse messageRequestResponse = 14;
|
optional MessageRequestResponse messageRequestResponse = 14;
|
||||||
|
optional OutgoingPayment outgoingPayment = 15;
|
||||||
repeated Viewed viewed = 16;
|
repeated Viewed viewed = 16;
|
||||||
reserved /*pniIdentity*/ 17;
|
reserved /*pniIdentity*/ 17;
|
||||||
optional PniChangeNumber pniChangeNumber = 18;
|
optional PniChangeNumber pniChangeNumber = 18;
|
||||||
|
@ -732,11 +824,10 @@ message SyncMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
message AttachmentPointer {
|
message AttachmentPointer {
|
||||||
// Our parser does not handle reserved in enums: DESKTOP-1569
|
|
||||||
enum Flags {
|
enum Flags {
|
||||||
VOICE_MESSAGE = 1;
|
VOICE_MESSAGE = 1;
|
||||||
BORDERLESS = 2;
|
BORDERLESS = 2;
|
||||||
// reserved 4;
|
reserved 4;
|
||||||
GIF = 8;
|
GIF = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -781,6 +872,7 @@ message ContactDetails {
|
||||||
|
|
||||||
optional string number = 1;
|
optional string number = 1;
|
||||||
optional string aci = 9;
|
optional string aci = 9;
|
||||||
|
optional bytes aciBinary = 13; // 16-byte UUID
|
||||||
optional string name = 2;
|
optional string name = 2;
|
||||||
optional Avatar avatar = 3;
|
optional Avatar avatar = 3;
|
||||||
reserved /* color */ 4;
|
reserved /* color */ 4;
|
||||||
|
@ -791,7 +883,18 @@ message ContactDetails {
|
||||||
optional uint32 expireTimerVersion = 12;
|
optional uint32 expireTimerVersion = 12;
|
||||||
optional uint32 inboxPosition = 10;
|
optional uint32 inboxPosition = 10;
|
||||||
reserved /* archived */ 11;
|
reserved /* archived */ 11;
|
||||||
// NEXT ID: 13
|
// NEXT ID: 14
|
||||||
|
}
|
||||||
|
|
||||||
|
message PaymentAddress {
|
||||||
|
message MobileCoin {
|
||||||
|
optional bytes publicAddress = 1;
|
||||||
|
optional bytes signature = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
oneof Address {
|
||||||
|
MobileCoin mobileCoin = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message DecryptionErrorMessage {
|
message DecryptionErrorMessage {
|
||||||
|
@ -827,6 +930,7 @@ message BodyRange {
|
||||||
oneof associatedValue {
|
oneof associatedValue {
|
||||||
string mentionAci = 3;
|
string mentionAci = 3;
|
||||||
Style style = 4;
|
Style style = 4;
|
||||||
|
bytes mentionAciBinary = 5; // 16-byte UUID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -834,6 +938,7 @@ message AddressableMessage {
|
||||||
oneof author {
|
oneof author {
|
||||||
string authorServiceId = 1;
|
string authorServiceId = 1;
|
||||||
string authorE164 = 2;
|
string authorE164 = 2;
|
||||||
|
bytes authorServiceIdBinary = 4; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||||
}
|
}
|
||||||
optional uint64 sentTimestamp = 3;
|
optional uint64 sentTimestamp = 3;
|
||||||
}
|
}
|
||||||
|
@ -843,5 +948,6 @@ message ConversationIdentifier {
|
||||||
string threadServiceId = 1;
|
string threadServiceId = 1;
|
||||||
bytes threadGroupId = 2;
|
bytes threadGroupId = 2;
|
||||||
string threadE164 = 3;
|
string threadE164 = 3;
|
||||||
|
bytes threadServiceIdBinary = 4; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,7 +142,9 @@ message ContactRecord {
|
||||||
Name nickname = 22;
|
Name nickname = 22;
|
||||||
string note = 23;
|
string note = 23;
|
||||||
optional AvatarColor avatarColor = 24;
|
optional AvatarColor avatarColor = 24;
|
||||||
// Next ID: 25
|
bytes aciBinary = 25; // 16-byte UUID
|
||||||
|
bytes pniBinary = 26; // 16-byte UUID
|
||||||
|
// Next ID: 27
|
||||||
}
|
}
|
||||||
|
|
||||||
message GroupV1Record {
|
message GroupV1Record {
|
||||||
|
@ -174,6 +176,11 @@ message GroupV2Record {
|
||||||
optional AvatarColor avatarColor = 11;
|
optional AvatarColor avatarColor = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message Payments {
|
||||||
|
bool enabled = 1;
|
||||||
|
bytes entropy = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message AccountRecord {
|
message AccountRecord {
|
||||||
|
|
||||||
enum PhoneNumberSharingMode {
|
enum PhoneNumberSharingMode {
|
||||||
|
@ -186,6 +193,7 @@ message AccountRecord {
|
||||||
message Contact {
|
message Contact {
|
||||||
string serviceId = 1;
|
string serviceId = 1;
|
||||||
string e164 = 2;
|
string e164 = 2;
|
||||||
|
bytes serviceIdBinary = 3; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||||
}
|
}
|
||||||
|
|
||||||
oneof identifier {
|
oneof identifier {
|
||||||
|
@ -301,6 +309,7 @@ message StoryDistributionListRecord {
|
||||||
uint64 deletedAtTimestamp = 4;
|
uint64 deletedAtTimestamp = 4;
|
||||||
bool allowsReplies = 5;
|
bool allowsReplies = 5;
|
||||||
bool isBlockList = 6;
|
bool isBlockList = 6;
|
||||||
|
repeated bytes recipientServiceIdsBinary = 7; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||||
}
|
}
|
||||||
|
|
||||||
message StickerPackRecord {
|
message StickerPackRecord {
|
||||||
|
@ -335,6 +344,7 @@ message Recipient {
|
||||||
message Contact {
|
message Contact {
|
||||||
string serviceId = 1;
|
string serviceId = 1;
|
||||||
string e164 = 2;
|
string e164 = 2;
|
||||||
|
bytes serviceIdBinary = 3; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
|
||||||
}
|
}
|
||||||
|
|
||||||
oneof identifier {
|
oneof identifier {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
signal as Signal,
|
signal as Signal,
|
||||||
signalbackups as Backups,
|
signalbackups as Backups,
|
||||||
signalservice as SignalService,
|
signalservice as SignalService,
|
||||||
|
migrations as Migrations,
|
||||||
} from './compiled';
|
} from './compiled';
|
||||||
|
|
||||||
export { Backups, SignalService, Signal };
|
export { Backups, SignalService, Signal, Migrations };
|
||||||
|
|
|
@ -10,7 +10,6 @@ import {
|
||||||
parseContactsV2,
|
parseContactsV2,
|
||||||
type ContactDetailsWithAvatar,
|
type ContactDetailsWithAvatar,
|
||||||
} from '../textsecure/ContactsParser';
|
} from '../textsecure/ContactsParser';
|
||||||
import { normalizeAci } from '../util/normalizeAci';
|
|
||||||
import * as Conversation from '../types/Conversation';
|
import * as Conversation from '../types/Conversation';
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
import type { ValidateConversationType } from '../model-types.d';
|
import type { ValidateConversationType } from '../model-types.d';
|
||||||
|
@ -152,7 +151,7 @@ async function doContactSync({
|
||||||
for (const details of contacts) {
|
for (const details of contacts) {
|
||||||
const partialConversation: ValidateConversationType = {
|
const partialConversation: ValidateConversationType = {
|
||||||
e164: details.number,
|
e164: details.number,
|
||||||
serviceId: normalizeAci(details.aci, 'doContactSync'),
|
serviceId: details.aci,
|
||||||
type: 'private',
|
type: 'private',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -167,7 +166,7 @@ async function doContactSync({
|
||||||
|
|
||||||
const { conversation } = window.ConversationController.maybeMergeContacts({
|
const { conversation } = window.ConversationController.maybeMergeContacts({
|
||||||
e164: details.number,
|
e164: details.number,
|
||||||
aci: normalizeAci(details.aci, 'contactSync.aci'),
|
aci: details.aci,
|
||||||
reason: logId,
|
reason: logId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,7 @@ import {
|
||||||
callLinkFromRecord,
|
callLinkFromRecord,
|
||||||
getRoomIdFromRootKeyString,
|
getRoomIdFromRootKeyString,
|
||||||
} from '../util/callLinksRingrtc';
|
} from '../util/callLinksRingrtc';
|
||||||
|
import { fromPniUuidBytesOrUntaggedString } from '../util/ServiceId';
|
||||||
import { callLinkRefreshJobQueue } from '../jobs/callLinkRefreshJobQueue';
|
import { callLinkRefreshJobQueue } from '../jobs/callLinkRefreshJobQueue';
|
||||||
|
|
||||||
const log = createLogger('storage');
|
const log = createLogger('storage');
|
||||||
|
@ -1800,11 +1801,16 @@ async function processRemoteRecords(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!contact.e164 || !contact.pni) {
|
const pni = fromPniUuidBytesOrUntaggedString(
|
||||||
|
contact.pniBinary,
|
||||||
|
contact.pni,
|
||||||
|
'splitPNIContacts'
|
||||||
|
);
|
||||||
|
if (!contact.e164 || !pni) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const localAci = window.ConversationController.get(contact.pni)?.getAci();
|
const localAci = window.ConversationController.get(pni)?.getAci();
|
||||||
if (!localAci) {
|
if (!localAci) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,14 +45,10 @@ import { normalizeStoryDistributionId } from '../types/StoryDistributionId';
|
||||||
import type { StoryDistributionIdString } from '../types/StoryDistributionId';
|
import type { StoryDistributionIdString } from '../types/StoryDistributionId';
|
||||||
import type { ServiceIdString } from '../types/ServiceId';
|
import type { ServiceIdString } from '../types/ServiceId';
|
||||||
import {
|
import {
|
||||||
normalizeServiceId,
|
|
||||||
normalizePni,
|
|
||||||
ServiceIdKind,
|
ServiceIdKind,
|
||||||
isUntaggedPniString,
|
normalizeServiceId,
|
||||||
toUntaggedPni,
|
toUntaggedPni,
|
||||||
toTaggedPni,
|
|
||||||
} from '../types/ServiceId';
|
} from '../types/ServiceId';
|
||||||
import { normalizeAci } from '../util/normalizeAci';
|
|
||||||
import { isAciString } from '../util/isAciString';
|
import { isAciString } from '../util/isAciString';
|
||||||
import * as Stickers from '../types/Stickers';
|
import * as Stickers from '../types/Stickers';
|
||||||
import type {
|
import type {
|
||||||
|
@ -84,6 +80,15 @@ import {
|
||||||
generateBackupsSubscriberData,
|
generateBackupsSubscriberData,
|
||||||
saveBackupsSubscriberData,
|
saveBackupsSubscriberData,
|
||||||
} from '../util/backupSubscriptionData';
|
} from '../util/backupSubscriptionData';
|
||||||
|
import {
|
||||||
|
toAciObject,
|
||||||
|
toPniObject,
|
||||||
|
toServiceIdObject,
|
||||||
|
fromServiceIdBinaryOrString,
|
||||||
|
fromAciUuidBytesOrString,
|
||||||
|
fromPniUuidBytesOrUntaggedString,
|
||||||
|
} from '../util/ServiceId';
|
||||||
|
import { isProtoBinaryEncodingEnabled } from '../util/isProtoBinaryEncodingEnabled';
|
||||||
import { getLinkPreviewSetting } from '../types/LinkPreview';
|
import { getLinkPreviewSetting } from '../types/LinkPreview';
|
||||||
import {
|
import {
|
||||||
getReadReceiptSetting,
|
getReadReceiptSetting,
|
||||||
|
@ -228,7 +233,11 @@ export async function toContactRecord(
|
||||||
const contactRecord = new Proto.ContactRecord();
|
const contactRecord = new Proto.ContactRecord();
|
||||||
const aci = conversation.getAci();
|
const aci = conversation.getAci();
|
||||||
if (aci) {
|
if (aci) {
|
||||||
contactRecord.aci = aci;
|
if (isProtoBinaryEncodingEnabled()) {
|
||||||
|
contactRecord.aciBinary = toAciObject(aci).getRawUuidBytes();
|
||||||
|
} else {
|
||||||
|
contactRecord.aci = aci;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const e164 = conversation.get('e164');
|
const e164 = conversation.get('e164');
|
||||||
if (e164) {
|
if (e164) {
|
||||||
|
@ -241,7 +250,11 @@ export async function toContactRecord(
|
||||||
}
|
}
|
||||||
const pni = conversation.getPni();
|
const pni = conversation.getPni();
|
||||||
if (pni) {
|
if (pni) {
|
||||||
contactRecord.pni = toUntaggedPni(pni);
|
if (isProtoBinaryEncodingEnabled()) {
|
||||||
|
contactRecord.pniBinary = toPniObject(pni).getRawUuidBytes();
|
||||||
|
} else {
|
||||||
|
contactRecord.pni = toUntaggedPni(pni);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
contactRecord.pniSignatureVerified =
|
contactRecord.pniSignatureVerified =
|
||||||
conversation.get('pniSignatureVerified') ?? false;
|
conversation.get('pniSignatureVerified') ?? false;
|
||||||
|
@ -411,9 +424,19 @@ export function toAccountRecord(
|
||||||
new Proto.AccountRecord.PinnedConversation();
|
new Proto.AccountRecord.PinnedConversation();
|
||||||
|
|
||||||
if (pinnedConversation.get('type') === 'private') {
|
if (pinnedConversation.get('type') === 'private') {
|
||||||
|
const serviceId = pinnedConversation.getServiceId();
|
||||||
pinnedConversationRecord.identifier = 'contact';
|
pinnedConversationRecord.identifier = 'contact';
|
||||||
pinnedConversationRecord.contact = {
|
pinnedConversationRecord.contact = {
|
||||||
serviceId: pinnedConversation.getServiceId(),
|
...(isProtoBinaryEncodingEnabled()
|
||||||
|
? {
|
||||||
|
serviceIdBinary:
|
||||||
|
serviceId == null
|
||||||
|
? null
|
||||||
|
: toServiceIdObject(serviceId).getServiceIdBinary(),
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
serviceId,
|
||||||
|
}),
|
||||||
e164: pinnedConversation.get('e164'),
|
e164: pinnedConversation.get('e164'),
|
||||||
};
|
};
|
||||||
} else if (isGroupV1(pinnedConversation.attributes)) {
|
} else if (isGroupV1(pinnedConversation.attributes)) {
|
||||||
|
@ -621,8 +644,16 @@ export function toStoryDistributionListRecord(
|
||||||
storyDistributionListRecord.isBlockList = Boolean(
|
storyDistributionListRecord.isBlockList = Boolean(
|
||||||
storyDistributionList.isBlockList
|
storyDistributionList.isBlockList
|
||||||
);
|
);
|
||||||
storyDistributionListRecord.recipientServiceIds =
|
|
||||||
storyDistributionList.members;
|
if (isProtoBinaryEncodingEnabled()) {
|
||||||
|
storyDistributionListRecord.recipientServiceIdsBinary =
|
||||||
|
storyDistributionList.members.map(serviceId => {
|
||||||
|
return toServiceIdObject(serviceId).getServiceIdBinary();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
storyDistributionListRecord.recipientServiceIds =
|
||||||
|
storyDistributionList.members;
|
||||||
|
}
|
||||||
|
|
||||||
if (storyDistributionList.storageUnknownFields) {
|
if (storyDistributionList.storageUnknownFields) {
|
||||||
storyDistributionListRecord.$unknownFields = [
|
storyDistributionListRecord.$unknownFields = [
|
||||||
|
@ -1108,17 +1139,16 @@ export async function mergeContactRecord(
|
||||||
const contactRecord = {
|
const contactRecord = {
|
||||||
...originalContactRecord,
|
...originalContactRecord,
|
||||||
|
|
||||||
aci: originalContactRecord.aci
|
aci: fromAciUuidBytesOrString(
|
||||||
? normalizeAci(originalContactRecord.aci, 'ContactRecord.aci')
|
originalContactRecord.aciBinary,
|
||||||
: undefined,
|
originalContactRecord.aci,
|
||||||
pni:
|
'ContactRecord.aci'
|
||||||
originalContactRecord.pni &&
|
),
|
||||||
isUntaggedPniString(originalContactRecord.pni)
|
pni: fromPniUuidBytesOrUntaggedString(
|
||||||
? normalizePni(
|
originalContactRecord.pniBinary,
|
||||||
toTaggedPni(originalContactRecord.pni),
|
originalContactRecord.pni,
|
||||||
'ContactRecord.pni'
|
'ContactRecord.pni'
|
||||||
)
|
),
|
||||||
: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const e164 = dropNull(contactRecord.e164);
|
const e164 = dropNull(contactRecord.e164);
|
||||||
|
@ -1481,19 +1511,22 @@ export async function mergeAccountRecord(
|
||||||
let convo: ConversationModel | undefined;
|
let convo: ConversationModel | undefined;
|
||||||
|
|
||||||
if (contact) {
|
if (contact) {
|
||||||
if (!contact.serviceId && !contact.e164) {
|
if (
|
||||||
|
!contact.serviceId &&
|
||||||
|
!Bytes.isNotEmpty(contact.serviceIdBinary) &&
|
||||||
|
!contact.e164
|
||||||
|
) {
|
||||||
log.error(
|
log.error(
|
||||||
'storageService.mergeAccountRecord: No serviceId or e164 on contact'
|
'storageService.mergeAccountRecord: No serviceId or e164 on contact'
|
||||||
);
|
);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
convo = window.ConversationController.lookupOrCreate({
|
convo = window.ConversationController.lookupOrCreate({
|
||||||
serviceId: contact.serviceId
|
serviceId: fromServiceIdBinaryOrString(
|
||||||
? normalizeServiceId(
|
contact.serviceIdBinary,
|
||||||
contact.serviceId,
|
contact.serviceId,
|
||||||
'AccountRecord.pin.serviceId'
|
'AccountRecord.pin.serviceId'
|
||||||
)
|
),
|
||||||
: undefined,
|
|
||||||
e164: contact.e164,
|
e164: contact.e164,
|
||||||
reason: 'storageService.mergeAccountRecord',
|
reason: 'storageService.mergeAccountRecord',
|
||||||
});
|
});
|
||||||
|
@ -1751,9 +1784,20 @@ export async function mergeStoryDistributionListRecord(
|
||||||
storyDistributionListRecord
|
storyDistributionListRecord
|
||||||
);
|
);
|
||||||
|
|
||||||
const remoteListMembers: Array<ServiceIdString> = (
|
let remoteListMembers: Array<ServiceIdString>;
|
||||||
storyDistributionListRecord.recipientServiceIds || []
|
|
||||||
).map(id => normalizeServiceId(id, 'mergeStoryDistributionListRecord'));
|
if (storyDistributionListRecord.recipientServiceIdsBinary?.length) {
|
||||||
|
remoteListMembers =
|
||||||
|
storyDistributionListRecord.recipientServiceIdsBinary.map(id =>
|
||||||
|
fromServiceIdBinaryOrString(id, undefined, 'unused')
|
||||||
|
);
|
||||||
|
} else if (storyDistributionListRecord.recipientServiceIds?.length) {
|
||||||
|
remoteListMembers = storyDistributionListRecord.recipientServiceIds.map(
|
||||||
|
id => normalizeServiceId(id, 'mergeStoryDistributionListRecord')
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
remoteListMembers = [];
|
||||||
|
}
|
||||||
|
|
||||||
if (storyDistributionListRecord.$unknownFields) {
|
if (storyDistributionListRecord.$unknownFields) {
|
||||||
details.push('adding unknown fields');
|
details.push('adding unknown fields');
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
toTaggedPni,
|
toTaggedPni,
|
||||||
isUntaggedPniString,
|
isUntaggedPniString,
|
||||||
} from '../../types/ServiceId';
|
} from '../../types/ServiceId';
|
||||||
import { SignalService as Proto } from '../../protobuf';
|
import { Migrations as Proto } from '../../protobuf';
|
||||||
import { sql } from '../util';
|
import { sql } from '../util';
|
||||||
import type { WritableDB } from '../Interface';
|
import type { WritableDB } from '../Interface';
|
||||||
import { getOurUuid } from './41-uuid-keys';
|
import { getOurUuid } from './41-uuid-keys';
|
||||||
|
|
|
@ -13,9 +13,11 @@ import type { ProcessedAttachment } from '../textsecure/Types.d';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
import { IMAGE_GIF, IMAGE_JPEG, LONG_MESSAGE } from '../types/MIME';
|
import { IMAGE_GIF, IMAGE_JPEG, LONG_MESSAGE } from '../types/MIME';
|
||||||
import { generateAci } from '../types/ServiceId';
|
import { generateAci } from '../types/ServiceId';
|
||||||
|
import { toAciObject } from '../util/ServiceId';
|
||||||
import { uuidToBytes } from '../util/uuidToBytes';
|
import { uuidToBytes } from '../util/uuidToBytes';
|
||||||
|
|
||||||
const ACI_1 = generateAci();
|
const ACI_1 = generateAci();
|
||||||
|
const ACI_BINARY_1 = toAciObject(ACI_1).getRawUuidBytes();
|
||||||
const FLAGS = Proto.DataMessage.Flags;
|
const FLAGS = Proto.DataMessage.Flags;
|
||||||
|
|
||||||
const TIMESTAMP = Date.now();
|
const TIMESTAMP = Date.now();
|
||||||
|
@ -205,7 +207,7 @@ describe('processDataMessage', () => {
|
||||||
const out = check({
|
const out = check({
|
||||||
quote: {
|
quote: {
|
||||||
id: Long.fromNumber(1),
|
id: Long.fromNumber(1),
|
||||||
authorAci: ACI_1,
|
authorAciBinary: ACI_BINARY_1,
|
||||||
text: 'text',
|
text: 'text',
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
|
@ -298,7 +300,7 @@ describe('processDataMessage', () => {
|
||||||
check({
|
check({
|
||||||
reaction: {
|
reaction: {
|
||||||
emoji: '😎',
|
emoji: '😎',
|
||||||
targetAuthorAci: ACI_1,
|
targetAuthorAciBinary: ACI_BINARY_1,
|
||||||
targetSentTimestamp: Long.fromNumber(TIMESTAMP),
|
targetSentTimestamp: Long.fromNumber(TIMESTAMP),
|
||||||
},
|
},
|
||||||
}).reaction,
|
}).reaction,
|
||||||
|
@ -315,7 +317,7 @@ describe('processDataMessage', () => {
|
||||||
reaction: {
|
reaction: {
|
||||||
emoji: '😎',
|
emoji: '😎',
|
||||||
remove: true,
|
remove: true,
|
||||||
targetAuthorAci: ACI_1,
|
targetAuthorAciBinary: ACI_BINARY_1,
|
||||||
targetSentTimestamp: Long.fromNumber(TIMESTAMP),
|
targetSentTimestamp: Long.fromNumber(TIMESTAMP),
|
||||||
},
|
},
|
||||||
}).reaction,
|
}).reaction,
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { createLogger } from '../logging/log';
|
||||||
import * as Bytes from '../Bytes';
|
import * as Bytes from '../Bytes';
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
import { APPLICATION_OCTET_STREAM } from '../types/MIME';
|
import { APPLICATION_OCTET_STREAM } from '../types/MIME';
|
||||||
|
import { type AciString, generateAci } from '../types/ServiceId';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
import {
|
import {
|
||||||
ParseContactsTransform,
|
ParseContactsTransform,
|
||||||
|
@ -21,12 +22,15 @@ import {
|
||||||
import type { ContactDetailsWithAvatar } from '../textsecure/ContactsParser';
|
import type { ContactDetailsWithAvatar } from '../textsecure/ContactsParser';
|
||||||
import { createTempDir, deleteTempDir } from '../updater/common';
|
import { createTempDir, deleteTempDir } from '../updater/common';
|
||||||
import { strictAssert } from '../util/assert';
|
import { strictAssert } from '../util/assert';
|
||||||
|
import { toAciObject } from '../util/ServiceId';
|
||||||
import { generateKeys, encryptAttachmentV2ToDisk } from '../AttachmentCrypto';
|
import { generateKeys, encryptAttachmentV2ToDisk } from '../AttachmentCrypto';
|
||||||
|
|
||||||
const log = createLogger('ContactsParser_test');
|
const log = createLogger('ContactsParser_test');
|
||||||
|
|
||||||
const { Writer } = protobuf;
|
const { Writer } = protobuf;
|
||||||
|
|
||||||
|
const DEFAULT_ACI = generateAci();
|
||||||
|
|
||||||
describe('ContactsParser', () => {
|
describe('ContactsParser', () => {
|
||||||
let tempDir: string;
|
let tempDir: string;
|
||||||
|
|
||||||
|
@ -130,9 +134,9 @@ describe('ContactsParser', () => {
|
||||||
try {
|
try {
|
||||||
const avatarBuffer = generateAvatar();
|
const avatarBuffer = generateAvatar();
|
||||||
const bytes = Bytes.concatenate([
|
const bytes = Bytes.concatenate([
|
||||||
generatePrefixedContact(avatarBuffer, 'invalid'),
|
generatePrefixedContact(avatarBuffer, null),
|
||||||
avatarBuffer,
|
avatarBuffer,
|
||||||
generatePrefixedContact(undefined, 'invalid'),
|
generatePrefixedContact(undefined, null),
|
||||||
getTestBuffer(),
|
getTestBuffer(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -216,12 +220,12 @@ function getTestBuffer(): Uint8Array {
|
||||||
|
|
||||||
function generatePrefixedContact(
|
function generatePrefixedContact(
|
||||||
avatarBuffer: Uint8Array | undefined,
|
avatarBuffer: Uint8Array | undefined,
|
||||||
aci = '7198E1BD-1293-452A-A098-F982FF201902'
|
aci: AciString | null = DEFAULT_ACI
|
||||||
) {
|
) {
|
||||||
const contactInfoBuffer = Proto.ContactDetails.encode({
|
const contactInfoBuffer = Proto.ContactDetails.encode({
|
||||||
name: 'Zero Cool',
|
name: 'Zero Cool',
|
||||||
number: '+10000000000',
|
number: '+10000000000',
|
||||||
aci,
|
aciBinary: aci == null ? null : toAciObject(aci).getRawUuidBytes(),
|
||||||
avatar: avatarBuffer
|
avatar: avatarBuffer
|
||||||
? { contentType: 'image/jpeg', length: avatarBuffer.length }
|
? { contentType: 'image/jpeg', length: avatarBuffer.length }
|
||||||
: undefined,
|
: undefined,
|
||||||
|
@ -239,7 +243,7 @@ async function verifyContact(
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
assert.strictEqual(contact.name, 'Zero Cool');
|
assert.strictEqual(contact.name, 'Zero Cool');
|
||||||
assert.strictEqual(contact.number, '+10000000000');
|
assert.strictEqual(contact.number, '+10000000000');
|
||||||
assert.strictEqual(contact.aci, '7198e1bd-1293-452a-a098-f982ff201902');
|
assert.strictEqual(contact.aci, DEFAULT_ACI);
|
||||||
|
|
||||||
if (avatarIsMissing) {
|
if (avatarIsMissing) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { IncomingWebSocketRequestLegacy } from '../textsecure/WebsocketResources
|
||||||
import type { DecryptionErrorEvent } from '../textsecure/messageReceiverEvents';
|
import type { DecryptionErrorEvent } from '../textsecure/messageReceiverEvents';
|
||||||
import { generateAci } from '../types/ServiceId';
|
import { generateAci } from '../types/ServiceId';
|
||||||
import type { AciString } from '../types/ServiceId';
|
import type { AciString } from '../types/ServiceId';
|
||||||
|
import { toAciObject } from '../util/ServiceId';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
import * as Crypto from '../Crypto';
|
import * as Crypto from '../Crypto';
|
||||||
import { toBase64 } from '../Bytes';
|
import { toBase64 } from '../Bytes';
|
||||||
|
@ -47,10 +48,10 @@ describe('MessageReceiver', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const body = Proto.Envelope.encode({
|
const body = Proto.Envelope.encode({
|
||||||
type: Proto.Envelope.Type.CIPHERTEXT,
|
type: Proto.Envelope.Type.DOUBLE_RATCHET,
|
||||||
sourceServiceId: someAci,
|
sourceServiceIdBinary: toAciObject(someAci).getRawUuidBytes(),
|
||||||
sourceDevice: deviceId,
|
sourceDeviceId: deviceId,
|
||||||
timestamp: Long.fromNumber(Date.now()),
|
clientTimestamp: Long.fromNumber(Date.now()),
|
||||||
content: Crypto.getRandomBytes(200),
|
content: Crypto.getRandomBytes(200),
|
||||||
}).finish();
|
}).finish();
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ describe('backups', function (this: Mocha.Suite) {
|
||||||
identifier: uuidToBytes(MY_STORY_ID),
|
identifier: uuidToBytes(MY_STORY_ID),
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
name: MY_STORY_ID,
|
name: MY_STORY_ID,
|
||||||
recipientServiceIds: [pinned.device.aci],
|
recipientServiceIdsBinary: [pinned.device.aciBinary],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -119,7 +119,7 @@ describe('backups', function (this: Mocha.Suite) {
|
||||||
identifier: uuidToBytes(DISTRIBUTION1),
|
identifier: uuidToBytes(DISTRIBUTION1),
|
||||||
isBlockList: false,
|
isBlockList: false,
|
||||||
name: 'friend',
|
name: 'friend',
|
||||||
recipientServiceIds: [friend.device.aci],
|
recipientServiceIdsBinary: [friend.device.aciBinary],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -262,14 +262,14 @@ describe('backups', function (this: Mocha.Suite) {
|
||||||
async (window, snapshot) => {
|
async (window, snapshot) => {
|
||||||
const leftPane = window.locator('#LeftPane');
|
const leftPane = window.locator('#LeftPane');
|
||||||
const pinnedElem = leftPane.locator(
|
const pinnedElem = leftPane.locator(
|
||||||
`[data-testid="${pinned.toContact().aci}"] >> "cat photo"`
|
`[data-testid="${pinned.device.aci}"] >> "cat photo"`
|
||||||
);
|
);
|
||||||
|
|
||||||
debug('Waiting for messages to pinned contact to come through');
|
debug('Waiting for messages to pinned contact to come through');
|
||||||
await pinnedElem.click();
|
await pinnedElem.click();
|
||||||
|
|
||||||
const contactElem = leftPane.locator(
|
const contactElem = leftPane.locator(
|
||||||
`[data-testid="${friend.toContact().aci}"] >> "respond 4"`
|
`[data-testid="${friend.device.aci}"] >> "respond 4"`
|
||||||
);
|
);
|
||||||
|
|
||||||
debug('Waiting for messages to regular contact to come through');
|
debug('Waiting for messages to regular contact to come through');
|
||||||
|
|
|
@ -62,9 +62,7 @@ Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
|
||||||
|
|
||||||
const openConvo = async (contact: PrimaryDevice): Promise<void> => {
|
const openConvo = async (contact: PrimaryDevice): Promise<void> => {
|
||||||
debug('opening conversation', contact.profileName);
|
debug('opening conversation', contact.profileName);
|
||||||
const item = leftPane.locator(
|
const item = leftPane.locator(`[data-testid="${contact.device.aci}"]`);
|
||||||
`[data-testid="${contact.toContact().aci}"]`
|
|
||||||
);
|
|
||||||
|
|
||||||
await item.click();
|
await item.click();
|
||||||
};
|
};
|
||||||
|
|
|
@ -126,39 +126,49 @@ Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
|
||||||
}
|
}
|
||||||
debug('encrypted');
|
debug('encrypted');
|
||||||
|
|
||||||
await Promise.all(messages.map(message => server.send(desktop, message)));
|
debug('sending first message');
|
||||||
|
{
|
||||||
|
const firstMessage = messages.shift();
|
||||||
|
if (firstMessage != null) {
|
||||||
|
await server.send(desktop, firstMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const window = await app.getWindow();
|
const window = await app.getWindow();
|
||||||
|
|
||||||
|
debug('waiting for conversation');
|
||||||
|
{
|
||||||
|
const leftPane = window.locator('#LeftPane');
|
||||||
|
|
||||||
|
// Left pane should show either the message preview or
|
||||||
|
// "You were added to the group".
|
||||||
|
await leftPane
|
||||||
|
.locator(
|
||||||
|
`.module-conversation-list__item--contact-or-conversation[data-testid="${group.id}"]`
|
||||||
|
)
|
||||||
|
.waitFor();
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('sending the rest of messages');
|
||||||
|
await Promise.all(messages.map(message => server.send(desktop, message)));
|
||||||
|
|
||||||
debug('opening conversation');
|
debug('opening conversation');
|
||||||
{
|
{
|
||||||
const leftPane = window.locator('#LeftPane');
|
const leftPane = window.locator('#LeftPane');
|
||||||
|
|
||||||
const item = leftPane
|
await leftPane
|
||||||
.locator(
|
.locator(
|
||||||
`.module-conversation-list__item--contact-or-conversation[data-testid="${group.id}"]`
|
`.module-conversation-list__item--contact-or-conversation[data-testid="${group.id}"]` +
|
||||||
|
` >> text=${LAST_MESSAGE}`
|
||||||
)
|
)
|
||||||
.first();
|
.click();
|
||||||
|
|
||||||
// Wait for unread indicator to give desktop time to process messages without
|
|
||||||
// the timeline open
|
|
||||||
await item
|
|
||||||
.locator(
|
|
||||||
'.module-conversation-list__item--contact-or-conversation__content'
|
|
||||||
)
|
|
||||||
.locator(
|
|
||||||
'.module-conversation-list__item--contact-or-conversation__unread-indicator'
|
|
||||||
)
|
|
||||||
.first()
|
|
||||||
.waitFor();
|
|
||||||
|
|
||||||
await item.click();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debug('scrolling to bottom of timeline');
|
debug('scrolling to bottom of timeline');
|
||||||
await window
|
await window
|
||||||
.locator('.module-timeline__messages__at-bottom-detector')
|
.locator('.ScrollDownButton')
|
||||||
.scrollIntoViewIfNeeded();
|
.or(window.locator(`.module-message >> text="${LAST_MESSAGE}"`))
|
||||||
|
.click({ timeout: MINUTE });
|
||||||
|
|
||||||
debug('finding message in timeline');
|
debug('finding message in timeline');
|
||||||
{
|
{
|
||||||
|
|
|
@ -68,7 +68,7 @@ Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
|
||||||
{
|
{
|
||||||
const leftPane = window.locator('#LeftPane');
|
const leftPane = window.locator('#LeftPane');
|
||||||
const item = leftPane.locator(
|
const item = leftPane.locator(
|
||||||
`[data-testid="${first.toContact().aci}"] >> text=${LAST_MESSAGE}`
|
`[data-testid="${first.device.aci}"] >> text=${LAST_MESSAGE}`
|
||||||
);
|
);
|
||||||
await item.click();
|
await item.click();
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,9 +49,7 @@ Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
|
||||||
|
|
||||||
const leftPane = window.locator('#LeftPane');
|
const leftPane = window.locator('#LeftPane');
|
||||||
|
|
||||||
const item = leftPane.locator(
|
const item = leftPane.locator(`[data-testid="${lastContact?.device.aci}"]`);
|
||||||
`[data-testid="${lastContact?.toContact().aci}"]`
|
|
||||||
);
|
|
||||||
await item.waitFor();
|
await item.waitFor();
|
||||||
|
|
||||||
const duration = Date.now() - start;
|
const duration = Date.now() - start;
|
||||||
|
|
|
@ -129,11 +129,11 @@ function maybeWrapInSyncMessage({
|
||||||
? {
|
? {
|
||||||
syncMessage: {
|
syncMessage: {
|
||||||
sent: {
|
sent: {
|
||||||
destinationServiceId: getDevice(to).aci,
|
destinationServiceIdBinary: getDevice(to).aciBinary,
|
||||||
message: dataMessage,
|
message: dataMessage,
|
||||||
timestamp: dataMessage.timestamp,
|
timestamp: dataMessage.timestamp,
|
||||||
unidentifiedStatus: (sentTo ?? [to]).map(contact => ({
|
unidentifiedStatus: (sentTo ?? [to]).map(contact => ({
|
||||||
destinationServiceId: getDevice(contact).aci,
|
destinationServiceIdBinary: getDevice(contact).aciBinary,
|
||||||
destination: getDevice(contact).number,
|
destination: getDevice(contact).number,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
|
@ -222,7 +222,7 @@ export function sendReaction({
|
||||||
timestamp: Long.fromNumber(reactionTimestamp),
|
timestamp: Long.fromNumber(reactionTimestamp),
|
||||||
reaction: {
|
reaction: {
|
||||||
emoji,
|
emoji,
|
||||||
targetAuthorAci: getDevice(targetAuthor).aci,
|
targetAuthorAciBinary: getDevice(targetAuthor).aciRawUuid,
|
||||||
targetSentTimestamp: Long.fromNumber(targetMessageTimestamp),
|
targetSentTimestamp: Long.fromNumber(targetMessageTimestamp),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -119,13 +119,13 @@ describe('attachment backfill', function (this: Mocha.Suite) {
|
||||||
return entry.syncMessage.attachmentBackfillRequest != null;
|
return entry.syncMessage.attachmentBackfillRequest != null;
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.deepEqual(
|
||||||
request?.targetConversation?.threadServiceId,
|
request?.targetConversation?.threadServiceIdBinary,
|
||||||
unknownContact.device.aci
|
unknownContact.device.aciBinary
|
||||||
);
|
);
|
||||||
assert.strictEqual(
|
assert.deepEqual(
|
||||||
request?.targetMessage?.authorServiceId,
|
request?.targetMessage?.authorServiceIdBinary,
|
||||||
unknownContact.device.aci
|
unknownContact.device.aciBinary
|
||||||
);
|
);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
request?.targetMessage?.sentTimestamp?.toNumber(),
|
request?.targetMessage?.sentTimestamp?.toNumber(),
|
||||||
|
@ -302,13 +302,13 @@ describe('attachment backfill', function (this: Mocha.Suite) {
|
||||||
return entry.syncMessage.attachmentBackfillRequest != null;
|
return entry.syncMessage.attachmentBackfillRequest != null;
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.deepEqual(
|
||||||
request?.targetConversation?.threadServiceId,
|
request?.targetConversation?.threadServiceIdBinary,
|
||||||
unknownContact.device.aci
|
unknownContact.device.aciBinary
|
||||||
);
|
);
|
||||||
assert.strictEqual(
|
assert.deepEqual(
|
||||||
request?.targetMessage?.authorServiceId,
|
request?.targetMessage?.authorServiceIdBinary,
|
||||||
unknownContact.device.aci
|
unknownContact.device.aciBinary
|
||||||
);
|
);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
request?.targetMessage?.sentTimestamp?.toNumber(),
|
request?.targetMessage?.sentTimestamp?.toNumber(),
|
||||||
|
@ -380,13 +380,13 @@ describe('attachment backfill', function (this: Mocha.Suite) {
|
||||||
return entry.syncMessage.attachmentBackfillRequest != null;
|
return entry.syncMessage.attachmentBackfillRequest != null;
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.deepEqual(
|
||||||
request?.targetConversation?.threadServiceId,
|
request?.targetConversation?.threadServiceIdBinary,
|
||||||
unknownContact.device.aci
|
unknownContact.device.aciBinary
|
||||||
);
|
);
|
||||||
assert.strictEqual(
|
assert.deepEqual(
|
||||||
request?.targetMessage?.authorServiceId,
|
request?.targetMessage?.authorServiceIdBinary,
|
||||||
unknownContact.device.aci
|
unknownContact.device.aciBinary
|
||||||
);
|
);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
request?.targetMessage?.sentTimestamp?.toNumber(),
|
request?.targetMessage?.sentTimestamp?.toNumber(),
|
||||||
|
@ -439,7 +439,7 @@ describe('attachment backfill', function (this: Mocha.Suite) {
|
||||||
desktop,
|
desktop,
|
||||||
quote: {
|
quote: {
|
||||||
id: Long.fromNumber(bootstrap.getTimestamp()),
|
id: Long.fromNumber(bootstrap.getTimestamp()),
|
||||||
authorAci: unknownContact.device.aci,
|
authorAciBinary: unknownContact.device.aciRawUuid,
|
||||||
text: 'quote text',
|
text: 'quote text',
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { Proto } from '@signalapp/mock-server';
|
import { Proto } from '@signalapp/mock-server';
|
||||||
|
import { Aci } from '@signalapp/libsignal-client';
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import createDebug from 'debug';
|
import createDebug from 'debug';
|
||||||
import Long from 'long';
|
import Long from 'long';
|
||||||
|
@ -23,6 +24,7 @@ import { sleep } from '../../util/sleep';
|
||||||
export const debug = createDebug('mock:test:edit');
|
export const debug = createDebug('mock:test:edit');
|
||||||
|
|
||||||
const ACI_1 = generateAci();
|
const ACI_1 = generateAci();
|
||||||
|
const ACI_1_BINARY = Aci.parseFromServiceIdString(ACI_1).getRawUuidBytes();
|
||||||
const UNPROCESSED_ATTACHMENT: Proto.IAttachmentPointer = {
|
const UNPROCESSED_ATTACHMENT: Proto.IAttachmentPointer = {
|
||||||
cdnId: Long.fromNumber(123),
|
cdnId: Long.fromNumber(123),
|
||||||
key: new Uint8Array([1, 2, 3]),
|
key: new Uint8Array([1, 2, 3]),
|
||||||
|
@ -57,7 +59,7 @@ function createMessageWithQuote(body: string): Proto.IDataMessage {
|
||||||
body,
|
body,
|
||||||
quote: {
|
quote: {
|
||||||
id: Long.fromNumber(1),
|
id: Long.fromNumber(1),
|
||||||
authorAci: ACI_1,
|
authorAciBinary: ACI_1_BINARY,
|
||||||
text: 'text',
|
text: 'text',
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
|
@ -516,7 +518,6 @@ describe('editing', function (this: Mocha.Suite) {
|
||||||
const { contacts, desktop } = bootstrap;
|
const { contacts, desktop } = bootstrap;
|
||||||
|
|
||||||
const [friend] = contacts;
|
const [friend] = contacts;
|
||||||
const contact = friend.toContact();
|
|
||||||
|
|
||||||
const page = await app.getWindow();
|
const page = await app.getWindow();
|
||||||
|
|
||||||
|
@ -567,7 +568,7 @@ describe('editing', function (this: Mocha.Suite) {
|
||||||
debug("getting friend's conversationId");
|
debug("getting friend's conversationId");
|
||||||
const conversationId = await page.evaluate(
|
const conversationId = await page.evaluate(
|
||||||
serviceId => window.SignalCI?.getConversationId(serviceId),
|
serviceId => window.SignalCI?.getConversationId(serviceId),
|
||||||
contact.aci
|
friend.device.aci
|
||||||
);
|
);
|
||||||
debug(`got friend's conversationId: ${conversationId}`);
|
debug(`got friend's conversationId: ${conversationId}`);
|
||||||
strictAssert(conversationId, 'conversationId exists');
|
strictAssert(conversationId, 'conversationId exists');
|
||||||
|
|
|
@ -70,7 +70,6 @@ describe('messaging/expireTimerVersion', function (this: Mocha.Suite) {
|
||||||
identifier: uuidToBytes(MY_STORY_ID),
|
identifier: uuidToBytes(MY_STORY_ID),
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
name: MY_STORY_ID,
|
name: MY_STORY_ID,
|
||||||
recipientServiceIds: [],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -180,11 +179,11 @@ describe('messaging/expireTimerVersion', function (this: Mocha.Suite) {
|
||||||
const sendSync = async () => {
|
const sendSync = async () => {
|
||||||
debug('Send a sync message');
|
debug('Send a sync message');
|
||||||
const timestamp = bootstrap.getTimestamp();
|
const timestamp = bootstrap.getTimestamp();
|
||||||
const destinationServiceId = stranger.device.aci;
|
const destinationServiceIdBinary = stranger.device.aciBinary;
|
||||||
const content = {
|
const content = {
|
||||||
syncMessage: {
|
syncMessage: {
|
||||||
sent: {
|
sent: {
|
||||||
destinationServiceId,
|
destinationServiceIdBinary,
|
||||||
timestamp: Long.fromNumber(timestamp),
|
timestamp: Long.fromNumber(timestamp),
|
||||||
message: {
|
message: {
|
||||||
body: 'request',
|
body: 'request',
|
||||||
|
@ -194,7 +193,7 @@ describe('messaging/expireTimerVersion', function (this: Mocha.Suite) {
|
||||||
},
|
},
|
||||||
unidentifiedStatus: [
|
unidentifiedStatus: [
|
||||||
{
|
{
|
||||||
destinationServiceId,
|
destinationServiceIdBinary,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -102,14 +102,14 @@ describe('readSync', function (this: Mocha.Suite) {
|
||||||
Long.fromNumber(timestamp)
|
Long.fromNumber(timestamp)
|
||||||
);
|
);
|
||||||
|
|
||||||
const senderAci = friend.device.aci;
|
const senderAciBinary = friend.device.aciRawUuid;
|
||||||
|
|
||||||
await phone.sendRaw(
|
await phone.sendRaw(
|
||||||
desktop,
|
desktop,
|
||||||
{
|
{
|
||||||
syncMessage: {
|
syncMessage: {
|
||||||
read: longTimestamps.map(timestamp => ({
|
read: longTimestamps.map(timestamp => ({
|
||||||
senderAci,
|
senderAciBinary,
|
||||||
timestamp,
|
timestamp,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
|
|
|
@ -55,7 +55,7 @@ describe('safety number', function (this: Mocha.Suite) {
|
||||||
identifier: uuidToBytes(MY_STORY_ID),
|
identifier: uuidToBytes(MY_STORY_ID),
|
||||||
isBlockList: false,
|
isBlockList: false,
|
||||||
name: MY_STORY_ID,
|
name: MY_STORY_ID,
|
||||||
recipientServiceIds: [alice.device.aci],
|
recipientServiceIdsBinary: [alice.device.aciBinary],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -68,7 +68,7 @@ describe('sendSync', function (this: Mocha.Suite) {
|
||||||
timestamp: Long.fromNumber(timestamp),
|
timestamp: Long.fromNumber(timestamp),
|
||||||
message: originalDataMessage,
|
message: originalDataMessage,
|
||||||
unidentifiedStatus: members.map(member => ({
|
unidentifiedStatus: members.map(member => ({
|
||||||
destinationServiceId: member.device.aci,
|
destinationServiceIdBinary: member.device.aciBinary,
|
||||||
destination: member.device.number,
|
destination: member.device.number,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
|
|
|
@ -51,7 +51,6 @@ describe('story/messaging', function (this: Mocha.Suite) {
|
||||||
identifier: uuidToBytes(MY_STORY_ID),
|
identifier: uuidToBytes(MY_STORY_ID),
|
||||||
isBlockList: false,
|
isBlockList: false,
|
||||||
name: MY_STORY_ID,
|
name: MY_STORY_ID,
|
||||||
recipientServiceIds: [],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -65,7 +64,7 @@ describe('story/messaging', function (this: Mocha.Suite) {
|
||||||
identifier: uuidToBytes(DISTRIBUTION1),
|
identifier: uuidToBytes(DISTRIBUTION1),
|
||||||
isBlockList: false,
|
isBlockList: false,
|
||||||
name: 'first',
|
name: 'first',
|
||||||
recipientServiceIds: [first.device.aci],
|
recipientServiceIdsBinary: [first.device.aciBinary],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -77,7 +76,7 @@ describe('story/messaging', function (this: Mocha.Suite) {
|
||||||
identifier: uuidToBytes(DISTRIBUTION2),
|
identifier: uuidToBytes(DISTRIBUTION2),
|
||||||
isBlockList: false,
|
isBlockList: false,
|
||||||
name: 'second',
|
name: 'second',
|
||||||
recipientServiceIds: [second.device.aci],
|
recipientServiceIdsBinary: [second.device.aciBinary],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -148,12 +147,12 @@ describe('story/messaging', function (this: Mocha.Suite) {
|
||||||
},
|
},
|
||||||
storyMessageRecipients: [
|
storyMessageRecipients: [
|
||||||
{
|
{
|
||||||
destinationServiceId: first.device.aci,
|
destinationServiceIdBinary: first.device.aciBinary,
|
||||||
distributionListIds: [DISTRIBUTION1],
|
distributionListIds: [DISTRIBUTION1],
|
||||||
isAllowedToReply: true,
|
isAllowedToReply: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
destinationServiceId: second.device.aci,
|
destinationServiceIdBinary: second.device.aciBinary,
|
||||||
distributionListIds: [DISTRIBUTION2],
|
distributionListIds: [DISTRIBUTION2],
|
||||||
isAllowedToReply: true,
|
isAllowedToReply: true,
|
||||||
},
|
},
|
||||||
|
@ -171,7 +170,7 @@ describe('story/messaging', function (this: Mocha.Suite) {
|
||||||
dataMessage: {
|
dataMessage: {
|
||||||
body: 'first reply',
|
body: 'first reply',
|
||||||
storyContext: {
|
storyContext: {
|
||||||
authorAci: phone.device.aci,
|
authorAciBinary: phone.device.aciRawUuid,
|
||||||
sentTimestamp: Long.fromNumber(sentAt),
|
sentTimestamp: Long.fromNumber(sentAt),
|
||||||
},
|
},
|
||||||
timestamp: Long.fromNumber(sentAt + 1),
|
timestamp: Long.fromNumber(sentAt + 1),
|
||||||
|
@ -185,7 +184,7 @@ describe('story/messaging', function (this: Mocha.Suite) {
|
||||||
dataMessage: {
|
dataMessage: {
|
||||||
body: 'second reply',
|
body: 'second reply',
|
||||||
storyContext: {
|
storyContext: {
|
||||||
authorAci: phone.device.aci,
|
authorAciBinary: phone.device.aciRawUuid,
|
||||||
sentTimestamp: Long.fromNumber(sentAt),
|
sentTimestamp: Long.fromNumber(sentAt),
|
||||||
},
|
},
|
||||||
timestamp: Long.fromNumber(sentAt + 2),
|
timestamp: Long.fromNumber(sentAt + 2),
|
||||||
|
@ -245,7 +244,7 @@ describe('story/messaging', function (this: Mocha.Suite) {
|
||||||
dataMessage: {
|
dataMessage: {
|
||||||
body: 'first reply',
|
body: 'first reply',
|
||||||
storyContext: {
|
storyContext: {
|
||||||
authorAci: desktop.aci,
|
authorAciBinary: desktop.aciRawUuid,
|
||||||
sentTimestamp: Long.fromNumber(sentAt),
|
sentTimestamp: Long.fromNumber(sentAt),
|
||||||
},
|
},
|
||||||
groupV2: {
|
groupV2: {
|
||||||
|
|
|
@ -98,7 +98,7 @@ describe('unknown contacts', function (this: Mocha.Suite) {
|
||||||
syncMessage: {
|
syncMessage: {
|
||||||
messageRequestResponse: {
|
messageRequestResponse: {
|
||||||
type: Proto.SyncMessage.MessageRequestResponse.Type.ACCEPT,
|
type: Proto.SyncMessage.MessageRequestResponse.Type.ACCEPT,
|
||||||
threadAci: unknownContact.device.aci,
|
threadAciBinary: unknownContact.device.aciRawUuid,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -64,7 +64,7 @@ describe('Libsignal-net', function (this: Mocha.Suite) {
|
||||||
{
|
{
|
||||||
const leftPane = window.locator('#LeftPane');
|
const leftPane = window.locator('#LeftPane');
|
||||||
const item = leftPane
|
const item = leftPane
|
||||||
.getByTestId(contact.toContact().aci)
|
.getByTestId(contact.device.aci)
|
||||||
.getByText('incoming message');
|
.getByText('incoming message');
|
||||||
await item.click();
|
await item.click();
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,7 @@ describe('pnp/calling', function (this: Mocha.Suite) {
|
||||||
});
|
});
|
||||||
|
|
||||||
debug('Open conversation with a known contact');
|
debug('Open conversation with a known contact');
|
||||||
await leftPane.locator(`[data-testid="${alice.toContact().aci}"]`).click();
|
await leftPane.locator(`[data-testid="${alice.device.aci}"]`).click();
|
||||||
|
|
||||||
debug('Accept conversation from a known contact');
|
debug('Accept conversation from a known contact');
|
||||||
await acceptConversation(window);
|
await acceptConversation(window);
|
||||||
|
|
|
@ -61,7 +61,7 @@ describe('pnp/change number', function (this: Mocha.Suite) {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
debug('opening conversation with the first contact');
|
debug('opening conversation with the first contact');
|
||||||
await leftPane.locator(`[data-testid="${first.toContact().aci}"]`).click();
|
await leftPane.locator(`[data-testid="${first.device.aci}"]`).click();
|
||||||
|
|
||||||
debug('done');
|
debug('done');
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { timingSafeEqual } from 'node:crypto';
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { ServiceIdKind, Proto, StorageState } from '@signalapp/mock-server';
|
import { ServiceIdKind, Proto, StorageState } from '@signalapp/mock-server';
|
||||||
import type { PrimaryDevice } from '@signalapp/mock-server';
|
import type { PrimaryDevice } from '@signalapp/mock-server';
|
||||||
|
@ -10,7 +11,6 @@ import Long from 'long';
|
||||||
import * as durations from '../../util/durations';
|
import * as durations from '../../util/durations';
|
||||||
import { uuidToBytes } from '../../util/uuidToBytes';
|
import { uuidToBytes } from '../../util/uuidToBytes';
|
||||||
import { generateConfigMatrix } from '../../util/generateConfigMatrix';
|
import { generateConfigMatrix } from '../../util/generateConfigMatrix';
|
||||||
import { toUntaggedPni } from '../../types/ServiceId';
|
|
||||||
import { MY_STORY_ID } from '../../types/Stories';
|
import { MY_STORY_ID } from '../../types/Stories';
|
||||||
import { Bootstrap } from '../bootstrap';
|
import { Bootstrap } from '../bootstrap';
|
||||||
import type { App } from '../bootstrap';
|
import type { App } from '../bootstrap';
|
||||||
|
@ -87,7 +87,6 @@ describe('pnp/merge', function (this: Mocha.Suite) {
|
||||||
identifier: uuidToBytes(MY_STORY_ID),
|
identifier: uuidToBytes(MY_STORY_ID),
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
name: MY_STORY_ID,
|
name: MY_STORY_ID,
|
||||||
recipientServiceIds: [],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -282,7 +281,7 @@ describe('pnp/merge', function (this: Mocha.Suite) {
|
||||||
let state = await phone.expectStorageState('consistency check');
|
let state = await phone.expectStorageState('consistency check');
|
||||||
|
|
||||||
state = state.updateContact(pniContact, {
|
state = state.updateContact(pniContact, {
|
||||||
pni: undefined,
|
pniBinary: undefined,
|
||||||
e164: undefined,
|
e164: undefined,
|
||||||
unregisteredAtTimestamp: Long.fromNumber(bootstrap.getTimestamp()),
|
unregisteredAtTimestamp: Long.fromNumber(bootstrap.getTimestamp()),
|
||||||
});
|
});
|
||||||
|
@ -403,25 +402,34 @@ describe('pnp/merge', function (this: Mocha.Suite) {
|
||||||
throw new Error('Invalid record');
|
throw new Error('Invalid record');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { aci, e164, pni } = contact;
|
const { aciBinary, e164, pniBinary } = contact;
|
||||||
if (aci === pniContact.device.aci) {
|
if (
|
||||||
|
aciBinary?.length &&
|
||||||
|
timingSafeEqual(aciBinary, pniContact.device.aciRawUuid)
|
||||||
|
) {
|
||||||
aciContacts += 1;
|
aciContacts += 1;
|
||||||
assert.strictEqual(pni, '');
|
assert.strictEqual(pniBinary?.length, 0);
|
||||||
assert.strictEqual(e164, '');
|
assert.strictEqual(e164, '');
|
||||||
} else if (pni === toUntaggedPni(pniContact.device.pni)) {
|
} else if (
|
||||||
|
pniBinary?.length &&
|
||||||
|
timingSafeEqual(pniBinary, pniContact.device.pniRawUuid)
|
||||||
|
) {
|
||||||
pniContacts += 1;
|
pniContacts += 1;
|
||||||
assert.strictEqual(aci, '');
|
assert.strictEqual(aciBinary?.length, 0);
|
||||||
assert.strictEqual(e164, pniContact.device.number);
|
assert.strictEqual(e164, pniContact.device.number);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert.strictEqual(aciContacts, 1);
|
assert.strictEqual(aciContacts, 1);
|
||||||
assert.strictEqual(pniContacts, 1);
|
assert.strictEqual(pniContacts, 1);
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.deepEqual(
|
||||||
removed[0].contact?.pni,
|
removed[0].contact?.pniBinary,
|
||||||
toUntaggedPni(pniContact.device.pni)
|
pniContact.device.pniRawUuid
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
removed[0].contact?.aciBinary,
|
||||||
|
pniContact.device.aciRawUuid
|
||||||
);
|
);
|
||||||
assert.strictEqual(removed[0].contact?.aci, pniContact.device.aci);
|
|
||||||
|
|
||||||
// Pin PNI so that it appears in the left pane
|
// Pin PNI so that it appears in the left pane
|
||||||
const updated = newState.pin(pniContact, ServiceIdKind.PNI);
|
const updated = newState.pin(pniContact, ServiceIdKind.PNI);
|
||||||
|
@ -556,12 +564,12 @@ describe('pnp/merge', function (this: Mocha.Suite) {
|
||||||
for (const key of ['aci' as const, 'pni' as const]) {
|
for (const key of ['aci' as const, 'pni' as const]) {
|
||||||
debug(`Send a ${key} sync message`);
|
debug(`Send a ${key} sync message`);
|
||||||
const timestamp = bootstrap.getTimestamp();
|
const timestamp = bootstrap.getTimestamp();
|
||||||
const destinationServiceId = pniContact.device[key];
|
const destinationServiceIdBinary = pniContact.device[`${key}Binary`];
|
||||||
const destination = key === 'pni' ? pniContact.device.number : undefined;
|
const destination = key === 'pni' ? pniContact.device.number : undefined;
|
||||||
const content = {
|
const content = {
|
||||||
syncMessage: {
|
syncMessage: {
|
||||||
sent: {
|
sent: {
|
||||||
destinationServiceId,
|
destinationServiceIdBinary,
|
||||||
destination,
|
destination,
|
||||||
timestamp: Long.fromNumber(timestamp),
|
timestamp: Long.fromNumber(timestamp),
|
||||||
message: {
|
message: {
|
||||||
|
@ -572,7 +580,7 @@ describe('pnp/merge', function (this: Mocha.Suite) {
|
||||||
},
|
},
|
||||||
unidentifiedStatus: [
|
unidentifiedStatus: [
|
||||||
{
|
{
|
||||||
destinationServiceId,
|
destinationServiceIdBinary,
|
||||||
destination,
|
destination,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -9,7 +9,6 @@ import createDebug from 'debug';
|
||||||
import * as durations from '../../util/durations';
|
import * as durations from '../../util/durations';
|
||||||
import { uuidToBytes } from '../../util/uuidToBytes';
|
import { uuidToBytes } from '../../util/uuidToBytes';
|
||||||
import { MY_STORY_ID } from '../../types/Stories';
|
import { MY_STORY_ID } from '../../types/Stories';
|
||||||
import { toUntaggedPni } from '../../types/ServiceId';
|
|
||||||
import { Bootstrap } from '../bootstrap';
|
import { Bootstrap } from '../bootstrap';
|
||||||
import type { App } from '../bootstrap';
|
import type { App } from '../bootstrap';
|
||||||
import {
|
import {
|
||||||
|
@ -73,7 +72,6 @@ describe('pnp/phone discovery', function (this: Mocha.Suite) {
|
||||||
identifier: uuidToBytes(MY_STORY_ID),
|
identifier: uuidToBytes(MY_STORY_ID),
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
name: MY_STORY_ID,
|
name: MY_STORY_ID,
|
||||||
recipientServiceIds: [],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -119,7 +117,7 @@ describe('pnp/phone discovery', function (this: Mocha.Suite) {
|
||||||
whitelisted: true,
|
whitelisted: true,
|
||||||
identityKey: pniContact.publicKey.serialize(),
|
identityKey: pniContact.publicKey.serialize(),
|
||||||
profileKey: pniContact.profileKey.serialize(),
|
profileKey: pniContact.profileKey.serialize(),
|
||||||
pni: toUntaggedPni(pniContact.device.pni),
|
pniBinary: pniContact.device.pniRawUuid,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
await phone.sendFetchStorage({
|
await phone.sendFetchStorage({
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { timingSafeEqual } from 'node:crypto';
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { ServiceIdKind, StorageState, Proto } from '@signalapp/mock-server';
|
import { ServiceIdKind, StorageState, Proto } from '@signalapp/mock-server';
|
||||||
import type { PrimaryDevice } from '@signalapp/mock-server';
|
import type { PrimaryDevice } from '@signalapp/mock-server';
|
||||||
import createDebug from 'debug';
|
import createDebug from 'debug';
|
||||||
|
|
||||||
import * as durations from '../../util/durations';
|
import * as durations from '../../util/durations';
|
||||||
import { generatePni, toUntaggedPni } from '../../types/ServiceId';
|
import { generatePni } from '../../types/ServiceId';
|
||||||
|
import { toPniObject } from '../../util/ServiceId';
|
||||||
import { Bootstrap } from '../bootstrap';
|
import { Bootstrap } from '../bootstrap';
|
||||||
import type { App } from '../bootstrap';
|
import type { App } from '../bootstrap';
|
||||||
import {
|
import {
|
||||||
|
@ -54,7 +56,7 @@ describe('pnp/PNI Change', function (this: Mocha.Suite) {
|
||||||
whitelisted: true,
|
whitelisted: true,
|
||||||
e164: contactA.device.number,
|
e164: contactA.device.number,
|
||||||
identityKey: contactA.getPublicKey(ServiceIdKind.PNI).serialize(),
|
identityKey: contactA.getPublicKey(ServiceIdKind.PNI).serialize(),
|
||||||
pni: toUntaggedPni(contactA.device.pni),
|
pniBinary: contactA.device.pniRawUuid,
|
||||||
givenName: 'ContactA',
|
givenName: 'ContactA',
|
||||||
},
|
},
|
||||||
ServiceIdKind.PNI
|
ServiceIdKind.PNI
|
||||||
|
@ -135,17 +137,21 @@ describe('pnp/PNI Change', function (this: Mocha.Suite) {
|
||||||
const state = await phone.expectStorageState('consistency check');
|
const state = await phone.expectStorageState('consistency check');
|
||||||
const updated = await phone.setStorageState(
|
const updated = await phone.setStorageState(
|
||||||
state
|
state
|
||||||
.removeRecord(
|
.removeRecord(item => {
|
||||||
item =>
|
return item.record.contact?.pniBinary?.length
|
||||||
item.record.contact?.pni === toUntaggedPni(contactA.device.pni)
|
? timingSafeEqual(
|
||||||
)
|
item.record.contact.pniBinary,
|
||||||
|
contactA.device.pniRawUuid
|
||||||
|
)
|
||||||
|
: false;
|
||||||
|
})
|
||||||
.addContact(
|
.addContact(
|
||||||
contactA,
|
contactA,
|
||||||
{
|
{
|
||||||
identityState: Proto.ContactRecord.IdentityState.DEFAULT,
|
identityState: Proto.ContactRecord.IdentityState.DEFAULT,
|
||||||
whitelisted: true,
|
whitelisted: true,
|
||||||
e164: contactA.device.number,
|
e164: contactA.device.number,
|
||||||
pni: toUntaggedPni(updatedPni),
|
pniBinary: toPniObject(updatedPni).getRawUuidBytes(),
|
||||||
identityKey: contactA.getPublicKey(ServiceIdKind.PNI).serialize(),
|
identityKey: contactA.getPublicKey(ServiceIdKind.PNI).serialize(),
|
||||||
},
|
},
|
||||||
ServiceIdKind.PNI
|
ServiceIdKind.PNI
|
||||||
|
@ -232,17 +238,21 @@ describe('pnp/PNI Change', function (this: Mocha.Suite) {
|
||||||
const state = await phone.expectStorageState('consistency check');
|
const state = await phone.expectStorageState('consistency check');
|
||||||
const updated = await phone.setStorageState(
|
const updated = await phone.setStorageState(
|
||||||
state
|
state
|
||||||
.removeRecord(
|
.removeRecord(item => {
|
||||||
item =>
|
return item.record.contact?.pniBinary?.length
|
||||||
item.record.contact?.pni === toUntaggedPni(contactA.device.pni)
|
? timingSafeEqual(
|
||||||
)
|
item.record.contact.pniBinary,
|
||||||
|
contactA.device.pniRawUuid
|
||||||
|
)
|
||||||
|
: false;
|
||||||
|
})
|
||||||
.addContact(
|
.addContact(
|
||||||
contactB,
|
contactB,
|
||||||
{
|
{
|
||||||
identityState: Proto.ContactRecord.IdentityState.DEFAULT,
|
identityState: Proto.ContactRecord.IdentityState.DEFAULT,
|
||||||
whitelisted: true,
|
whitelisted: true,
|
||||||
e164: contactA.device.number,
|
e164: contactA.device.number,
|
||||||
pni: toUntaggedPni(contactB.device.pni),
|
pniBinary: contactB.device.pniRawUuid,
|
||||||
|
|
||||||
// Key change - different identity key
|
// Key change - different identity key
|
||||||
identityKey: contactB.publicKey.serialize(),
|
identityKey: contactB.publicKey.serialize(),
|
||||||
|
@ -334,17 +344,21 @@ describe('pnp/PNI Change', function (this: Mocha.Suite) {
|
||||||
const state = await phone.expectStorageState('consistency check');
|
const state = await phone.expectStorageState('consistency check');
|
||||||
const updated = await phone.setStorageState(
|
const updated = await phone.setStorageState(
|
||||||
state
|
state
|
||||||
.removeRecord(
|
.removeRecord(item => {
|
||||||
item =>
|
return item.record.contact?.pniBinary?.length
|
||||||
item.record.contact?.pni === toUntaggedPni(contactA.device.pni)
|
? timingSafeEqual(
|
||||||
)
|
item.record.contact.pniBinary,
|
||||||
|
contactA.device.pniRawUuid
|
||||||
|
)
|
||||||
|
: false;
|
||||||
|
})
|
||||||
.addContact(
|
.addContact(
|
||||||
contactB,
|
contactB,
|
||||||
{
|
{
|
||||||
identityState: Proto.ContactRecord.IdentityState.DEFAULT,
|
identityState: Proto.ContactRecord.IdentityState.DEFAULT,
|
||||||
whitelisted: true,
|
whitelisted: true,
|
||||||
e164: contactA.device.number,
|
e164: contactA.device.number,
|
||||||
pni: toUntaggedPni(contactB.device.pni),
|
pniBinary: contactB.device.pniRawUuid,
|
||||||
|
|
||||||
// Note: No identityKey key provided here!
|
// Note: No identityKey key provided here!
|
||||||
},
|
},
|
||||||
|
@ -465,17 +479,21 @@ describe('pnp/PNI Change', function (this: Mocha.Suite) {
|
||||||
const state = await phone.expectStorageState('consistency check');
|
const state = await phone.expectStorageState('consistency check');
|
||||||
const updated = await phone.setStorageState(
|
const updated = await phone.setStorageState(
|
||||||
state
|
state
|
||||||
.removeRecord(
|
.removeRecord(item => {
|
||||||
item =>
|
return item.record.contact?.pniBinary?.length
|
||||||
item.record.contact?.pni === toUntaggedPni(contactA.device.pni)
|
? timingSafeEqual(
|
||||||
)
|
item.record.contact.pniBinary,
|
||||||
|
contactA.device.pniRawUuid
|
||||||
|
)
|
||||||
|
: false;
|
||||||
|
})
|
||||||
.addContact(
|
.addContact(
|
||||||
contactB,
|
contactB,
|
||||||
{
|
{
|
||||||
identityState: Proto.ContactRecord.IdentityState.DEFAULT,
|
identityState: Proto.ContactRecord.IdentityState.DEFAULT,
|
||||||
whitelisted: true,
|
whitelisted: true,
|
||||||
e164: contactA.device.number,
|
e164: contactA.device.number,
|
||||||
pni: toUntaggedPni(contactB.device.pni),
|
pniBinary: contactB.device.pniRawUuid,
|
||||||
|
|
||||||
// Note: No identityKey key provided here!
|
// Note: No identityKey key provided here!
|
||||||
},
|
},
|
||||||
|
@ -497,17 +515,21 @@ describe('pnp/PNI Change', function (this: Mocha.Suite) {
|
||||||
const state = await phone.expectStorageState('consistency check');
|
const state = await phone.expectStorageState('consistency check');
|
||||||
const updated = await phone.setStorageState(
|
const updated = await phone.setStorageState(
|
||||||
state
|
state
|
||||||
.removeRecord(
|
.removeRecord(item => {
|
||||||
item =>
|
return item.record.contact?.pniBinary?.length
|
||||||
item.record.contact?.pni === toUntaggedPni(contactB.device.pni)
|
? timingSafeEqual(
|
||||||
)
|
item.record.contact.pniBinary,
|
||||||
|
contactB.device.pniRawUuid
|
||||||
|
)
|
||||||
|
: false;
|
||||||
|
})
|
||||||
.addContact(
|
.addContact(
|
||||||
contactB,
|
contactB,
|
||||||
{
|
{
|
||||||
identityState: Proto.ContactRecord.IdentityState.DEFAULT,
|
identityState: Proto.ContactRecord.IdentityState.DEFAULT,
|
||||||
whitelisted: true,
|
whitelisted: true,
|
||||||
e164: contactA.device.number,
|
e164: contactA.device.number,
|
||||||
pni: toUntaggedPni(contactA.device.pni),
|
pniBinary: contactA.device.pniRawUuid,
|
||||||
},
|
},
|
||||||
ServiceIdKind.PNI
|
ServiceIdKind.PNI
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,7 +15,6 @@ import createDebug from 'debug';
|
||||||
import * as durations from '../../util/durations';
|
import * as durations from '../../util/durations';
|
||||||
import { uuidToBytes } from '../../util/uuidToBytes';
|
import { uuidToBytes } from '../../util/uuidToBytes';
|
||||||
import { MY_STORY_ID } from '../../types/Stories';
|
import { MY_STORY_ID } from '../../types/Stories';
|
||||||
import { isUntaggedPniString, toTaggedPni } from '../../types/ServiceId';
|
|
||||||
import { Bootstrap } from '../bootstrap';
|
import { Bootstrap } from '../bootstrap';
|
||||||
import type { App } from '../bootstrap';
|
import type { App } from '../bootstrap';
|
||||||
import {
|
import {
|
||||||
|
@ -61,7 +60,6 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
||||||
identifier: uuidToBytes(MY_STORY_ID),
|
identifier: uuidToBytes(MY_STORY_ID),
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
name: MY_STORY_ID,
|
name: MY_STORY_ID,
|
||||||
recipientServiceIds: [],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -132,9 +130,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
||||||
});
|
});
|
||||||
|
|
||||||
debug('Open conversation with the stranger');
|
debug('Open conversation with the stranger');
|
||||||
await leftPane
|
await leftPane.locator(`[data-testid="${stranger.device.aci}"]`).click();
|
||||||
.locator(`[data-testid="${stranger.toContact().aci}"]`)
|
|
||||||
.click();
|
|
||||||
|
|
||||||
debug('Accept conversation from a stranger');
|
debug('Accept conversation from a stranger');
|
||||||
await acceptConversation(window);
|
await acceptConversation(window);
|
||||||
|
@ -259,7 +255,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
||||||
|
|
||||||
debug('Send a PNI sync message');
|
debug('Send a PNI sync message');
|
||||||
const timestamp = bootstrap.getTimestamp();
|
const timestamp = bootstrap.getTimestamp();
|
||||||
const destinationServiceId = stranger.device.pni;
|
const destinationServiceIdBinary = stranger.device.pniBinary;
|
||||||
const destinationE164 = stranger.device.number;
|
const destinationE164 = stranger.device.number;
|
||||||
const destinationPniIdentityKey = await stranger.device.getIdentityKey(
|
const destinationPniIdentityKey = await stranger.device.getIdentityKey(
|
||||||
ServiceIdKind.PNI
|
ServiceIdKind.PNI
|
||||||
|
@ -271,13 +267,13 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
||||||
const content = {
|
const content = {
|
||||||
syncMessage: {
|
syncMessage: {
|
||||||
sent: {
|
sent: {
|
||||||
destinationServiceId,
|
destinationServiceIdBinary,
|
||||||
destinationE164,
|
destinationE164,
|
||||||
timestamp: Long.fromNumber(timestamp),
|
timestamp: Long.fromNumber(timestamp),
|
||||||
message: originalDataMessage,
|
message: originalDataMessage,
|
||||||
unidentifiedStatus: [
|
unidentifiedStatus: [
|
||||||
{
|
{
|
||||||
destinationServiceId,
|
destinationServiceIdBinary,
|
||||||
destinationPniIdentityKey: destinationPniIdentityKey.serialize(),
|
destinationPniIdentityKey: destinationPniIdentityKey.serialize(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -367,9 +363,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
||||||
});
|
});
|
||||||
|
|
||||||
debug('Wait for merge to happen');
|
debug('Wait for merge to happen');
|
||||||
await leftPane
|
await leftPane.locator(`[data-testid="${stranger.device.aci}"]`).waitFor();
|
||||||
.locator(`[data-testid="${stranger.toContact().aci}"]`)
|
|
||||||
.waitFor();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
debug('Wait for composition input to clear');
|
debug('Wait for composition input to clear');
|
||||||
|
@ -409,13 +403,8 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
||||||
);
|
);
|
||||||
assert(aciRecord, 'ACI Contact must be in storage service');
|
assert(aciRecord, 'ACI Contact must be in storage service');
|
||||||
|
|
||||||
assert.strictEqual(aciRecord?.aci, stranger.device.aci);
|
assert.deepEqual(aciRecord?.aciBinary, stranger.device.aciRawUuid);
|
||||||
assert.strictEqual(
|
assert.deepEqual(aciRecord?.pniBinary, stranger.device.pniRawUuid);
|
||||||
aciRecord?.pni &&
|
|
||||||
isUntaggedPniString(aciRecord?.pni) &&
|
|
||||||
toTaggedPni(aciRecord?.pni),
|
|
||||||
stranger.device.pni
|
|
||||||
);
|
|
||||||
assert.strictEqual(aciRecord?.pniSignatureVerified, true);
|
assert.strictEqual(aciRecord?.pniSignatureVerified, true);
|
||||||
|
|
||||||
// Two outgoing, one incoming
|
// Two outgoing, one incoming
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
import createDebug from 'debug';
|
import createDebug from 'debug';
|
||||||
|
|
||||||
import * as durations from '../../util/durations';
|
import * as durations from '../../util/durations';
|
||||||
import { generatePni, toUntaggedPni } from '../../types/ServiceId';
|
import { generatePni } from '../../types/ServiceId';
|
||||||
import { Bootstrap } from '../bootstrap';
|
import { Bootstrap } from '../bootstrap';
|
||||||
import type { App } from '../bootstrap';
|
import type { App } from '../bootstrap';
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ describe('pnp/PNI DecryptionError unlink', function (this: Mocha.Suite) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamp: bootstrap.getTimestamp(),
|
timestamp: bootstrap.getTimestamp(),
|
||||||
updatedPni: toUntaggedPni(generatePni()),
|
updatedPni: generatePni(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -107,7 +107,7 @@ describe('pnp/PNI DecryptionError unlink', function (this: Mocha.Suite) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamp: bootstrap.getTimestamp(),
|
timestamp: bootstrap.getTimestamp(),
|
||||||
updatedPni: toUntaggedPni(desktop.pni),
|
updatedPni: desktop.pni,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -73,7 +73,6 @@ describe('pnp/send gv2 invite', function (this: Mocha.Suite) {
|
||||||
identifier: uuidToBytes(MY_STORY_ID),
|
identifier: uuidToBytes(MY_STORY_ID),
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
name: MY_STORY_ID,
|
name: MY_STORY_ID,
|
||||||
recipientServiceIds: [],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -67,7 +67,6 @@ describe('pnp/username', function (this: Mocha.Suite) {
|
||||||
identifier: uuidToBytes(MY_STORY_ID),
|
identifier: uuidToBytes(MY_STORY_ID),
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
name: MY_STORY_ID,
|
name: MY_STORY_ID,
|
||||||
recipientServiceIds: [],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -146,10 +145,16 @@ describe('pnp/username', function (this: Mocha.Suite) {
|
||||||
'only one record must be removed'
|
'only one record must be removed'
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.strictEqual(added[0].contact?.aci, usernameContact.device.aci);
|
assert.deepEqual(
|
||||||
|
added[0].contact?.aciBinary,
|
||||||
|
usernameContact.device.aciRawUuid
|
||||||
|
);
|
||||||
assert.strictEqual(added[0].contact?.username, '');
|
assert.strictEqual(added[0].contact?.username, '');
|
||||||
|
|
||||||
assert.strictEqual(removed[0].contact?.aci, usernameContact.device.aci);
|
assert.deepEqual(
|
||||||
|
removed[0].contact?.aciBinary,
|
||||||
|
usernameContact.device.aciRawUuid
|
||||||
|
);
|
||||||
assert.strictEqual(removed[0].contact?.username, USERNAME);
|
assert.strictEqual(removed[0].contact?.username, USERNAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,6 @@ describe('story/no-sender-key', function (this: Mocha.Suite) {
|
||||||
identifier: uuidToBytes(MY_STORY_ID),
|
identifier: uuidToBytes(MY_STORY_ID),
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
name: MY_STORY_ID,
|
name: MY_STORY_ID,
|
||||||
recipientServiceIds: [],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,6 @@ import * as durations from '../../util/durations';
|
||||||
import { Bootstrap } from '../bootstrap';
|
import { Bootstrap } from '../bootstrap';
|
||||||
import type { App } from '../bootstrap';
|
import type { App } from '../bootstrap';
|
||||||
import { ReceiptType } from '../../types/Receipt';
|
import { ReceiptType } from '../../types/Receipt';
|
||||||
import { toUntaggedPni } from '../../types/ServiceId';
|
|
||||||
import {
|
import {
|
||||||
acceptConversation,
|
acceptConversation,
|
||||||
typeIntoInput,
|
typeIntoInput,
|
||||||
|
@ -57,7 +56,7 @@ describe('challenge/receipts', function (this: Mocha.Suite) {
|
||||||
whitelisted: true,
|
whitelisted: true,
|
||||||
e164: contact.device.number,
|
e164: contact.device.number,
|
||||||
identityKey: contact.getPublicKey(ServiceIdKind.PNI).serialize(),
|
identityKey: contact.getPublicKey(ServiceIdKind.PNI).serialize(),
|
||||||
pni: toUntaggedPni(contact.device.pni),
|
pniBinary: contact.device.pniRawUuid,
|
||||||
givenName: 'Jamie',
|
givenName: 'Jamie',
|
||||||
},
|
},
|
||||||
ServiceIdKind.PNI
|
ServiceIdKind.PNI
|
||||||
|
@ -68,7 +67,7 @@ describe('challenge/receipts', function (this: Mocha.Suite) {
|
||||||
whitelisted: true,
|
whitelisted: true,
|
||||||
e164: contactB.device.number,
|
e164: contactB.device.number,
|
||||||
identityKey: contactB.getPublicKey(ServiceIdKind.PNI).serialize(),
|
identityKey: contactB.getPublicKey(ServiceIdKind.PNI).serialize(),
|
||||||
pni: toUntaggedPni(contactB.device.pni),
|
pniBinary: contactB.device.pniRawUuid,
|
||||||
givenName: 'Kim',
|
givenName: 'Kim',
|
||||||
},
|
},
|
||||||
ServiceIdKind.PNI
|
ServiceIdKind.PNI
|
||||||
|
@ -111,10 +110,8 @@ describe('challenge/receipts', function (this: Mocha.Suite) {
|
||||||
const window = await app.getWindow();
|
const window = await app.getWindow();
|
||||||
const leftPane = window.locator('#LeftPane');
|
const leftPane = window.locator('#LeftPane');
|
||||||
|
|
||||||
debug(`Opening conversation with contact (${contact.toContact().aci})`);
|
debug(`Opening conversation with contact (${contact.device.aci})`);
|
||||||
await leftPane
|
await leftPane.locator(`[data-testid="${contact.device.aci}"]`).click();
|
||||||
.locator(`[data-testid="${contact.toContact().aci}"]`)
|
|
||||||
.click();
|
|
||||||
|
|
||||||
debug('Accept conversation from contact - does not trigger captcha!');
|
debug('Accept conversation from contact - does not trigger captcha!');
|
||||||
await acceptConversation(window);
|
await acceptConversation(window);
|
||||||
|
@ -172,10 +169,8 @@ describe('challenge/receipts', function (this: Mocha.Suite) {
|
||||||
timestamp: timestampA,
|
timestamp: timestampA,
|
||||||
});
|
});
|
||||||
|
|
||||||
debug(`Opening conversation with ContactA (${contact.toContact().aci})`);
|
debug(`Opening conversation with ContactA (${contact.device.aci})`);
|
||||||
await leftPane
|
await leftPane.locator(`[data-testid="${contact.device.aci}"]`).click();
|
||||||
.locator(`[data-testid="${contact.toContact().aci}"]`)
|
|
||||||
.click();
|
|
||||||
|
|
||||||
debug('Accept conversation from ContactA - does not trigger captcha!');
|
debug('Accept conversation from ContactA - does not trigger captcha!');
|
||||||
await acceptConversation(window);
|
await acceptConversation(window);
|
||||||
|
@ -186,10 +181,8 @@ describe('challenge/receipts', function (this: Mocha.Suite) {
|
||||||
timestamp: timestampB,
|
timestamp: timestampB,
|
||||||
});
|
});
|
||||||
|
|
||||||
debug(`Opening conversation with ContactB (${contact.toContact().aci})`);
|
debug(`Opening conversation with ContactB (${contact.device.aci})`);
|
||||||
await leftPane
|
await leftPane.locator(`[data-testid="${contactB.device.aci}"]`).click();
|
||||||
.locator(`[data-testid="${contactB.toContact().aci}"]`)
|
|
||||||
.click();
|
|
||||||
|
|
||||||
debug('Accept conversation from ContactB - does not trigger captcha!');
|
debug('Accept conversation from ContactB - does not trigger captcha!');
|
||||||
await acceptConversation(window);
|
await acceptConversation(window);
|
||||||
|
@ -273,10 +266,8 @@ describe('challenge/receipts', function (this: Mocha.Suite) {
|
||||||
const window = await app.getWindow();
|
const window = await app.getWindow();
|
||||||
const leftPane = window.locator('#LeftPane');
|
const leftPane = window.locator('#LeftPane');
|
||||||
|
|
||||||
debug(`Opening conversation with contact (${contact.toContact().aci})`);
|
debug(`Opening conversation with contact (${contact.device.aci})`);
|
||||||
await leftPane
|
await leftPane.locator(`[data-testid="${contact.device.aci}"]`).click();
|
||||||
.locator(`[data-testid="${contact.toContact().aci}"]`)
|
|
||||||
.click();
|
|
||||||
|
|
||||||
debug('Accept conversation from contact - does not trigger captcha!');
|
debug('Accept conversation from contact - does not trigger captcha!');
|
||||||
await acceptConversation(window);
|
await acceptConversation(window);
|
||||||
|
@ -342,10 +333,8 @@ describe('challenge/receipts', function (this: Mocha.Suite) {
|
||||||
timestamp,
|
timestamp,
|
||||||
});
|
});
|
||||||
|
|
||||||
debug(`Opening conversation with Contact B (${contactB.toContact().aci})`);
|
debug(`Opening conversation with Contact B (${contactB.device.aci})`);
|
||||||
await leftPane
|
await leftPane.locator(`[data-testid="${contactB.device.aci}"]`).click();
|
||||||
.locator(`[data-testid="${contactB.toContact().aci}"]`)
|
|
||||||
.click();
|
|
||||||
|
|
||||||
debug('Accept conversation from Contact B - does not trigger captcha!');
|
debug('Accept conversation from Contact B - does not trigger captcha!');
|
||||||
await acceptConversation(window);
|
await acceptConversation(window);
|
||||||
|
|
|
@ -57,7 +57,7 @@ describe('routing', function (this: Mocha.Suite) {
|
||||||
await page.locator('#LeftPane').waitFor();
|
await page.locator('#LeftPane').waitFor();
|
||||||
const token = await page.evaluate(
|
const token = await page.evaluate(
|
||||||
serviceId => window.SignalCI?.createNotificationToken(serviceId),
|
serviceId => window.SignalCI?.createNotificationToken(serviceId),
|
||||||
friend.toContact().aci
|
friend.device.aci
|
||||||
);
|
);
|
||||||
strictAssert(typeof token === 'string', 'token must be returned');
|
strictAssert(typeof token === 'string', 'token must be returned');
|
||||||
const conversationUrl = showConversationRoute.toAppUrl({
|
const conversationUrl = showConversationRoute.toAppUrl({
|
||||||
|
|
|
@ -49,7 +49,7 @@ describe('storage service', function (this: Mocha.Suite) {
|
||||||
});
|
});
|
||||||
|
|
||||||
await leftPane
|
await leftPane
|
||||||
.locator(`[data-testid="${firstContact.toContact().aci}"]`)
|
.locator(`[data-testid="${firstContact.device.aci}"]`)
|
||||||
.waitFor({ state: 'hidden' });
|
.waitFor({ state: 'hidden' });
|
||||||
|
|
||||||
await leftPane
|
await leftPane
|
||||||
|
@ -74,7 +74,7 @@ describe('storage service', function (this: Mocha.Suite) {
|
||||||
});
|
});
|
||||||
|
|
||||||
await leftPane
|
await leftPane
|
||||||
.locator(`[data-testid="${firstContact.toContact().aci}"]`)
|
.locator(`[data-testid="${firstContact.device.aci}"]`)
|
||||||
.waitFor();
|
.waitFor();
|
||||||
|
|
||||||
await leftPane
|
await leftPane
|
||||||
|
@ -89,7 +89,7 @@ describe('storage service', function (this: Mocha.Suite) {
|
||||||
const state = await phone.expectStorageState('consistency check');
|
const state = await phone.expectStorageState('consistency check');
|
||||||
|
|
||||||
await leftPane
|
await leftPane
|
||||||
.locator(`[data-testid="${firstContact.toContact().aci}"]`)
|
.locator(`[data-testid="${firstContact.device.aci}"]`)
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
const moreButton = conversationStack.locator(
|
const moreButton = conversationStack.locator(
|
||||||
|
|
|
@ -41,7 +41,6 @@ describe('storage service', function (this: Mocha.Suite) {
|
||||||
identifier: uuidToBytes(MY_STORY_ID),
|
identifier: uuidToBytes(MY_STORY_ID),
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
name: MY_STORY_ID,
|
name: MY_STORY_ID,
|
||||||
recipientServiceIds: [],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -101,7 +101,6 @@ export async function initStorage(
|
||||||
identifier: uuidToBytes(MY_STORY_ID),
|
identifier: uuidToBytes(MY_STORY_ID),
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
name: MY_STORY_ID,
|
name: MY_STORY_ID,
|
||||||
recipientServiceIds: [],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { Proto } from '@signalapp/mock-server';
|
||||||
|
|
||||||
import * as durations from '../../util/durations';
|
import * as durations from '../../util/durations';
|
||||||
import { generateAci } from '../../types/ServiceId';
|
import { generateAci } from '../../types/ServiceId';
|
||||||
|
import { toAciObject } from '../../util/ServiceId';
|
||||||
import { MAX_READ_KEYS } from '../../services/storageConstants';
|
import { MAX_READ_KEYS } from '../../services/storageConstants';
|
||||||
import type { App, Bootstrap } from './fixtures';
|
import type { App, Bootstrap } from './fixtures';
|
||||||
import { initStorage, debug } from './fixtures';
|
import { initStorage, debug } from './fixtures';
|
||||||
|
@ -45,7 +46,7 @@ describe('storage service', function (this: Mocha.Suite) {
|
||||||
|
|
||||||
debug('wait for first contact to be pinned in the left pane');
|
debug('wait for first contact to be pinned in the left pane');
|
||||||
await leftPane
|
await leftPane
|
||||||
.locator(`[data-testid="${firstContact.toContact().aci}"]`)
|
.locator(`[data-testid="${firstContact.device.aci}"]`)
|
||||||
.waitFor();
|
.waitFor();
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -57,7 +58,7 @@ describe('storage service', function (this: Mocha.Suite) {
|
||||||
type: IdentifierType.CONTACT,
|
type: IdentifierType.CONTACT,
|
||||||
record: {
|
record: {
|
||||||
contact: {
|
contact: {
|
||||||
aci: generateAci(),
|
aciBinary: toAciObject(generateAci()).getRawUuidBytes(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -76,7 +77,7 @@ describe('storage service', function (this: Mocha.Suite) {
|
||||||
|
|
||||||
debug('wait for last contact to be pinned in the left pane');
|
debug('wait for last contact to be pinned in the left pane');
|
||||||
await leftPane
|
await leftPane
|
||||||
.locator(`[data-testid="${lastContact.toContact().aci}"]`)
|
.locator(`[data-testid="${lastContact.device.aci}"]`)
|
||||||
.waitFor({ timeout: durations.MINUTE });
|
.waitFor({ timeout: durations.MINUTE });
|
||||||
|
|
||||||
debug('Verifying the final manifest version');
|
debug('Verifying the final manifest version');
|
||||||
|
|
|
@ -56,10 +56,8 @@ describe('storage service', function (this: Mocha.Suite) {
|
||||||
const leftPane = window.locator('#LeftPane');
|
const leftPane = window.locator('#LeftPane');
|
||||||
|
|
||||||
debug('Opening conversation with a stranger');
|
debug('Opening conversation with a stranger');
|
||||||
debug(stranger.toContact().aci);
|
debug(stranger.device.aci);
|
||||||
await leftPane
|
await leftPane.locator(`[data-testid="${stranger.device.aci}"]`).click();
|
||||||
.locator(`[data-testid="${stranger.toContact().aci}"]`)
|
|
||||||
.click();
|
|
||||||
|
|
||||||
debug("Verify that we stored stranger's profile key");
|
debug("Verify that we stored stranger's profile key");
|
||||||
const postMessageState = await phone.waitForStorageState({
|
const postMessageState = await phone.waitForStorageState({
|
||||||
|
|
|
@ -108,7 +108,7 @@ describe('storage service', function (this: Mocha.Suite) {
|
||||||
|
|
||||||
debug('pinning contact=%d', i);
|
debug('pinning contact=%d', i);
|
||||||
const convo = leftPane.locator(
|
const convo = leftPane.locator(
|
||||||
`[data-testid="${contact.toContact().aci}"]`
|
`[data-testid="${contact.device.aci}"]`
|
||||||
);
|
);
|
||||||
await convo.click();
|
await convo.click();
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ describe('storage service', function (this: Mocha.Suite) {
|
||||||
);
|
);
|
||||||
|
|
||||||
await leftPane
|
await leftPane
|
||||||
.locator(`[data-testid="${firstContact.toContact().aci}"]`)
|
.locator(`[data-testid="${firstContact.device.aci}"]`)
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
|
|
||||||
import { type WritableDB } from '../../sql/Interface';
|
import { type WritableDB } from '../../sql/Interface';
|
||||||
import { SignalService as Proto } from '../../protobuf';
|
import { Migrations as Proto } from '../../protobuf';
|
||||||
import { generateAci } from '../../types/ServiceId';
|
import { generateAci } from '../../types/ServiceId';
|
||||||
import { createDB, updateToVersion, insertData, getTableData } from './helpers';
|
import { createDB, updateToVersion, insertData, getTableData } from './helpers';
|
||||||
|
|
||||||
|
|
|
@ -3,16 +3,16 @@
|
||||||
|
|
||||||
import { Transform } from 'stream';
|
import { Transform } from 'stream';
|
||||||
|
|
||||||
|
import { createLogger } from '../logging/log';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
import protobuf from '../protobuf/wrap';
|
import protobuf from '../protobuf/wrap';
|
||||||
import { normalizeAci } from '../util/normalizeAci';
|
|
||||||
import { isAciString } from '../util/isAciString';
|
|
||||||
import { DurationInSeconds } from '../util/durations';
|
import { DurationInSeconds } from '../util/durations';
|
||||||
import { createLogger } from '../logging/log';
|
|
||||||
import type { ContactAvatarType } from '../types/Avatar';
|
import type { ContactAvatarType } from '../types/Avatar';
|
||||||
import type { AttachmentType } from '../types/Attachment';
|
import type { AttachmentType } from '../types/Attachment';
|
||||||
|
import type { AciString } from '../types/ServiceId';
|
||||||
import { computeHash } from '../Crypto';
|
import { computeHash } from '../Crypto';
|
||||||
import { dropNull } from '../util/dropNull';
|
import { dropNull } from '../util/dropNull';
|
||||||
|
import { fromAciUuidBytesOrString } from '../util/ServiceId';
|
||||||
import { decryptAttachmentV2ToSink } from '../AttachmentCrypto';
|
import { decryptAttachmentV2ToSink } from '../AttachmentCrypto';
|
||||||
|
|
||||||
import Avatar = Proto.ContactDetails.IAvatar;
|
import Avatar = Proto.ContactDetails.IAvatar;
|
||||||
|
@ -30,8 +30,9 @@ type OptionalFields = {
|
||||||
|
|
||||||
type MessageWithAvatar<Message extends OptionalFields> = Omit<
|
type MessageWithAvatar<Message extends OptionalFields> = Omit<
|
||||||
Message,
|
Message,
|
||||||
'avatar' | 'toJSON'
|
'avatar' | 'toJSON' | 'aci' | 'aciBinary'
|
||||||
> & {
|
> & {
|
||||||
|
aci: AciString;
|
||||||
avatar?: ContactAvatarType;
|
avatar?: ContactAvatarType;
|
||||||
expireTimer?: DurationInSeconds;
|
expireTimer?: DurationInSeconds;
|
||||||
expireTimerVersion: number | null;
|
expireTimerVersion: number | null;
|
||||||
|
@ -193,7 +194,7 @@ export class ParseContactsTransform extends Transform {
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareContact(
|
function prepareContact(
|
||||||
proto: Proto.ContactDetails,
|
{ aci: rawAci, aciBinary, ...proto }: Proto.ContactDetails,
|
||||||
avatar?: ContactAvatarType
|
avatar?: ContactAvatarType
|
||||||
): ContactDetailsWithAvatar | undefined {
|
): ContactDetailsWithAvatar | undefined {
|
||||||
const expireTimer =
|
const expireTimer =
|
||||||
|
@ -201,15 +202,13 @@ function prepareContact(
|
||||||
? DurationInSeconds.fromSeconds(proto.expireTimer)
|
? DurationInSeconds.fromSeconds(proto.expireTimer)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
// We reject incoming contacts with invalid aci information
|
const aci = fromAciUuidBytesOrString(aciBinary, rawAci, 'ContactBuffer.aci');
|
||||||
if (proto.aci && !isAciString(proto.aci)) {
|
|
||||||
log.warn('ParseContactsTransform: Dropping contact with invalid aci');
|
|
||||||
|
|
||||||
|
if (aci == null) {
|
||||||
|
log.warn('ParseContactsTransform: Dropping contact with invalid aci');
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const aci = proto.aci ? normalizeAci(proto.aci, 'ContactBuffer.aci') : null;
|
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
...proto,
|
...proto,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
|
|
|
@ -54,7 +54,7 @@ import { DurationInSeconds } from '../util/durations';
|
||||||
import { Address } from '../types/Address';
|
import { Address } from '../types/Address';
|
||||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||||
import { normalizeStoryDistributionId } from '../types/StoryDistributionId';
|
import { normalizeStoryDistributionId } from '../types/StoryDistributionId';
|
||||||
import type { ServiceIdString } from '../types/ServiceId';
|
import type { ServiceIdString, AciString } from '../types/ServiceId';
|
||||||
import {
|
import {
|
||||||
fromPniObject,
|
fromPniObject,
|
||||||
isPniString,
|
isPniString,
|
||||||
|
@ -159,6 +159,12 @@ import { checkOurPniIdentityKey } from '../util/checkOurPniIdentityKey';
|
||||||
import { CallLinkUpdateSyncType } from '../types/CallLink';
|
import { CallLinkUpdateSyncType } from '../types/CallLink';
|
||||||
import { bytesToUuid } from '../util/uuidToBytes';
|
import { bytesToUuid } from '../util/uuidToBytes';
|
||||||
import { isBodyTooLong } from '../util/longAttachment';
|
import { isBodyTooLong } from '../util/longAttachment';
|
||||||
|
import {
|
||||||
|
fromServiceIdBinaryOrString,
|
||||||
|
fromAciUuidBytes,
|
||||||
|
fromAciUuidBytesOrString,
|
||||||
|
fromPniUuidBytesOrUntaggedString,
|
||||||
|
} from '../util/ServiceId';
|
||||||
|
|
||||||
const log = createLogger('MessageReceiver');
|
const log = createLogger('MessageReceiver');
|
||||||
|
|
||||||
|
@ -415,27 +421,24 @@ export default class MessageReceiver
|
||||||
// Proto.Envelope fields
|
// Proto.Envelope fields
|
||||||
type: decoded.type ?? Proto.Envelope.Type.UNKNOWN,
|
type: decoded.type ?? Proto.Envelope.Type.UNKNOWN,
|
||||||
source: undefined,
|
source: undefined,
|
||||||
sourceServiceId: decoded.sourceServiceId
|
sourceServiceId: fromServiceIdBinaryOrString(
|
||||||
? normalizeServiceId(
|
decoded.sourceServiceIdBinary,
|
||||||
decoded.sourceServiceId,
|
decoded.sourceServiceId,
|
||||||
'MessageReceiver.handleRequest.sourceServiceId'
|
'MessageReceiver.handleRequest.sourceServiceId'
|
||||||
)
|
),
|
||||||
: undefined,
|
sourceDevice: decoded.sourceDeviceId ?? 1,
|
||||||
sourceDevice: decoded.sourceDevice ?? 1,
|
destinationServiceId:
|
||||||
destinationServiceId: decoded.destinationServiceId
|
fromServiceIdBinaryOrString(
|
||||||
? normalizeServiceId(
|
decoded.destinationServiceIdBinary,
|
||||||
decoded.destinationServiceId,
|
decoded.destinationServiceId,
|
||||||
'MessageReceiver.handleRequest.destinationServiceId'
|
'MessageReceiver.handleRequest.destinationServiceId'
|
||||||
)
|
) || ourAci,
|
||||||
: ourAci,
|
updatedPni: fromPniUuidBytesOrUntaggedString(
|
||||||
updatedPni:
|
decoded.updatedPniBinary,
|
||||||
decoded.updatedPni && isUntaggedPniString(decoded.updatedPni)
|
decoded.updatedPni,
|
||||||
? normalizePni(
|
'MessageReceiver.handleRequest.updatedPni'
|
||||||
toTaggedPni(decoded.updatedPni),
|
),
|
||||||
'MessageReceiver.handleRequest.updatedPni'
|
timestamp: decoded.clientTimestamp?.toNumber() ?? 0,
|
||||||
)
|
|
||||||
: undefined,
|
|
||||||
timestamp: decoded.timestamp?.toNumber() ?? 0,
|
|
||||||
content,
|
content,
|
||||||
serverGuid: decoded.serverGuid ?? getGuid(),
|
serverGuid: decoded.serverGuid ?? getGuid(),
|
||||||
serverTimestamp,
|
serverTimestamp,
|
||||||
|
@ -1828,7 +1831,7 @@ export default class MessageReceiver
|
||||||
|
|
||||||
if (
|
if (
|
||||||
serviceIdKind === ServiceIdKind.PNI &&
|
serviceIdKind === ServiceIdKind.PNI &&
|
||||||
envelope.type !== envelopeTypeEnum.PREKEY_BUNDLE
|
envelope.type !== envelopeTypeEnum.PREKEY_MESSAGE
|
||||||
) {
|
) {
|
||||||
log.warn(`innerDecrypt(${logId}): non-PreKey envelope on PNI`);
|
log.warn(`innerDecrypt(${logId}): non-PreKey envelope on PNI`);
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -1850,7 +1853,7 @@ export default class MessageReceiver
|
||||||
wasEncrypted: false,
|
wasEncrypted: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (envelope.type === envelopeTypeEnum.CIPHERTEXT) {
|
if (envelope.type === envelopeTypeEnum.DOUBLE_RATCHET) {
|
||||||
log.info(`decrypt/${logId}: ciphertext message`);
|
log.info(`decrypt/${logId}: ciphertext message`);
|
||||||
if (!identifier) {
|
if (!identifier) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -1879,7 +1882,7 @@ export default class MessageReceiver
|
||||||
);
|
);
|
||||||
return { plaintext, wasEncrypted: true };
|
return { plaintext, wasEncrypted: true };
|
||||||
}
|
}
|
||||||
if (envelope.type === envelopeTypeEnum.PREKEY_BUNDLE) {
|
if (envelope.type === envelopeTypeEnum.PREKEY_MESSAGE) {
|
||||||
log.info(`decrypt/${logId}: prekey message`);
|
log.info(`decrypt/${logId}: prekey message`);
|
||||||
if (!identifier) {
|
if (!identifier) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -3180,9 +3183,11 @@ export default class MessageReceiver
|
||||||
|
|
||||||
const ev = new ViewOnceOpenSyncEvent(
|
const ev = new ViewOnceOpenSyncEvent(
|
||||||
{
|
{
|
||||||
sourceAci: sync.senderAci
|
sourceAci: fromAciUuidBytesOrString(
|
||||||
? normalizeAci(sync.senderAci, 'handleViewOnceOpen.senderUuid')
|
sync.senderAciBinary,
|
||||||
: undefined,
|
sync.senderAci,
|
||||||
|
'handleViewOnceOpen.senderUuid'
|
||||||
|
),
|
||||||
timestamp: sync.timestamp?.toNumber(),
|
timestamp: sync.timestamp?.toNumber(),
|
||||||
},
|
},
|
||||||
this.#removeFromCache.bind(this, envelope)
|
this.#removeFromCache.bind(this, envelope)
|
||||||
|
@ -3216,12 +3221,11 @@ export default class MessageReceiver
|
||||||
const ev = new MessageRequestResponseEvent(
|
const ev = new MessageRequestResponseEvent(
|
||||||
{
|
{
|
||||||
envelopeId: envelope.id,
|
envelopeId: envelope.id,
|
||||||
threadAci: sync.threadAci
|
threadAci: fromAciUuidBytesOrString(
|
||||||
? normalizeAci(
|
sync.threadAciBinary,
|
||||||
sync.threadAci,
|
sync.threadAci,
|
||||||
'handleMessageRequestResponse.threadUuid'
|
'handleMessageRequestResponse.threadUuid'
|
||||||
)
|
),
|
||||||
: undefined,
|
|
||||||
messageRequestResponseType: sync.type,
|
messageRequestResponseType: sync.type,
|
||||||
groupV2Id: groupV2IdString,
|
groupV2Id: groupV2IdString,
|
||||||
},
|
},
|
||||||
|
@ -3357,16 +3361,20 @@ export default class MessageReceiver
|
||||||
|
|
||||||
logUnexpectedUrgentValue(envelope, 'readSync');
|
logUnexpectedUrgentValue(envelope, 'readSync');
|
||||||
|
|
||||||
const reads = read.map(
|
const reads = read.map((data): ReadSyncEventData => {
|
||||||
({ timestamp, senderAci }): ReadSyncEventData => ({
|
const { timestamp, senderAci: rawSenderAci, senderAciBinary } = data;
|
||||||
|
|
||||||
|
return {
|
||||||
envelopeId: envelope.id,
|
envelopeId: envelope.id,
|
||||||
envelopeTimestamp: envelope.timestamp,
|
envelopeTimestamp: envelope.timestamp,
|
||||||
timestamp: timestamp?.toNumber(),
|
timestamp: timestamp?.toNumber(),
|
||||||
senderAci: senderAci
|
senderAci: fromAciUuidBytesOrString(
|
||||||
? normalizeAci(senderAci, 'handleRead.senderAci')
|
senderAciBinary,
|
||||||
: undefined,
|
rawSenderAci,
|
||||||
})
|
'handleRead.senderAci'
|
||||||
);
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
await this.#dispatchAndWait(
|
await this.#dispatchAndWait(
|
||||||
logId,
|
logId,
|
||||||
|
@ -3388,14 +3396,18 @@ export default class MessageReceiver
|
||||||
|
|
||||||
logUnexpectedUrgentValue(envelope, 'viewSync');
|
logUnexpectedUrgentValue(envelope, 'viewSync');
|
||||||
|
|
||||||
const views = viewed.map(
|
const views = viewed.map((data): ViewSyncEventData => {
|
||||||
({ timestamp, senderAci }): ViewSyncEventData => ({
|
const { timestamp, senderAci: rawSenderAci, senderAciBinary } = data;
|
||||||
|
|
||||||
|
return {
|
||||||
timestamp: timestamp?.toNumber(),
|
timestamp: timestamp?.toNumber(),
|
||||||
senderAci: senderAci
|
senderAci: fromAciUuidBytesOrString(
|
||||||
? normalizeAci(senderAci, 'handleViewed.senderAci')
|
senderAciBinary,
|
||||||
: undefined,
|
rawSenderAci,
|
||||||
})
|
'handleViewed.senderAci'
|
||||||
);
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
await this.#dispatchAndWait(
|
await this.#dispatchAndWait(
|
||||||
logId,
|
logId,
|
||||||
|
@ -3879,21 +3891,40 @@ export default class MessageReceiver
|
||||||
log.info(`${logId}: New e164 unblocks:`, removed);
|
log.info(`${logId}: New e164 unblocks:`, removed);
|
||||||
await this.#storage.put('blocked', blocked.numbers);
|
await this.#storage.put('blocked', blocked.numbers);
|
||||||
}
|
}
|
||||||
if (blocked.acis) {
|
if (blocked.acisBinary?.length || blocked.acis?.length) {
|
||||||
const previous = this.#storage.get('blocked-uuids', []);
|
const previous = this.#storage.get('blocked-uuids', []);
|
||||||
const acis = blocked.acis
|
let acis: Array<AciString>;
|
||||||
.map((aci, index) => {
|
if (blocked.acisBinary?.length) {
|
||||||
try {
|
acis = blocked.acisBinary
|
||||||
return normalizeAci(aci, `handleBlocked.acis.${index}`);
|
.map((aciBinary, index) => {
|
||||||
} catch (error) {
|
try {
|
||||||
log.warn(
|
return fromAciUuidBytes(aciBinary);
|
||||||
`${logId}: ACI ${aci} was malformed`,
|
} catch (error) {
|
||||||
Errors.toLogFormat(error)
|
log.warn(
|
||||||
);
|
`${logId}: ACI ${index} was malformed`,
|
||||||
return undefined;
|
Errors.toLogFormat(error)
|
||||||
}
|
);
|
||||||
})
|
return undefined;
|
||||||
.filter(isNotNil);
|
}
|
||||||
|
})
|
||||||
|
.filter(isNotNil);
|
||||||
|
} else if (blocked.acis?.length) {
|
||||||
|
acis = blocked.acis
|
||||||
|
.map((aci, index) => {
|
||||||
|
try {
|
||||||
|
return normalizeAci(aci, `handleBlocked.acis.${index}`);
|
||||||
|
} catch (error) {
|
||||||
|
log.warn(
|
||||||
|
`${logId}: ACI ${aci} was malformed`,
|
||||||
|
Errors.toLogFormat(error)
|
||||||
|
);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(isNotNil);
|
||||||
|
} else {
|
||||||
|
throw new Error('No blocked acis');
|
||||||
|
}
|
||||||
|
|
||||||
const { added, removed } = diffArraysAsSets(previous, acis);
|
const { added, removed } = diffArraysAsSets(previous, acis);
|
||||||
if (added.length) {
|
if (added.length) {
|
||||||
|
@ -4004,13 +4035,13 @@ export default class MessageReceiver
|
||||||
function envelopeTypeToCiphertextType(type: number | undefined): number {
|
function envelopeTypeToCiphertextType(type: number | undefined): number {
|
||||||
const { Type } = Proto.Envelope;
|
const { Type } = Proto.Envelope;
|
||||||
|
|
||||||
if (type === Type.CIPHERTEXT) {
|
if (type === Type.DOUBLE_RATCHET) {
|
||||||
return CiphertextMessageType.Whisper;
|
return CiphertextMessageType.Whisper;
|
||||||
}
|
}
|
||||||
if (type === Type.PLAINTEXT_CONTENT) {
|
if (type === Type.PLAINTEXT_CONTENT) {
|
||||||
return CiphertextMessageType.Plaintext;
|
return CiphertextMessageType.Plaintext;
|
||||||
}
|
}
|
||||||
if (type === Type.PREKEY_BUNDLE) {
|
if (type === Type.PREKEY_MESSAGE) {
|
||||||
return CiphertextMessageType.PreKey;
|
return CiphertextMessageType.PreKey;
|
||||||
}
|
}
|
||||||
if (type === Type.SERVER_DELIVERY_RECEIPT) {
|
if (type === Type.SERVER_DELIVERY_RECEIPT) {
|
||||||
|
@ -4042,25 +4073,26 @@ function processAddressableMessage(
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { authorServiceId } = target;
|
const { authorServiceId: rawAuthorServiceId, authorServiceIdBinary } = target;
|
||||||
|
|
||||||
|
const authorServiceId = fromServiceIdBinaryOrString(
|
||||||
|
authorServiceIdBinary,
|
||||||
|
rawAuthorServiceId,
|
||||||
|
logId
|
||||||
|
);
|
||||||
|
|
||||||
if (authorServiceId) {
|
if (authorServiceId) {
|
||||||
if (isAciString(authorServiceId)) {
|
if (isAciString(authorServiceId)) {
|
||||||
return {
|
return {
|
||||||
type: 'aci' as const,
|
type: 'aci' as const,
|
||||||
authorAci: normalizeAci(
|
authorAci: authorServiceId,
|
||||||
authorServiceId,
|
|
||||||
`${logId}/processAddressableMessage/aci`
|
|
||||||
),
|
|
||||||
sentAt,
|
sentAt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (isPniString(authorServiceId)) {
|
if (isPniString(authorServiceId)) {
|
||||||
return {
|
return {
|
||||||
type: 'pni' as const,
|
type: 'pni' as const,
|
||||||
authorPni: normalizePni(
|
authorPni: authorServiceId,
|
||||||
authorServiceId,
|
|
||||||
`${logId}/processAddressableMessage/pni`
|
|
||||||
),
|
|
||||||
sentAt,
|
sentAt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -4087,19 +4119,30 @@ function processConversationIdentifier(
|
||||||
target: Proto.IConversationIdentifier,
|
target: Proto.IConversationIdentifier,
|
||||||
logId: string
|
logId: string
|
||||||
): ConversationIdentifier | undefined {
|
): ConversationIdentifier | undefined {
|
||||||
const { threadServiceId, threadGroupId, threadE164 } = target;
|
const {
|
||||||
|
threadServiceId: rawThreadServiceId,
|
||||||
|
threadServiceIdBinary,
|
||||||
|
threadGroupId,
|
||||||
|
threadE164,
|
||||||
|
} = target;
|
||||||
|
|
||||||
|
const threadServiceId = fromServiceIdBinaryOrString(
|
||||||
|
threadServiceIdBinary,
|
||||||
|
rawThreadServiceId,
|
||||||
|
logId
|
||||||
|
);
|
||||||
|
|
||||||
if (threadServiceId) {
|
if (threadServiceId) {
|
||||||
if (isAciString(threadServiceId)) {
|
if (isAciString(threadServiceId)) {
|
||||||
return {
|
return {
|
||||||
type: 'aci' as const,
|
type: 'aci' as const,
|
||||||
aci: normalizeAci(threadServiceId, `${logId}/aci`),
|
aci: threadServiceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (isPniString(threadServiceId)) {
|
if (isPniString(threadServiceId)) {
|
||||||
return {
|
return {
|
||||||
type: 'pni' as const,
|
type: 'pni' as const,
|
||||||
pni: normalizePni(threadServiceId, `${logId}/pni`),
|
pni: threadServiceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
log.error(
|
log.error(
|
||||||
|
|
|
@ -72,10 +72,10 @@ type OutgoingMessageOptionsType = SendOptionsType & {
|
||||||
|
|
||||||
function ciphertextMessageTypeToEnvelopeType(type: number) {
|
function ciphertextMessageTypeToEnvelopeType(type: number) {
|
||||||
if (type === CiphertextMessageType.PreKey) {
|
if (type === CiphertextMessageType.PreKey) {
|
||||||
return Proto.Envelope.Type.PREKEY_BUNDLE;
|
return Proto.Envelope.Type.PREKEY_MESSAGE;
|
||||||
}
|
}
|
||||||
if (type === CiphertextMessageType.Whisper) {
|
if (type === CiphertextMessageType.Whisper) {
|
||||||
return Proto.Envelope.Type.CIPHERTEXT;
|
return Proto.Envelope.Type.DOUBLE_RATCHET;
|
||||||
}
|
}
|
||||||
if (type === CiphertextMessageType.Plaintext) {
|
if (type === CiphertextMessageType.Plaintext) {
|
||||||
return Proto.Envelope.Type.PLAINTEXT_CONTENT;
|
return Proto.Envelope.Type.PLAINTEXT_CONTENT;
|
||||||
|
|
|
@ -6,18 +6,12 @@ import pTimeout, { TimeoutError as PTimeoutError } from 'p-timeout';
|
||||||
import { createLogger } from '../logging/log';
|
import { createLogger } from '../logging/log';
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
import { MAX_DEVICE_NAME_LENGTH } from '../types/InstallScreen';
|
import { MAX_DEVICE_NAME_LENGTH } from '../types/InstallScreen';
|
||||||
import {
|
|
||||||
isUntaggedPniString,
|
|
||||||
normalizePni,
|
|
||||||
toTaggedPni,
|
|
||||||
} from '../types/ServiceId';
|
|
||||||
import { strictAssert } from '../util/assert';
|
import { strictAssert } from '../util/assert';
|
||||||
import { BackOff, FIBONACCI_TIMEOUTS } from '../util/BackOff';
|
import { BackOff, FIBONACCI_TIMEOUTS } from '../util/BackOff';
|
||||||
import { SECOND } from '../util/durations';
|
import { SECOND } from '../util/durations';
|
||||||
import { explodePromise } from '../util/explodePromise';
|
import { explodePromise } from '../util/explodePromise';
|
||||||
import { drop } from '../util/drop';
|
import { drop } from '../util/drop';
|
||||||
import { isLinkAndSyncEnabled } from '../util/isLinkAndSyncEnabled';
|
import { isLinkAndSyncEnabled } from '../util/isLinkAndSyncEnabled';
|
||||||
import { normalizeAci } from '../util/normalizeAci';
|
|
||||||
import { normalizeDeviceName } from '../util/normalizeDeviceName';
|
import { normalizeDeviceName } from '../util/normalizeDeviceName';
|
||||||
import { linkDeviceRoute } from '../util/signalRoutes';
|
import { linkDeviceRoute } from '../util/signalRoutes';
|
||||||
import { sleep } from '../util/sleep';
|
import { sleep } from '../util/sleep';
|
||||||
|
@ -156,10 +150,10 @@ export class Provisioner {
|
||||||
provisioningCode,
|
provisioningCode,
|
||||||
aciKeyPair,
|
aciKeyPair,
|
||||||
pniKeyPair,
|
pniKeyPair,
|
||||||
aci,
|
aci: ourAci,
|
||||||
profileKey,
|
profileKey,
|
||||||
masterKey,
|
masterKey,
|
||||||
untaggedPni,
|
pni: ourPni,
|
||||||
userAgent,
|
userAgent,
|
||||||
readReceipts,
|
readReceipts,
|
||||||
ephemeralBackupKey,
|
ephemeralBackupKey,
|
||||||
|
@ -171,7 +165,6 @@ export class Provisioner {
|
||||||
strictAssert(provisioningCode, 'prepareLinkData: missing provisioningCode');
|
strictAssert(provisioningCode, 'prepareLinkData: missing provisioningCode');
|
||||||
strictAssert(aciKeyPair, 'prepareLinkData: missing aciKeyPair');
|
strictAssert(aciKeyPair, 'prepareLinkData: missing aciKeyPair');
|
||||||
strictAssert(pniKeyPair, 'prepareLinkData: missing pniKeyPair');
|
strictAssert(pniKeyPair, 'prepareLinkData: missing pniKeyPair');
|
||||||
strictAssert(aci, 'prepareLinkData: missing aci');
|
|
||||||
strictAssert(
|
strictAssert(
|
||||||
Bytes.isNotEmpty(profileKey),
|
Bytes.isNotEmpty(profileKey),
|
||||||
'prepareLinkData: missing profileKey'
|
'prepareLinkData: missing profileKey'
|
||||||
|
@ -180,16 +173,6 @@ export class Provisioner {
|
||||||
Bytes.isNotEmpty(masterKey) || accountEntropyPool,
|
Bytes.isNotEmpty(masterKey) || accountEntropyPool,
|
||||||
'prepareLinkData: missing masterKey or accountEntropyPool'
|
'prepareLinkData: missing masterKey or accountEntropyPool'
|
||||||
);
|
);
|
||||||
strictAssert(
|
|
||||||
isUntaggedPniString(untaggedPni),
|
|
||||||
'prepareLinkData: invalid untaggedPni'
|
|
||||||
);
|
|
||||||
|
|
||||||
const ourAci = normalizeAci(aci, 'provisionMessage.aci');
|
|
||||||
const ourPni = normalizePni(
|
|
||||||
toTaggedPni(untaggedPni),
|
|
||||||
'provisionMessage.pni'
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: AccountType.Linked,
|
type: AccountType.Linked,
|
||||||
|
@ -363,11 +346,14 @@ export class Provisioner {
|
||||||
'Provisioner.connect: duplicate uuid'
|
'Provisioner.connect: duplicate uuid'
|
||||||
);
|
);
|
||||||
|
|
||||||
const proto = Proto.ProvisioningUuid.decode(body);
|
const proto = Proto.ProvisioningAddress.decode(body);
|
||||||
strictAssert(proto.uuid, 'Provisioner.connect: expected a UUID');
|
strictAssert(
|
||||||
|
proto.address,
|
||||||
|
'Provisioner.connect: expected a UUID'
|
||||||
|
);
|
||||||
|
|
||||||
state = SocketState.WaitingForEnvelope;
|
state = SocketState.WaitingForEnvelope;
|
||||||
uuidPromise.resolve(proto.uuid);
|
uuidPromise.resolve(proto.address);
|
||||||
request.respond(200, 'OK');
|
request.respond(200, 'OK');
|
||||||
} else if (requestType === ServerRequestType.ProvisioningMessage) {
|
} else if (requestType === ServerRequestType.ProvisioningMessage) {
|
||||||
strictAssert(
|
strictAssert(
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
/* eslint-disable max-classes-per-file */
|
/* eslint-disable max-classes-per-file */
|
||||||
|
|
||||||
import * as client from '@signalapp/libsignal-client';
|
import { PublicKey, Aci, Pni } from '@signalapp/libsignal-client';
|
||||||
import type { KeyPairType } from './Types.d';
|
import type { KeyPairType } from './Types.d';
|
||||||
import * as Bytes from '../Bytes';
|
import * as Bytes from '../Bytes';
|
||||||
import {
|
import {
|
||||||
|
@ -15,13 +15,23 @@ import { calculateAgreement, createKeyPair, generateKeyPair } from '../Curve';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
import { strictAssert } from '../util/assert';
|
import { strictAssert } from '../util/assert';
|
||||||
import { dropNull } from '../util/dropNull';
|
import { dropNull } from '../util/dropNull';
|
||||||
|
import { normalizeAci } from '../util/normalizeAci';
|
||||||
|
import {
|
||||||
|
type AciString,
|
||||||
|
type PniString,
|
||||||
|
normalizePni,
|
||||||
|
toTaggedPni,
|
||||||
|
isUntaggedPniString,
|
||||||
|
fromAciObject,
|
||||||
|
fromPniObject,
|
||||||
|
} from '../types/ServiceId';
|
||||||
|
|
||||||
export type ProvisionDecryptResult = Readonly<{
|
export type ProvisionDecryptResult = Readonly<{
|
||||||
aciKeyPair: KeyPairType;
|
aciKeyPair: KeyPairType;
|
||||||
pniKeyPair?: KeyPairType;
|
pniKeyPair?: KeyPairType;
|
||||||
number?: string;
|
number?: string;
|
||||||
aci?: string;
|
aci: AciString;
|
||||||
untaggedPni?: string;
|
pni: PniString;
|
||||||
provisioningCode?: string;
|
provisioningCode?: string;
|
||||||
userAgent?: string;
|
userAgent?: string;
|
||||||
readReceipts?: boolean;
|
readReceipts?: boolean;
|
||||||
|
@ -57,7 +67,7 @@ class ProvisioningCipherInner {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ecRes = calculateAgreement(
|
const ecRes = calculateAgreement(
|
||||||
client.PublicKey.deserialize(Buffer.from(masterEphemeral)),
|
PublicKey.deserialize(Buffer.from(masterEphemeral)),
|
||||||
this.keyPair.privateKey
|
this.keyPair.privateKey
|
||||||
);
|
);
|
||||||
const keys = deriveSecrets(
|
const keys = deriveSecrets(
|
||||||
|
@ -78,16 +88,36 @@ class ProvisioningCipherInner {
|
||||||
? createKeyPair(pniPrivKey)
|
? createKeyPair(pniPrivKey)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const { aci, pni } = provisionMessage;
|
const {
|
||||||
strictAssert(aci, 'Missing aci in provisioning message');
|
aci: rawAci,
|
||||||
strictAssert(pni, 'Missing pni in provisioning message');
|
pni: rawUntaggedPni,
|
||||||
|
aciBinary,
|
||||||
|
pniBinary,
|
||||||
|
} = provisionMessage;
|
||||||
|
|
||||||
|
let aci: AciString;
|
||||||
|
let pni: PniString;
|
||||||
|
if (Bytes.isNotEmpty(aciBinary) && Bytes.isNotEmpty(pniBinary)) {
|
||||||
|
aci = fromAciObject(Aci.fromUuidBytes(aciBinary));
|
||||||
|
pni = fromPniObject(Pni.fromUuidBytes(pniBinary));
|
||||||
|
} else if (rawAci && rawUntaggedPni) {
|
||||||
|
strictAssert(
|
||||||
|
isUntaggedPniString(rawUntaggedPni),
|
||||||
|
'ProvisioningCipher: invalid untaggedPni'
|
||||||
|
);
|
||||||
|
|
||||||
|
aci = normalizeAci(rawAci, 'provisionMessage.aci');
|
||||||
|
pni = normalizePni(toTaggedPni(rawUntaggedPni), 'provisionMessage.pni');
|
||||||
|
} else {
|
||||||
|
throw new Error('Missing aci/pni in provisioning message');
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
aciKeyPair,
|
aciKeyPair,
|
||||||
pniKeyPair,
|
pniKeyPair,
|
||||||
number: dropNull(provisionMessage.number),
|
number: dropNull(provisionMessage.number),
|
||||||
aci,
|
aci,
|
||||||
untaggedPni: pni,
|
pni,
|
||||||
provisioningCode: dropNull(provisionMessage.provisioningCode),
|
provisioningCode: dropNull(provisionMessage.provisioningCode),
|
||||||
userAgent: dropNull(provisionMessage.userAgent),
|
userAgent: dropNull(provisionMessage.userAgent),
|
||||||
readReceipts: provisionMessage.readReceipts ?? false,
|
readReceipts: provisionMessage.readReceipts ?? false,
|
||||||
|
@ -107,7 +137,7 @@ class ProvisioningCipherInner {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getPublicKey(): client.PublicKey {
|
getPublicKey(): PublicKey {
|
||||||
if (!this.keyPair) {
|
if (!this.keyPair) {
|
||||||
this.keyPair = generateKeyPair();
|
this.keyPair = generateKeyPair();
|
||||||
}
|
}
|
||||||
|
@ -132,5 +162,5 @@ export default class ProvisioningCipher {
|
||||||
provisionEnvelope: Proto.ProvisionEnvelope
|
provisionEnvelope: Proto.ProvisionEnvelope
|
||||||
) => ProvisionDecryptResult;
|
) => ProvisionDecryptResult;
|
||||||
|
|
||||||
getPublicKey: () => client.PublicKey;
|
getPublicKey: () => PublicKey;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import PQueue from 'p-queue';
|
||||||
import pMap from 'p-map';
|
import pMap from 'p-map';
|
||||||
import type { PlaintextContent } from '@signalapp/libsignal-client';
|
import type { PlaintextContent } from '@signalapp/libsignal-client';
|
||||||
import {
|
import {
|
||||||
Pni,
|
|
||||||
ProtocolAddress,
|
ProtocolAddress,
|
||||||
SenderKeyDistributionMessage,
|
SenderKeyDistributionMessage,
|
||||||
} from '@signalapp/libsignal-client';
|
} from '@signalapp/libsignal-client';
|
||||||
|
@ -33,6 +32,7 @@ import {
|
||||||
serviceIdSchema,
|
serviceIdSchema,
|
||||||
isPniString,
|
isPniString,
|
||||||
} from '../types/ServiceId';
|
} from '../types/ServiceId';
|
||||||
|
import { toAciObject, toPniObject, toServiceIdObject } from '../util/ServiceId';
|
||||||
import type {
|
import type {
|
||||||
ChallengeType,
|
ChallengeType,
|
||||||
GetGroupLogOptionsType,
|
GetGroupLogOptionsType,
|
||||||
|
@ -100,6 +100,7 @@ import {
|
||||||
getProtoForCallHistory,
|
getProtoForCallHistory,
|
||||||
} from '../util/callDisposition';
|
} from '../util/callDisposition';
|
||||||
import { MAX_MESSAGE_COUNT } from '../util/deleteForMe.types';
|
import { MAX_MESSAGE_COUNT } from '../util/deleteForMe.types';
|
||||||
|
import { isProtoBinaryEncodingEnabled } from '../util/isProtoBinaryEncodingEnabled';
|
||||||
import type { GroupSendToken } from '../types/GroupSendEndorsements';
|
import type { GroupSendToken } from '../types/GroupSendEndorsements';
|
||||||
|
|
||||||
const log = createLogger('SendMessage');
|
const log = createLogger('SendMessage');
|
||||||
|
@ -415,6 +416,11 @@ class Message {
|
||||||
proto.reaction.emoji = this.reaction.emoji || null;
|
proto.reaction.emoji = this.reaction.emoji || null;
|
||||||
proto.reaction.remove = this.reaction.remove || false;
|
proto.reaction.remove = this.reaction.remove || false;
|
||||||
proto.reaction.targetAuthorAci = this.reaction.targetAuthorAci || null;
|
proto.reaction.targetAuthorAci = this.reaction.targetAuthorAci || null;
|
||||||
|
if (isProtoBinaryEncodingEnabled()) {
|
||||||
|
proto.reaction.targetAuthorAciBinary = this.reaction.targetAuthorAci
|
||||||
|
? toAciObject(this.reaction.targetAuthorAci).getRawUuidBytes()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
proto.reaction.targetSentTimestamp =
|
proto.reaction.targetSentTimestamp =
|
||||||
this.reaction.targetTimestamp === undefined
|
this.reaction.targetTimestamp === undefined
|
||||||
? null
|
? null
|
||||||
|
@ -520,6 +526,11 @@ class Message {
|
||||||
quote.id =
|
quote.id =
|
||||||
this.quote.id === undefined ? null : Long.fromNumber(this.quote.id);
|
this.quote.id === undefined ? null : Long.fromNumber(this.quote.id);
|
||||||
quote.authorAci = this.quote.authorAci || null;
|
quote.authorAci = this.quote.authorAci || null;
|
||||||
|
if (isProtoBinaryEncodingEnabled()) {
|
||||||
|
quote.authorAciBinary = this.quote.authorAci
|
||||||
|
? toAciObject(this.quote.authorAci).getRawUuidBytes()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
quote.text = this.quote.text || null;
|
quote.text = this.quote.text || null;
|
||||||
quote.attachments = this.quote.attachments.slice() || [];
|
quote.attachments = this.quote.attachments.slice() || [];
|
||||||
const bodyRanges = this.quote.bodyRanges || [];
|
const bodyRanges = this.quote.bodyRanges || [];
|
||||||
|
@ -529,6 +540,11 @@ class Message {
|
||||||
bodyRange.length = range.length;
|
bodyRange.length = range.length;
|
||||||
if (BodyRange.isMention(range)) {
|
if (BodyRange.isMention(range)) {
|
||||||
bodyRange.mentionAci = range.mentionAci;
|
bodyRange.mentionAci = range.mentionAci;
|
||||||
|
if (isProtoBinaryEncodingEnabled()) {
|
||||||
|
bodyRange.mentionAciBinary = toAciObject(
|
||||||
|
range.mentionAci
|
||||||
|
).getRawUuidBytes();
|
||||||
|
}
|
||||||
} else if (BodyRange.isFormatting(range)) {
|
} else if (BodyRange.isFormatting(range)) {
|
||||||
bodyRange.style = range.style;
|
bodyRange.style = range.style;
|
||||||
} else {
|
} else {
|
||||||
|
@ -599,6 +615,11 @@ class Message {
|
||||||
const storyContext = new StoryContext();
|
const storyContext = new StoryContext();
|
||||||
if (this.storyContext.authorAci) {
|
if (this.storyContext.authorAci) {
|
||||||
storyContext.authorAci = this.storyContext.authorAci;
|
storyContext.authorAci = this.storyContext.authorAci;
|
||||||
|
if (isProtoBinaryEncodingEnabled()) {
|
||||||
|
storyContext.authorAciBinary = toAciObject(
|
||||||
|
this.storyContext.authorAci
|
||||||
|
).getRawUuidBytes();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
storyContext.sentTimestamp = Long.fromNumber(this.storyContext.timestamp);
|
storyContext.sentTimestamp = Long.fromNumber(this.storyContext.timestamp);
|
||||||
|
|
||||||
|
@ -637,9 +658,7 @@ function addPniSignatureMessageToProto({
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
proto.pniSignatureMessage = {
|
proto.pniSignatureMessage = {
|
||||||
pni: Pni.parseFromServiceIdString(
|
pni: toPniObject(pniSignatureMessage.pni).getRawUuidBytes(),
|
||||||
pniSignatureMessage.pni
|
|
||||||
).getRawUuidBytes(),
|
|
||||||
signature: pniSignatureMessage.signature,
|
signature: pniSignatureMessage.signature,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1300,6 +1319,10 @@ export default class MessageSender {
|
||||||
}
|
}
|
||||||
if (destinationServiceId) {
|
if (destinationServiceId) {
|
||||||
sentMessage.destinationServiceId = destinationServiceId;
|
sentMessage.destinationServiceId = destinationServiceId;
|
||||||
|
if (isProtoBinaryEncodingEnabled()) {
|
||||||
|
sentMessage.destinationServiceIdBinary =
|
||||||
|
toServiceIdObject(destinationServiceId).getServiceIdBinary();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (expirationStartTimestamp) {
|
if (expirationStartTimestamp) {
|
||||||
sentMessage.expirationStartTimestamp = Long.fromNumber(
|
sentMessage.expirationStartTimestamp = Long.fromNumber(
|
||||||
|
@ -1330,6 +1353,10 @@ export default class MessageSender {
|
||||||
const serviceId = conv.getServiceId();
|
const serviceId = conv.getServiceId();
|
||||||
if (serviceId) {
|
if (serviceId) {
|
||||||
status.destinationServiceId = serviceId;
|
status.destinationServiceId = serviceId;
|
||||||
|
if (isProtoBinaryEncodingEnabled()) {
|
||||||
|
status.destinationServiceIdBinary =
|
||||||
|
toServiceIdObject(serviceId).getServiceIdBinary();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isPniString(serviceId)) {
|
if (isPniString(serviceId)) {
|
||||||
const pniIdentityKey =
|
const pniIdentityKey =
|
||||||
|
@ -1801,7 +1828,11 @@ export default class MessageSender {
|
||||||
const syncMessage = MessageSender.createSyncMessage();
|
const syncMessage = MessageSender.createSyncMessage();
|
||||||
|
|
||||||
const viewOnceOpen = new Proto.SyncMessage.ViewOnceOpen();
|
const viewOnceOpen = new Proto.SyncMessage.ViewOnceOpen();
|
||||||
viewOnceOpen.senderAci = senderAci;
|
if (isProtoBinaryEncodingEnabled()) {
|
||||||
|
viewOnceOpen.senderAciBinary = toAciObject(senderAci).getRawUuidBytes();
|
||||||
|
} else {
|
||||||
|
viewOnceOpen.senderAci = senderAci;
|
||||||
|
}
|
||||||
viewOnceOpen.timestamp = Long.fromNumber(timestamp);
|
viewOnceOpen.timestamp = Long.fromNumber(timestamp);
|
||||||
syncMessage.viewOnceOpen = viewOnceOpen;
|
syncMessage.viewOnceOpen = viewOnceOpen;
|
||||||
|
|
||||||
|
@ -1823,7 +1854,7 @@ export default class MessageSender {
|
||||||
static getBlockSync(
|
static getBlockSync(
|
||||||
options: Readonly<{
|
options: Readonly<{
|
||||||
e164s: Array<string>;
|
e164s: Array<string>;
|
||||||
acis: Array<string>;
|
acis: Array<AciString>;
|
||||||
groupIds: Array<Uint8Array>;
|
groupIds: Array<Uint8Array>;
|
||||||
}>
|
}>
|
||||||
): SingleProtoJobData {
|
): SingleProtoJobData {
|
||||||
|
@ -1833,7 +1864,13 @@ export default class MessageSender {
|
||||||
|
|
||||||
const blocked = new Proto.SyncMessage.Blocked();
|
const blocked = new Proto.SyncMessage.Blocked();
|
||||||
blocked.numbers = options.e164s;
|
blocked.numbers = options.e164s;
|
||||||
blocked.acis = options.acis;
|
if (isProtoBinaryEncodingEnabled()) {
|
||||||
|
blocked.acisBinary = options.acis.map(aci =>
|
||||||
|
toAciObject(aci).getRawUuidBytes()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
blocked.acis = options.acis;
|
||||||
|
}
|
||||||
blocked.groupIds = options.groupIds;
|
blocked.groupIds = options.groupIds;
|
||||||
syncMessage.blocked = blocked;
|
syncMessage.blocked = blocked;
|
||||||
|
|
||||||
|
@ -1867,7 +1904,13 @@ export default class MessageSender {
|
||||||
|
|
||||||
const response = new Proto.SyncMessage.MessageRequestResponse();
|
const response = new Proto.SyncMessage.MessageRequestResponse();
|
||||||
if (options.threadAci !== undefined) {
|
if (options.threadAci !== undefined) {
|
||||||
response.threadAci = options.threadAci;
|
if (isProtoBinaryEncodingEnabled()) {
|
||||||
|
response.threadAciBinary = toAciObject(
|
||||||
|
options.threadAci
|
||||||
|
).getRawUuidBytes();
|
||||||
|
} else {
|
||||||
|
response.threadAci = options.threadAci;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (options.groupId) {
|
if (options.groupId) {
|
||||||
response.groupId = options.groupId;
|
response.groupId = options.groupId;
|
||||||
|
@ -1950,7 +1993,12 @@ export default class MessageSender {
|
||||||
const verified = new Proto.Verified();
|
const verified = new Proto.Verified();
|
||||||
verified.state = state;
|
verified.state = state;
|
||||||
if (destinationAci) {
|
if (destinationAci) {
|
||||||
verified.destinationAci = destinationAci;
|
if (isProtoBinaryEncodingEnabled()) {
|
||||||
|
verified.destinationAciBinary =
|
||||||
|
toAciObject(destinationAci).getRawUuidBytes();
|
||||||
|
} else {
|
||||||
|
verified.destinationAci = destinationAci;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
verified.identityKey = identityKey;
|
verified.identityKey = identityKey;
|
||||||
verified.nullMessage = padding;
|
verified.nullMessage = padding;
|
||||||
|
@ -2501,11 +2549,23 @@ function toAddressableMessage(message: AddressableMessage) {
|
||||||
targetMessage.sentTimestamp = Long.fromNumber(message.sentAt);
|
targetMessage.sentTimestamp = Long.fromNumber(message.sentAt);
|
||||||
|
|
||||||
if (message.type === 'aci') {
|
if (message.type === 'aci') {
|
||||||
targetMessage.authorServiceId = message.authorAci;
|
if (isProtoBinaryEncodingEnabled()) {
|
||||||
|
targetMessage.authorServiceIdBinary = toAciObject(
|
||||||
|
message.authorAci
|
||||||
|
).getServiceIdBinary();
|
||||||
|
} else {
|
||||||
|
targetMessage.authorServiceId = message.authorAci;
|
||||||
|
}
|
||||||
} else if (message.type === 'e164') {
|
} else if (message.type === 'e164') {
|
||||||
targetMessage.authorE164 = message.authorE164;
|
targetMessage.authorE164 = message.authorE164;
|
||||||
} else if (message.type === 'pni') {
|
} else if (message.type === 'pni') {
|
||||||
targetMessage.authorServiceId = message.authorPni;
|
if (isProtoBinaryEncodingEnabled()) {
|
||||||
|
targetMessage.authorServiceIdBinary = toPniObject(
|
||||||
|
message.authorPni
|
||||||
|
).getServiceIdBinary();
|
||||||
|
} else {
|
||||||
|
targetMessage.authorServiceId = message.authorPni;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw missingCaseError(message);
|
throw missingCaseError(message);
|
||||||
}
|
}
|
||||||
|
@ -2517,9 +2577,21 @@ function toConversationIdentifier(conversation: ConversationIdentifier) {
|
||||||
const targetConversation = new Proto.ConversationIdentifier();
|
const targetConversation = new Proto.ConversationIdentifier();
|
||||||
|
|
||||||
if (conversation.type === 'aci') {
|
if (conversation.type === 'aci') {
|
||||||
targetConversation.threadServiceId = conversation.aci;
|
if (isProtoBinaryEncodingEnabled()) {
|
||||||
|
targetConversation.threadServiceIdBinary = toAciObject(
|
||||||
|
conversation.aci
|
||||||
|
).getServiceIdBinary();
|
||||||
|
} else {
|
||||||
|
targetConversation.threadServiceId = conversation.aci;
|
||||||
|
}
|
||||||
} else if (conversation.type === 'pni') {
|
} else if (conversation.type === 'pni') {
|
||||||
targetConversation.threadServiceId = conversation.pni;
|
if (isProtoBinaryEncodingEnabled()) {
|
||||||
|
targetConversation.threadServiceIdBinary = toPniObject(
|
||||||
|
conversation.pni
|
||||||
|
).getServiceIdBinary();
|
||||||
|
} else {
|
||||||
|
targetConversation.threadServiceId = conversation.pni;
|
||||||
|
}
|
||||||
} else if (conversation.type === 'group') {
|
} else if (conversation.type === 'group') {
|
||||||
targetConversation.threadGroupId = Bytes.fromBase64(conversation.groupId);
|
targetConversation.threadGroupId = Bytes.fromBase64(conversation.groupId);
|
||||||
} else if (conversation.type === 'e164') {
|
} else if (conversation.type === 'e164') {
|
||||||
|
|
7
ts/textsecure/Types.d.ts
vendored
7
ts/textsecure/Types.d.ts
vendored
|
@ -189,8 +189,6 @@ export type ProcessedBodyRange = RawBodyRange;
|
||||||
|
|
||||||
export type ProcessedGroupCallUpdate = Proto.DataMessage.IGroupCallUpdate;
|
export type ProcessedGroupCallUpdate = Proto.DataMessage.IGroupCallUpdate;
|
||||||
|
|
||||||
export type ProcessedStoryContext = Proto.DataMessage.IStoryContext;
|
|
||||||
|
|
||||||
export type ProcessedGiftBadge = {
|
export type ProcessedGiftBadge = {
|
||||||
expiration: number;
|
expiration: number;
|
||||||
id: string | undefined;
|
id: string | undefined;
|
||||||
|
@ -199,6 +197,11 @@ export type ProcessedGiftBadge = {
|
||||||
state: GiftBadgeStates;
|
state: GiftBadgeStates;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ProcessedStoryContext = {
|
||||||
|
authorAci: AciString | undefined;
|
||||||
|
sentTimestamp: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type ProcessedDataMessage = {
|
export type ProcessedDataMessage = {
|
||||||
body?: string;
|
body?: string;
|
||||||
bodyAttachment?: ProcessedAttachment;
|
bodyAttachment?: ProcessedAttachment;
|
||||||
|
|
|
@ -7,6 +7,8 @@ import { isNumber } from 'lodash';
|
||||||
|
|
||||||
import { assertDev, strictAssert } from '../util/assert';
|
import { assertDev, strictAssert } from '../util/assert';
|
||||||
import { dropNull, shallowDropNull } from '../util/dropNull';
|
import { dropNull, shallowDropNull } from '../util/dropNull';
|
||||||
|
import { fromAciUuidBytesOrString } from '../util/ServiceId';
|
||||||
|
import { getTimestampFromLong } from '../util/timestampLongUtils';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
import { deriveGroupFields } from '../groups';
|
import { deriveGroupFields } from '../groups';
|
||||||
import * as Bytes from '../Bytes';
|
import * as Bytes from '../Bytes';
|
||||||
|
@ -22,6 +24,7 @@ import type {
|
||||||
ProcessedReaction,
|
ProcessedReaction,
|
||||||
ProcessedDelete,
|
ProcessedDelete,
|
||||||
ProcessedGiftBadge,
|
ProcessedGiftBadge,
|
||||||
|
ProcessedStoryContext,
|
||||||
} from './Types.d';
|
} from './Types.d';
|
||||||
import { GiftBadgeStates } from '../components/conversation/Message';
|
import { GiftBadgeStates } from '../components/conversation/Message';
|
||||||
import { APPLICATION_OCTET_STREAM, stringToMIMEType } from '../types/MIME';
|
import { APPLICATION_OCTET_STREAM, stringToMIMEType } from '../types/MIME';
|
||||||
|
@ -29,8 +32,6 @@ import { SECOND, DurationInSeconds } from '../util/durations';
|
||||||
import type { AnyPaymentEvent } from '../types/Payment';
|
import type { AnyPaymentEvent } from '../types/Payment';
|
||||||
import { PaymentEventKind } from '../types/Payment';
|
import { PaymentEventKind } from '../types/Payment';
|
||||||
import { filterAndClean } from '../types/BodyRange';
|
import { filterAndClean } from '../types/BodyRange';
|
||||||
import { isAciString } from '../util/isAciString';
|
|
||||||
import { normalizeAci } from '../util/normalizeAci';
|
|
||||||
import { bytesToUuid } from '../util/uuidToBytes';
|
import { bytesToUuid } from '../util/uuidToBytes';
|
||||||
import { createName } from '../util/attachmentPath';
|
import { createName } from '../util/attachmentPath';
|
||||||
import { partitionBodyAndNormalAttachments } from '../types/Attachment';
|
import { partitionBodyAndNormalAttachments } from '../types/Attachment';
|
||||||
|
@ -168,14 +169,16 @@ export function processQuote(
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { authorAci } = quote;
|
const { authorAci: rawAuthorAci, authorAciBinary } = quote;
|
||||||
if (!isAciString(authorAci)) {
|
const authorAci = fromAciUuidBytesOrString(
|
||||||
throw new Error('quote.authorAci is not an ACI string');
|
authorAciBinary,
|
||||||
}
|
rawAuthorAci,
|
||||||
|
'Quote.authorAci'
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: quote.id?.toNumber(),
|
id: quote.id?.toNumber(),
|
||||||
authorAci: normalizeAci(authorAci, 'Quote.authorAci'),
|
authorAci,
|
||||||
text: dropNull(quote.text),
|
text: dropNull(quote.text),
|
||||||
attachments: (quote.attachments ?? []).slice(0, 1).map(attachment => {
|
attachments: (quote.attachments ?? []).slice(0, 1).map(attachment => {
|
||||||
return {
|
return {
|
||||||
|
@ -191,6 +194,30 @@ export function processQuote(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function processStoryContext(
|
||||||
|
storyContext?: Proto.DataMessage.IStoryContext | null
|
||||||
|
): ProcessedStoryContext | undefined {
|
||||||
|
if (!storyContext) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
authorAci: rawAuthorAci,
|
||||||
|
authorAciBinary,
|
||||||
|
sentTimestamp,
|
||||||
|
} = storyContext;
|
||||||
|
const authorAci = fromAciUuidBytesOrString(
|
||||||
|
authorAciBinary,
|
||||||
|
rawAuthorAci,
|
||||||
|
'StoryContext.authorAci'
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
authorAci,
|
||||||
|
sentTimestamp: getTimestampFromLong(sentTimestamp),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function processContact(
|
export function processContact(
|
||||||
contact?: ReadonlyArray<Proto.DataMessage.IContact> | null
|
contact?: ReadonlyArray<Proto.DataMessage.IContact> | null
|
||||||
): ReadonlyArray<ProcessedContact> | undefined {
|
): ReadonlyArray<ProcessedContact> | undefined {
|
||||||
|
@ -266,15 +293,18 @@ export function processReaction(
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { targetAuthorAci } = reaction;
|
const { targetAuthorAci: rawTargetAuthorAci, targetAuthorAciBinary } =
|
||||||
if (!isAciString(targetAuthorAci)) {
|
reaction;
|
||||||
throw new Error('reaction.targetAuthorAci is not an ACI string');
|
const targetAuthorAci = fromAciUuidBytesOrString(
|
||||||
}
|
targetAuthorAciBinary,
|
||||||
|
rawTargetAuthorAci,
|
||||||
|
'Reaction.targetAuthorAci'
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
emoji: dropNull(reaction.emoji),
|
emoji: dropNull(reaction.emoji),
|
||||||
remove: Boolean(reaction.remove),
|
remove: Boolean(reaction.remove),
|
||||||
targetAuthorAci: normalizeAci(targetAuthorAci, 'Reaction.targetAuthorAci'),
|
targetAuthorAci,
|
||||||
targetTimestamp: reaction.targetSentTimestamp?.toNumber(),
|
targetTimestamp: reaction.targetSentTimestamp?.toNumber(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -380,7 +410,7 @@ export function processDataMessage(
|
||||||
delete: processDelete(message.delete),
|
delete: processDelete(message.delete),
|
||||||
bodyRanges: filterAndClean(message.bodyRanges),
|
bodyRanges: filterAndClean(message.bodyRanges),
|
||||||
groupCallUpdate: dropNull(message.groupCallUpdate),
|
groupCallUpdate: dropNull(message.groupCallUpdate),
|
||||||
storyContext: dropNull(message.storyContext),
|
storyContext: processStoryContext(message.storyContext),
|
||||||
giftBadge: processGiftBadge(message.giftBadge),
|
giftBadge: processGiftBadge(message.giftBadge),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
|
|
||||||
import type { SignalService as Proto } from '../protobuf';
|
import type { SignalService as Proto } from '../protobuf';
|
||||||
import type { ServiceIdString } from '../types/ServiceId';
|
import type { ServiceIdString } from '../types/ServiceId';
|
||||||
import { normalizeServiceId } from '../types/ServiceId';
|
import { fromServiceIdBinaryOrString } from '../util/ServiceId';
|
||||||
import type { ProcessedSent, ProcessedSyncMessage } from './Types.d';
|
import type { ProcessedSent, ProcessedSyncMessage } from './Types.d';
|
||||||
|
|
||||||
type ProtoServiceId = Readonly<{
|
type ProtoServiceId = Readonly<{
|
||||||
destinationServiceId?: string | null;
|
destinationServiceId?: string | null;
|
||||||
|
destinationServiceIdBinary?: Uint8Array | null;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
function processProtoWithDestinationServiceId<Input extends ProtoServiceId>(
|
function processProtoWithDestinationServiceId<Input extends ProtoServiceId>(
|
||||||
|
@ -15,14 +16,20 @@ function processProtoWithDestinationServiceId<Input extends ProtoServiceId>(
|
||||||
): Omit<Input, keyof ProtoServiceId> & {
|
): Omit<Input, keyof ProtoServiceId> & {
|
||||||
destinationServiceId?: ServiceIdString;
|
destinationServiceId?: ServiceIdString;
|
||||||
} {
|
} {
|
||||||
const { destinationServiceId, ...remaining } = input;
|
const {
|
||||||
|
destinationServiceId: rawDestinationServiceId,
|
||||||
|
destinationServiceIdBinary,
|
||||||
|
...remaining
|
||||||
|
} = input;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...remaining,
|
...remaining,
|
||||||
|
|
||||||
destinationServiceId: destinationServiceId
|
destinationServiceId: fromServiceIdBinaryOrString(
|
||||||
? normalizeServiceId(destinationServiceId, 'processSyncMessage')
|
destinationServiceIdBinary,
|
||||||
: undefined,
|
rawDestinationServiceId,
|
||||||
|
'processSyncMessage'
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +41,8 @@ function processSent(
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
destinationServiceId,
|
destinationServiceId: rawDestinationServiceId,
|
||||||
|
destinationServiceIdBinary,
|
||||||
unidentifiedStatus,
|
unidentifiedStatus,
|
||||||
storyMessageRecipients,
|
storyMessageRecipients,
|
||||||
...remaining
|
...remaining
|
||||||
|
@ -43,9 +51,11 @@ function processSent(
|
||||||
return {
|
return {
|
||||||
...remaining,
|
...remaining,
|
||||||
|
|
||||||
destinationServiceId: destinationServiceId
|
destinationServiceId: fromServiceIdBinaryOrString(
|
||||||
? normalizeServiceId(destinationServiceId, 'processSent')
|
destinationServiceIdBinary,
|
||||||
: undefined,
|
rawDestinationServiceId,
|
||||||
|
'processSent'
|
||||||
|
),
|
||||||
unidentifiedStatus: unidentifiedStatus
|
unidentifiedStatus: unidentifiedStatus
|
||||||
? unidentifiedStatus.map(processProtoWithDestinationServiceId)
|
? unidentifiedStatus.map(processProtoWithDestinationServiceId)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
|
@ -3,6 +3,17 @@
|
||||||
|
|
||||||
import { Aci, Pni, ServiceId } from '@signalapp/libsignal-client';
|
import { Aci, Pni, ServiceId } from '@signalapp/libsignal-client';
|
||||||
import type { AciString, PniString, ServiceIdString } from '../types/ServiceId';
|
import type { AciString, PniString, ServiceIdString } from '../types/ServiceId';
|
||||||
|
import {
|
||||||
|
normalizeServiceId,
|
||||||
|
normalizePni,
|
||||||
|
isUntaggedPniString,
|
||||||
|
toTaggedPni,
|
||||||
|
fromServiceIdObject,
|
||||||
|
fromAciObject,
|
||||||
|
fromPniObject,
|
||||||
|
} from '../types/ServiceId';
|
||||||
|
import * as Bytes from '../Bytes';
|
||||||
|
import { normalizeAci } from './normalizeAci';
|
||||||
|
|
||||||
export function toServiceIdObject(serviceId: ServiceIdString): ServiceId {
|
export function toServiceIdObject(serviceId: ServiceIdString): ServiceId {
|
||||||
return ServiceId.parseFromServiceIdString(serviceId);
|
return ServiceId.parseFromServiceIdString(serviceId);
|
||||||
|
@ -15,3 +26,86 @@ export function toAciObject(aci: AciString): Aci {
|
||||||
export function toPniObject(pni: PniString): Pni {
|
export function toPniObject(pni: PniString): Pni {
|
||||||
return Pni.parseFromServiceIdString(pni);
|
return Pni.parseFromServiceIdString(pni);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fromServiceIdBinaryOrString(
|
||||||
|
bytes: Uint8Array,
|
||||||
|
fallback: string | undefined | null,
|
||||||
|
context: string
|
||||||
|
): ServiceIdString;
|
||||||
|
|
||||||
|
export function fromServiceIdBinaryOrString(
|
||||||
|
bytes: Uint8Array | undefined | null,
|
||||||
|
fallback: string | undefined | null,
|
||||||
|
context: string
|
||||||
|
): ServiceIdString | undefined;
|
||||||
|
|
||||||
|
export function fromServiceIdBinaryOrString(
|
||||||
|
bytes: Uint8Array | undefined | null,
|
||||||
|
fallback: string | undefined | null,
|
||||||
|
context: string
|
||||||
|
): ServiceIdString | undefined {
|
||||||
|
if (Bytes.isNotEmpty(bytes)) {
|
||||||
|
return fromServiceIdObject(
|
||||||
|
ServiceId.parseFromServiceIdBinary(Buffer.from(bytes))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (fallback) {
|
||||||
|
return normalizeServiceId(fallback, context);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fromAciUuidBytes(bytes: Uint8Array): AciString;
|
||||||
|
|
||||||
|
export function fromAciUuidBytes(
|
||||||
|
bytes: Uint8Array | undefined | null
|
||||||
|
): AciString | undefined;
|
||||||
|
|
||||||
|
export function fromAciUuidBytes(
|
||||||
|
bytes: Uint8Array | undefined | null
|
||||||
|
): AciString | undefined {
|
||||||
|
if (Bytes.isNotEmpty(bytes)) {
|
||||||
|
return fromAciObject(Aci.fromUuidBytes(Buffer.from(bytes)));
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fromAciUuidBytesOrString(
|
||||||
|
bytes: Uint8Array,
|
||||||
|
fallback: string | undefined | null,
|
||||||
|
context: string
|
||||||
|
): AciString;
|
||||||
|
|
||||||
|
export function fromAciUuidBytesOrString(
|
||||||
|
bytes: Uint8Array | undefined | null,
|
||||||
|
fallback: string | undefined | null,
|
||||||
|
context: string
|
||||||
|
): AciString | undefined;
|
||||||
|
|
||||||
|
export function fromAciUuidBytesOrString(
|
||||||
|
bytes: Uint8Array | undefined | null,
|
||||||
|
fallback: string | undefined | null,
|
||||||
|
context: string
|
||||||
|
): AciString | undefined {
|
||||||
|
if (Bytes.isNotEmpty(bytes)) {
|
||||||
|
return fromAciUuidBytes(bytes);
|
||||||
|
}
|
||||||
|
if (fallback) {
|
||||||
|
return normalizeAci(fallback, context);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fromPniUuidBytesOrUntaggedString(
|
||||||
|
bytes: Uint8Array | undefined | null,
|
||||||
|
fallback: string | undefined | null,
|
||||||
|
context: string
|
||||||
|
): PniString | undefined {
|
||||||
|
if (Bytes.isNotEmpty(bytes)) {
|
||||||
|
return fromPniObject(Pni.fromUuidBytes(Buffer.from(bytes)));
|
||||||
|
}
|
||||||
|
if (fallback && isUntaggedPniString(fallback)) {
|
||||||
|
return normalizePni(toTaggedPni(fallback), context);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import * as Bytes from '../Bytes';
|
import * as Bytes from '../Bytes';
|
||||||
|
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
import { fromServiceIdBinaryOrString } from './ServiceId';
|
||||||
|
|
||||||
import PinnedConversation = Proto.AccountRecord.IPinnedConversation;
|
import PinnedConversation = Proto.AccountRecord.IPinnedConversation;
|
||||||
|
|
||||||
|
@ -22,10 +23,24 @@ export function arePinnedConversationsEqual(
|
||||||
localPinnedConversation;
|
localPinnedConversation;
|
||||||
|
|
||||||
if (contact) {
|
if (contact) {
|
||||||
return (
|
const { contact: remoteContact } = remotePinnedConversation;
|
||||||
remotePinnedConversation.contact &&
|
if (!remoteContact) {
|
||||||
contact.serviceId === remotePinnedConversation.contact.serviceId
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceId = fromServiceIdBinaryOrString(
|
||||||
|
contact.serviceIdBinary,
|
||||||
|
contact.serviceId,
|
||||||
|
`arePinnedConversationsEqual(${index}).local`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const remoteServiceId = fromServiceIdBinaryOrString(
|
||||||
|
remoteContact.serviceIdBinary,
|
||||||
|
remoteContact.serviceId,
|
||||||
|
`arePinnedConversationsEqual(${index}).remote`
|
||||||
|
);
|
||||||
|
|
||||||
|
return serviceId === remoteServiceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupMasterKey && groupMasterKey.length) {
|
if (groupMasterKey && groupMasterKey.length) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ export function checkFirstEnvelope(incoming: IncomingWebSocketRequest): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
const decoded = Proto.Envelope.decode(plaintext);
|
const decoded = Proto.Envelope.decode(plaintext);
|
||||||
const newEnvelopeTimestamp = decoded.timestamp?.toNumber();
|
const newEnvelopeTimestamp = decoded.clientTimestamp?.toNumber();
|
||||||
if (!isNumber(newEnvelopeTimestamp)) {
|
if (!isNumber(newEnvelopeTimestamp)) {
|
||||||
log.warn('timestamp is not a number!');
|
log.warn('timestamp is not a number!');
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -5,33 +5,32 @@ import type {
|
||||||
ReadonlyMessageAttributesType,
|
ReadonlyMessageAttributesType,
|
||||||
MessageAttributesType,
|
MessageAttributesType,
|
||||||
} from '../model-types.d';
|
} from '../model-types.d';
|
||||||
import type { SignalService as Proto } from '../protobuf';
|
import { type AciString } from '../types/ServiceId';
|
||||||
import type { AciString } from '../types/ServiceId';
|
import { type ProcessedStoryContext } from '../textsecure/Types.d';
|
||||||
import { DataReader } from '../sql/Client';
|
import { DataReader } from '../sql/Client';
|
||||||
import { createLogger } from '../logging/log';
|
import { createLogger } from '../logging/log';
|
||||||
import { normalizeAci } from './normalizeAci';
|
|
||||||
import { getAuthorId } from '../messages/helpers';
|
import { getAuthorId } from '../messages/helpers';
|
||||||
import { getTimestampFromLong } from './timestampLongUtils';
|
|
||||||
|
|
||||||
const log = createLogger('findStoryMessage');
|
const log = createLogger('findStoryMessage');
|
||||||
|
|
||||||
export async function findStoryMessages(
|
export async function findStoryMessages(
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
storyContext?: Proto.DataMessage.IStoryContext
|
storyContext?: ProcessedStoryContext
|
||||||
): Promise<Array<MessageAttributesType>> {
|
): Promise<Array<MessageAttributesType>> {
|
||||||
if (!storyContext) {
|
if (!storyContext) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const { authorAci: rawAuthorAci, sentTimestamp } = storyContext;
|
const { authorAci, sentTimestamp: sentAt } = storyContext;
|
||||||
|
|
||||||
if (!rawAuthorAci || !sentTimestamp) {
|
if (!sentAt) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const authorAci = normalizeAci(rawAuthorAci, 'findStoryMessage');
|
if (authorAci == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const sentAt = getTimestampFromLong(sentTimestamp);
|
|
||||||
const ourConversationId =
|
const ourConversationId =
|
||||||
window.ConversationController.getOurConversationIdOrThrow();
|
window.ConversationController.getOurConversationIdOrThrow();
|
||||||
|
|
||||||
|
|
13
ts/util/isProtoBinaryEncodingEnabled.ts
Normal file
13
ts/util/isProtoBinaryEncodingEnabled.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright 2025 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { isTestOrMockEnvironment } from '../environment';
|
||||||
|
|
||||||
|
export function isProtoBinaryEncodingEnabled(): boolean {
|
||||||
|
if (isTestOrMockEnvironment()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: https://signalmessenger.atlassian.net/browse/DESKTOP-8938
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -4,7 +4,6 @@
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { DataReader } from '../sql/Client';
|
import { DataReader } from '../sql/Client';
|
||||||
import type { StoryRecipientUpdateEvent } from '../textsecure/messageReceiverEvents';
|
import type { StoryRecipientUpdateEvent } from '../textsecure/messageReceiverEvents';
|
||||||
import { normalizeServiceId } from '../types/ServiceId';
|
|
||||||
import { normalizeStoryDistributionId } from '../types/StoryDistributionId';
|
import { normalizeStoryDistributionId } from '../types/StoryDistributionId';
|
||||||
import { createLogger } from '../logging/log';
|
import { createLogger } from '../logging/log';
|
||||||
import { SendStatus } from '../messages/MessageSendState';
|
import { SendStatus } from '../messages/MessageSendState';
|
||||||
|
@ -13,6 +12,7 @@ import { isStory } from '../state/selectors/message';
|
||||||
import { queueUpdateMessage } from './messageBatcher';
|
import { queueUpdateMessage } from './messageBatcher';
|
||||||
import { isMe } from './whatTypeOfConversation';
|
import { isMe } from './whatTypeOfConversation';
|
||||||
import { drop } from './drop';
|
import { drop } from './drop';
|
||||||
|
import { fromServiceIdBinaryOrString } from './ServiceId';
|
||||||
import { handleDeleteForEveryone } from './deleteForEveryone';
|
import { handleDeleteForEveryone } from './deleteForEveryone';
|
||||||
import { MessageModel } from '../models/messages';
|
import { MessageModel } from '../models/messages';
|
||||||
|
|
||||||
|
@ -61,15 +61,22 @@ export async function onStoryRecipientUpdate(
|
||||||
Set<string>
|
Set<string>
|
||||||
>();
|
>();
|
||||||
data.storyMessageRecipients.forEach(item => {
|
data.storyMessageRecipients.forEach(item => {
|
||||||
const { destinationServiceId: recipientServiceId } = item;
|
const {
|
||||||
|
destinationServiceId: rawDestinationServiceId,
|
||||||
|
destinationServiceIdBinary,
|
||||||
|
} = item;
|
||||||
|
|
||||||
if (!recipientServiceId) {
|
const recipientServiceId = fromServiceIdBinaryOrString(
|
||||||
|
destinationServiceIdBinary,
|
||||||
|
rawDestinationServiceId,
|
||||||
|
`${logId}.recipientServiceId`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (recipientServiceId == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const convo = window.ConversationController.get(
|
const convo = window.ConversationController.get(recipientServiceId);
|
||||||
normalizeServiceId(recipientServiceId, `${logId}.recipientServiceId`)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!convo || !item.distributionListIds) {
|
if (!convo || !item.distributionListIds) {
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue