Update protopiler to 2.0.2

This commit is contained in:
Fedor Indutny 2026-03-06 10:22:26 -08:00 committed by GitHub
parent 94e80a26cb
commit 37b712167f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 233 additions and 222 deletions

View File

@ -44,7 +44,7 @@
"homepage": "https://github.com/signalapp/Mock-Signal-Server#readme",
"dependencies": {
"@indutny/parallel-prettier": "^3.0.0",
"@indutny/protopiler": "1.0.0",
"@indutny/protopiler": "2.0.2",
"@signalapp/libsignal-client": "^0.76.7",
"@tus/file-store": "^1.4.0",
"@tus/server": "^1.7.0",

View File

@ -64,8 +64,8 @@ export class Group extends GroupData {
const cipher = new ClientZkGroupCipher(secretParams);
const decrypted = decryptBlob(cipher, groupState.title);
assert.strictEqual(decrypted.content, 'title', 'expected title');
this.title = decrypted.title;
assert(decrypted.content?.title != null, 'expected title');
this.title = decrypted.content.title;
this.privPublicParams = this.secretParams.getPublicParams();
@ -92,7 +92,7 @@ export class Group extends GroupData {
const groupState: Proto.Group.Params = {
publicKey: secretParams.getPublicParams().serialize(),
version: 0,
title: encryptBlob(cipher, { content: 'title', title }),
title: encryptBlob(cipher, { content: { title } }),
// TODO(indutny): make it configurable
accessControl: {

View File

@ -994,11 +994,12 @@ export class PrimaryDevice {
const envelope = await this.encryptContent(
device,
{
content: 'dataMessage',
dataMessage: {
...EMPTY_DATA_MESSAGE,
groupV2,
timestamp: BigInt(encryptOptions.timestamp),
content: {
dataMessage: {
...EMPTY_DATA_MESSAGE,
groupV2,
timestamp: BigInt(encryptOptions.timestamp),
},
},
pniSignatureMessage: null,
senderKeyDistributionMessage: null,
@ -1067,11 +1068,12 @@ export class PrimaryDevice {
...options,
};
const content: Proto.Content.Params = {
content: 'dataMessage',
dataMessage: {
...EMPTY_DATA_MESSAGE,
groupV2,
timestamp: BigInt(encryptOptions.timestamp),
content: {
dataMessage: {
...EMPTY_DATA_MESSAGE,
groupV2,
timestamp: BigInt(encryptOptions.timestamp),
},
},
pniSignatureMessage: null,
senderKeyDistributionMessage: null,
@ -1205,68 +1207,60 @@ export class PrimaryDevice {
});
let handled = true;
switch (content.content) {
case 'decryptionErrorMessage':
assert.strictEqual(
serviceIdKind,
ServiceIdKind.ACI,
'Got sync message on PNI',
);
this.handleResendRequest(
unsealedSource,
serviceIdKind,
unsealedType,
content,
content.decryptionErrorMessage,
);
break;
case 'syncMessage':
assert.strictEqual(
serviceIdKind,
ServiceIdKind.ACI,
'Got sync message on PNI',
);
await this.handleSync(unsealedSource, content.syncMessage);
break;
case 'dataMessage':
this.handleDataMessage(
unsealedSource,
serviceIdKind,
unsealedType,
content,
content.dataMessage,
);
break;
case 'storyMessage':
this.handleStoryMessage(
unsealedSource,
serviceIdKind,
unsealedType,
content,
content.storyMessage,
);
break;
case 'editMessage':
this.handleEditMessage(
unsealedSource,
serviceIdKind,
unsealedType,
content,
content.editMessage,
);
break;
case 'receiptMessage':
this.handleReceiptMessage(
unsealedSource,
serviceIdKind,
unsealedType,
content,
content.receiptMessage,
);
break;
default:
handled = false;
break;
if (content.content?.decryptionErrorMessage) {
assert.strictEqual(
serviceIdKind,
ServiceIdKind.ACI,
'Got sync message on PNI',
);
this.handleResendRequest(
unsealedSource,
serviceIdKind,
unsealedType,
content,
content.content.decryptionErrorMessage,
);
} else if (content.content?.syncMessage) {
assert.strictEqual(
serviceIdKind,
ServiceIdKind.ACI,
'Got sync message on PNI',
);
await this.handleSync(unsealedSource, content.content.syncMessage);
} else if (content.content?.dataMessage) {
this.handleDataMessage(
unsealedSource,
serviceIdKind,
unsealedType,
content,
content.content.dataMessage,
);
} else if (content.content?.storyMessage) {
this.handleStoryMessage(
unsealedSource,
serviceIdKind,
unsealedType,
content,
content.content.storyMessage,
);
} else if (content.content?.editMessage) {
this.handleEditMessage(
unsealedSource,
serviceIdKind,
unsealedType,
content,
content.content.editMessage,
);
} else if (content.content?.receiptMessage) {
this.handleReceiptMessage(
unsealedSource,
serviceIdKind,
unsealedType,
content,
content.content.receiptMessage,
);
} else {
handled = false;
}
const { senderKeyDistributionMessage } = content;
@ -1310,13 +1304,16 @@ export class PrimaryDevice {
}
const content: Proto.Content.Params = {
content: 'dataMessage',
dataMessage: {
...EMPTY_DATA_MESSAGE,
groupV2: options.group?.toContext() ?? null,
body: text,
profileKey: options.withProfileKey ? this.profileKey.serialize() : null,
timestamp: BigInt(encryptOptions.timestamp),
content: {
dataMessage: {
...EMPTY_DATA_MESSAGE,
groupV2: options.group?.toContext() ?? null,
body: text,
profileKey: options.withProfileKey
? this.profileKey.serialize()
: null,
timestamp: BigInt(encryptOptions.timestamp),
},
},
pniSignatureMessage,
senderKeyDistributionMessage: null,
@ -1336,29 +1333,31 @@ export class PrimaryDevice {
};
const content: Proto.Content.Params = {
content: 'syncMessage',
syncMessage: {
content: 'sent',
sent: {
destinationServiceIdBinary: ServiceId.parseFromServiceIdString(
options.destinationServiceId,
).getServiceIdBinary(),
timestamp: BigInt(options.timestamp),
message: dataMessage,
destinationE164: null,
unidentifiedStatus: null,
isRecipientUpdate: null,
expirationStartTimestamp: null,
storyMessage: null,
storyMessageRecipients: null,
editMessage: null,
content: {
syncMessage: {
content: {
sent: {
destinationServiceIdBinary: ServiceId.parseFromServiceIdString(
options.destinationServiceId,
).getServiceIdBinary(),
timestamp: BigInt(options.timestamp),
message: dataMessage,
destinationE164: null,
unidentifiedStatus: null,
isRecipientUpdate: null,
expirationStartTimestamp: null,
storyMessage: null,
storyMessageRecipients: null,
editMessage: null,
destinationServiceId: null,
destinationServiceId: null,
},
},
stickerPackOperation: null,
read: null,
viewed: null,
padding: null,
},
stickerPackOperation: null,
read: null,
viewed: null,
padding: null,
},
pniSignatureMessage: null,
senderKeyDistributionMessage: null,
@ -1371,22 +1370,23 @@ export class PrimaryDevice {
options: SyncReadOptions,
): Promise<Buffer> {
const content: Proto.Content.Params = {
content: 'syncMessage',
syncMessage: {
content: null,
stickerPackOperation: null,
read: options.messages.map(({ senderAci, timestamp }) => {
return {
senderAciBinary:
Aci.parseFromServiceIdString(senderAci).getRawUuidBytes(),
timestamp: BigInt(timestamp),
content: {
syncMessage: {
content: null,
stickerPackOperation: null,
read: options.messages.map(({ senderAci, timestamp }) => {
return {
senderAciBinary:
Aci.parseFromServiceIdString(senderAci).getRawUuidBytes(),
timestamp: BigInt(timestamp),
// Deprecated string field
senderAci: null,
};
}),
viewed: null,
padding: null,
// Deprecated string field
senderAci: null,
};
}),
viewed: null,
padding: null,
},
},
pniSignatureMessage: null,
senderKeyDistributionMessage: null,
@ -1396,16 +1396,18 @@ export class PrimaryDevice {
public async sendFetchStorage(options: FetchStorageOptions): Promise<void> {
const content: Proto.Content.Params = {
content: 'syncMessage',
syncMessage: {
content: 'fetchLatest',
fetchLatest: {
type: Proto.SyncMessage.FetchLatest.Type.STORAGE_MANIFEST,
content: {
syncMessage: {
content: {
fetchLatest: {
type: Proto.SyncMessage.FetchLatest.Type.STORAGE_MANIFEST,
},
},
stickerPackOperation: null,
read: null,
viewed: null,
padding: null,
},
stickerPackOperation: null,
read: null,
viewed: null,
padding: null,
},
pniSignatureMessage: null,
senderKeyDistributionMessage: null,
@ -1420,19 +1422,20 @@ export class PrimaryDevice {
const Type = Proto.SyncMessage.StickerPackOperation.Type;
const content: Proto.Content.Params = {
content: 'syncMessage',
syncMessage: {
content: null,
stickerPackOperation: [
{
packId: options.packId,
packKey: options.packKey,
type: options.type === 'install' ? Type.INSTALL : Type.REMOVE,
},
],
read: null,
viewed: null,
padding: null,
content: {
syncMessage: {
content: null,
stickerPackOperation: [
{
packId: options.packId,
packKey: options.packKey,
type: options.type === 'install' ? Type.INSTALL : Type.REMOVE,
},
],
read: null,
viewed: null,
padding: null,
},
},
pniSignatureMessage: null,
senderKeyDistributionMessage: null,
@ -1454,12 +1457,13 @@ export class PrimaryDevice {
}
const content: Proto.Content.Params = {
content: 'receiptMessage',
receiptMessage: {
type,
timestamp: options.messageTimestamps.map((timestamp) =>
BigInt(timestamp),
),
content: {
receiptMessage: {
type,
timestamp: options.messageTimestamps.map((timestamp) =>
BigInt(timestamp),
),
},
},
pniSignatureMessage: null,
senderKeyDistributionMessage: null,
@ -1563,20 +1567,22 @@ export class PrimaryDevice {
const { signedPreKeyRecord, lastResortKeyRecord } = keys;
const content: Proto.Content.Params = {
content: 'syncMessage',
syncMessage: {
content: 'pniChangeNumber',
pniChangeNumber: {
identityKeyPair: newPniIdentity.serialize(),
lastResortKyberPreKey: lastResortKeyRecord.serialize(),
signedPreKey: signedPreKeyRecord.serialize(),
registrationId: newPniRegistrationId,
newE164: newNumber,
content: {
syncMessage: {
content: {
pniChangeNumber: {
identityKeyPair: newPniIdentity.serialize(),
lastResortKyberPreKey: lastResortKeyRecord.serialize(),
signedPreKey: signedPreKeyRecord.serialize(),
registrationId: newPniRegistrationId,
newE164: newNumber,
},
},
read: null,
stickerPackOperation: null,
viewed: null,
padding: null,
},
read: null,
stickerPackOperation: null,
viewed: null,
padding: null,
},
pniSignatureMessage: null,
senderKeyDistributionMessage: null,
@ -1854,7 +1860,7 @@ export class PrimaryDevice {
source: Device,
sync: Proto.SyncMessage,
): Promise<void> {
if (sync.content !== 'request') {
if (sync.content?.request == null) {
debug('got generic sync message');
this.syncMessageQueue.push({
source,
@ -1863,17 +1869,20 @@ export class PrimaryDevice {
return;
}
const { request } = sync;
const {
content: { request },
} = sync;
let stateChange: SyncState;
let response: Proto.SyncMessage.Params;
if (request.type === Proto.SyncMessage.Request.Type.CONTACTS) {
debug('got sync contacts request');
response = {
content: 'contacts',
contacts: {
blob: this.contactsBlob,
complete: true,
content: {
contacts: {
blob: this.contactsBlob,
complete: true,
},
},
stickerPackOperation: null,
read: null,
@ -1884,14 +1893,15 @@ export class PrimaryDevice {
} else if (request.type === Proto.SyncMessage.Request.Type.BLOCKED) {
debug('got sync blocked request');
response = {
content: 'blocked',
blocked: {
numbers: null,
groupIds: null,
acisBinary: null,
content: {
blocked: {
numbers: null,
groupIds: null,
acisBinary: null,
// Deprecated string field
acis: null,
// Deprecated string field
acis: null,
},
},
stickerPackOperation: null,
read: null,
@ -1902,12 +1912,13 @@ export class PrimaryDevice {
} else if (request.type === Proto.SyncMessage.Request.Type.CONFIGURATION) {
debug('got sync configuration request');
response = {
content: 'configuration',
configuration: {
readReceipts: true,
unidentifiedDeliveryIndicators: false,
typingIndicators: false,
linkPreviews: false,
content: {
configuration: {
readReceipts: true,
unidentifiedDeliveryIndicators: false,
typingIndicators: false,
linkPreviews: false,
},
},
stickerPackOperation: null,
read: null,
@ -1918,11 +1929,12 @@ export class PrimaryDevice {
} else if (request.type === Proto.SyncMessage.Request.Type.KEYS) {
debug('got sync keys request');
response = {
content: 'keys',
keys: {
master: this.masterKey,
mediaRootBackupKey: this.mediaRootBackupKey,
accountEntropyPool: this.accountEntropyPool,
content: {
keys: {
master: this.masterKey,
mediaRootBackupKey: this.mediaRootBackupKey,
accountEntropyPool: this.accountEntropyPool,
},
},
stickerPackOperation: null,
read: null,
@ -1936,8 +1948,9 @@ export class PrimaryDevice {
}
const encrypted = await this.encryptContent(source, {
content: 'syncMessage',
syncMessage: response,
content: {
syncMessage: response,
},
pniSignatureMessage: null,
senderKeyDistributionMessage: null,
});
@ -2373,7 +2386,7 @@ export class PrimaryDevice {
return {
type,
key: keyBuffer,
record: decrypted,
record: decrypted.record,
};
}),
);

View File

@ -378,8 +378,8 @@ export class Server extends BaseServer {
);
const contactsCDNKey = await this.storeAttachment(contactsAttachment.blob);
debug('contacts cdn key', contactsCDNKey);
if (this.emptyAttachment.attachmentIdentifier === 'cdnKey') {
debug('groups cdn key', this.emptyAttachment.cdnKey);
if (this.emptyAttachment.attachmentIdentifier?.cdnKey != null) {
debug('groups cdn key', this.emptyAttachment.attachmentIdentifier.cdnKey);
}
const primary = new PrimaryDevice(device, {

View File

@ -12,7 +12,7 @@ import { ServiceIdKind } from '../types';
import { Group } from './group';
import { PrimaryDevice } from './primary-device';
type RecordValue = NonNullable<Proto.StorageRecord.Params>;
type RecordValue = NonNullable<Proto.StorageRecord.Params['record']>;
export type StorageStateRecord<Value extends RecordValue = RecordValue> =
Readonly<{
@ -80,7 +80,9 @@ class StorageStateItem<Value extends RecordValue = RecordValue> {
storageKey,
recordIkm,
key: this.key,
record: this.record,
record: {
record: this.record,
},
});
}
@ -92,20 +94,18 @@ class StorageStateItem<Value extends RecordValue = RecordValue> {
}
public isAccount(): this is StorageStateItem<
Extract<RecordValue, { record?: 'account' }>
Extract<RecordValue, { account: unknown }>
> {
return (
this.type === IdentifierType.ACCOUNT && this.record.record === 'account'
);
return this.type === IdentifierType.ACCOUNT && this.record.account != null;
}
public isGroup(
group: Group,
): this is StorageStateItem<Extract<RecordValue, { record?: 'groupV2' }>> {
): this is StorageStateItem<Extract<RecordValue, { groupV2: unknown }>> {
if (this.type !== IdentifierType.GROUPV2) {
return false;
}
assert(this.record.record === 'groupV2', 'consistency check');
assert(this.record.groupV2 != null, 'consistency check');
const masterKey = this.record.groupV2.masterKey;
if (!masterKey) {
@ -118,11 +118,11 @@ class StorageStateItem<Value extends RecordValue = RecordValue> {
public isContact(
device: Device,
serviceIdKind: ServiceIdKind,
): this is StorageStateItem<Extract<RecordValue, { record?: 'contact' }>> {
): this is StorageStateItem<Extract<RecordValue, { contact: unknown }>> {
if (this.type !== IdentifierType.CONTACT) {
return false;
}
assert(this.record.record === 'contact', 'consistency check');
assert(this.record.contact != null, 'consistency check');
if (serviceIdKind === ServiceIdKind.ACI) {
const existingAci = this.record.contact.aciBinary;
@ -327,7 +327,6 @@ export class StorageState {
return this.addItem({
type: IdentifierType.GROUPV2,
record: {
record: 'groupV2',
groupV2: {
...EMPTY_GROUP,
...diff,
@ -345,7 +344,6 @@ export class StorageState {
(item) => item.isGroup(group),
(record) => {
return {
record: 'groupV2',
groupV2: {
...record.groupV2,
...diff,
@ -367,10 +365,10 @@ export class StorageState {
assert(account, 'No account record found');
return (account.pinnedConversations ?? []).some((convo) => {
if (convo.identifier !== 'groupMasterKey') {
if (convo.identifier?.groupMasterKey == null) {
return false;
}
return group.masterKey.equals(convo.groupMasterKey);
return group.masterKey.equals(convo.identifier.groupMasterKey);
});
}
@ -386,7 +384,6 @@ export class StorageState {
return this.addItem({
type: IdentifierType.CONTACT,
record: {
record: 'contact',
contact: {
...EMPTY_CONTACT,
aciBinary:
@ -473,10 +470,10 @@ export class StorageState {
assert(account, 'No account record found');
return (account.pinnedConversations ?? []).some((convo) => {
if (convo.identifier !== 'contact') {
if (convo.identifier?.contact == null) {
return false;
}
const existing = convo.contact.serviceIdBinary;
const existing = convo.identifier.contact.serviceIdBinary;
return existing && Buffer.compare(existing, device.aciRawUuid) === 0;
});
}
@ -546,14 +543,14 @@ export class StorageState {
}
public getAllGroupRecords(): ReadonlyArray<
StorageStateRecord<Extract<RecordValue, { record?: 'groupV2' }>>
StorageStateRecord<Extract<RecordValue, { groupV2: unknown }>>
> {
return this.items
.filter(
(
item,
): item is StorageStateItem<
Extract<RecordValue, { record?: 'groupV2' }>
Extract<RecordValue, { groupV2: unknown }>
> => item.type === IdentifierType.GROUPV2,
)
.map((item) => item.toRecord());
@ -751,10 +748,10 @@ export class StorageState {
const newPinnedConversations = pinnedConversations?.slice() ?? [];
const existingIndex = newPinnedConversations.findIndex((convo) => {
if (convo.identifier !== 'contact') {
if (convo.identifier?.contact == null) {
return false;
}
const existing = convo.contact.serviceIdBinary;
const existing = convo.identifier.contact.serviceIdBinary;
return (
existing && Buffer.compare(existing, deviceServiceIdBinary) === 0
);
@ -762,10 +759,11 @@ export class StorageState {
if (isPinned && existingIndex === -1) {
newPinnedConversations.push({
identifier: 'contact',
contact: {
e164: null,
serviceIdBinary: deviceServiceIdBinary,
identifier: {
contact: {
e164: null,
serviceIdBinary: deviceServiceIdBinary,
},
},
});
} else if (!isPinned && existingIndex !== -1) {
@ -773,7 +771,6 @@ export class StorageState {
}
return {
record: 'account',
account: {
...account,
pinnedConversations: newPinnedConversations,
@ -794,23 +791,23 @@ export class StorageState {
const newPinnedConversations = pinnedConversations?.slice() ?? [];
const existingIndex = newPinnedConversations.findIndex((convo) => {
if (convo.identifier !== 'groupMasterKey') {
if (convo.identifier?.groupMasterKey == null) {
return false;
}
return group.masterKey.equals(convo.groupMasterKey);
return group.masterKey.equals(convo.identifier.groupMasterKey);
});
if (isPinned && existingIndex === -1) {
newPinnedConversations.push({
identifier: 'groupMasterKey',
groupMasterKey: group.masterKey,
identifier: {
groupMasterKey: group.masterKey,
},
});
} else if (!isPinned && existingIndex !== -1) {
newPinnedConversations.splice(existingIndex, 1);
}
return {
record: 'account',
account: {
...account,
pinnedConversations: newPinnedConversations,

View File

@ -16,8 +16,9 @@ export function attachmentToPointer(
): Proto.AttachmentPointer.Params {
return {
contentType: 'application/octet-stream',
attachmentIdentifier: 'cdnKey',
cdnKey,
attachmentIdentifier: {
cdnKey,
},
key: attachment.key,
size: attachment.size,
digest: attachment.digest,