Use new compact representations in protobufs

Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
This commit is contained in:
Fedor Indutny 2025-06-25 10:30:40 -07:00 committed by GitHub
commit 8251720444
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
64 changed files with 1000 additions and 459 deletions

View file

@ -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
View file

@ -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

View file

@ -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
View 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
}

View file

@ -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)
} }
} }

View file

@ -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 {

View file

@ -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 };

View file

@ -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,
}); });

View file

@ -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;
} }

View file

@ -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');

View file

@ -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';

View file

@ -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,

View file

@ -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;

View file

@ -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();

View file

@ -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');

View file

@ -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();
}; };

View file

@ -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');
{ {

View file

@ -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();
} }

View file

@ -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;

View file

@ -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),
}, },
}, },

View file

@ -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: [
{ {

View file

@ -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');

View file

@ -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,
}, },
], ],
}, },

View file

@ -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,
})), })),
}, },

View file

@ -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],
}, },
}, },
}); });

View file

@ -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,
})), })),
}, },

View file

@ -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: {

View file

@ -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,
}, },
}, },
}); });

View file

@ -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();
} }

View file

@ -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);

View file

@ -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');
}); });

View file

@ -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,
}, },
], ],

View file

@ -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({

View file

@ -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
) )

View file

@ -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

View file

@ -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,
} }
) )
); );

View file

@ -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: [],
}, },
}, },
}); });

View file

@ -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);
} }

View file

@ -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: [],
}, },
}, },
}); });

View file

@ -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);

View file

@ -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({

View file

@ -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(

View file

@ -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: [],
}, },
}, },
}); });

View file

@ -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: [],
}, },
}, },
}); });

View file

@ -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');

View file

@ -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({

View file

@ -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();

View file

@ -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();
{ {

View file

@ -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';

View file

@ -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,

View file

@ -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(

View file

@ -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;

View file

@ -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(

View file

@ -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;
} }

View file

@ -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') {

View file

@ -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;

View file

@ -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),
}; };

View file

@ -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,

View file

@ -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;
}

View file

@ -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) {

View file

@ -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;

View file

@ -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();

View 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;
}

View file

@ -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;