@ryuu-reinzz/baileys 3.0.0-beta.2 → 3.0.0-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,69 +3,129 @@ import { proto } from '../../WAProto/index.js';
3
3
  import { NOISE_MODE, WA_CERT_DETAILS } from '../Defaults/index.js';
4
4
  import { decodeBinaryNode } from '../WABinary/index.js';
5
5
  import { aesDecryptGCM, aesEncryptGCM, Curve, hkdf, sha256 } from './crypto.js';
6
+ const IV_LENGTH = 12;
7
+ const EMPTY_BUFFER = Buffer.alloc(0);
6
8
  const generateIV = (counter) => {
7
- const iv = new ArrayBuffer(12);
9
+ const iv = new ArrayBuffer(IV_LENGTH);
8
10
  new DataView(iv).setUint32(8, counter);
9
11
  return new Uint8Array(iv);
10
12
  };
13
+ class TransportState {
14
+ constructor(encKey, decKey) {
15
+ this.encKey = encKey;
16
+ this.decKey = decKey;
17
+ this.readCounter = 0;
18
+ this.writeCounter = 0;
19
+ this.iv = new Uint8Array(IV_LENGTH);
20
+ }
21
+ encrypt(plaintext) {
22
+ const c = this.writeCounter++;
23
+ this.iv[8] = (c >>> 24) & 0xff;
24
+ this.iv[9] = (c >>> 16) & 0xff;
25
+ this.iv[10] = (c >>> 8) & 0xff;
26
+ this.iv[11] = c & 0xff;
27
+ return aesEncryptGCM(plaintext, this.encKey, this.iv, EMPTY_BUFFER);
28
+ }
29
+ decrypt(ciphertext) {
30
+ const c = this.readCounter++;
31
+ this.iv[8] = (c >>> 24) & 0xff;
32
+ this.iv[9] = (c >>> 16) & 0xff;
33
+ this.iv[10] = (c >>> 8) & 0xff;
34
+ this.iv[11] = c & 0xff;
35
+ return aesDecryptGCM(ciphertext, this.decKey, this.iv, EMPTY_BUFFER);
36
+ }
37
+ }
11
38
  export const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey }, NOISE_HEADER, logger, routingInfo }) => {
12
39
  logger = logger.child({ class: 'ns' });
40
+ const data = Buffer.from(NOISE_MODE);
41
+ let hash = data.byteLength === 32 ? data : sha256(data);
42
+ let salt = hash;
43
+ let encKey = hash;
44
+ let decKey = hash;
45
+ let counter = 0;
46
+ let sentIntro = false;
47
+ let inBytes = Buffer.alloc(0);
48
+ let transport = null;
49
+ let isWaitingForTransport = false;
50
+ let pendingOnFrame = null;
51
+ let introHeader;
52
+ if (routingInfo) {
53
+ introHeader = Buffer.alloc(7 + routingInfo.byteLength + NOISE_HEADER.length);
54
+ introHeader.write('ED', 0, 'utf8');
55
+ introHeader.writeUint8(0, 2);
56
+ introHeader.writeUint8(1, 3);
57
+ introHeader.writeUint8(routingInfo.byteLength >> 16, 4);
58
+ introHeader.writeUint16BE(routingInfo.byteLength & 65535, 5);
59
+ introHeader.set(routingInfo, 7);
60
+ introHeader.set(NOISE_HEADER, 7 + routingInfo.byteLength);
61
+ }
62
+ else {
63
+ introHeader = Buffer.from(NOISE_HEADER);
64
+ }
13
65
  const authenticate = (data) => {
14
- if (!isFinished) {
66
+ if (!transport) {
15
67
  hash = sha256(Buffer.concat([hash, data]));
16
68
  }
17
69
  };
18
70
  const encrypt = (plaintext) => {
19
- const result = aesEncryptGCM(plaintext, encKey, generateIV(writeCounter), hash);
20
- writeCounter += 1;
71
+ if (transport) {
72
+ return transport.encrypt(plaintext);
73
+ }
74
+ const result = aesEncryptGCM(plaintext, encKey, generateIV(counter++), hash);
21
75
  authenticate(result);
22
76
  return result;
23
77
  };
24
78
  const decrypt = (ciphertext) => {
25
- // before the handshake is finished, we use the same counter
26
- // after handshake, the counters are different
27
- const iv = generateIV(isFinished ? readCounter : writeCounter);
28
- const result = aesDecryptGCM(ciphertext, decKey, iv, hash);
29
- if (isFinished) {
30
- readCounter += 1;
31
- }
32
- else {
33
- writeCounter += 1;
79
+ if (transport) {
80
+ return transport.decrypt(ciphertext);
34
81
  }
82
+ const result = aesDecryptGCM(ciphertext, decKey, generateIV(counter++), hash);
35
83
  authenticate(ciphertext);
36
84
  return result;
37
85
  };
38
- const localHKDF = async (data) => {
39
- const key = await hkdf(Buffer.from(data), 64, { salt, info: '' });
40
- return [key.slice(0, 32), key.slice(32)];
86
+ const localHKDF = (data) => {
87
+ const key = hkdf(Buffer.from(data), 64, { salt, info: '' });
88
+ return [key.subarray(0, 32), key.subarray(32)];
41
89
  };
42
- const mixIntoKey = async (data) => {
43
- const [write, read] = await localHKDF(data);
90
+ const mixIntoKey = (data) => {
91
+ const [write, read] = localHKDF(data);
44
92
  salt = write;
45
93
  encKey = read;
46
94
  decKey = read;
47
- readCounter = 0;
48
- writeCounter = 0;
95
+ counter = 0;
49
96
  };
50
97
  const finishInit = async () => {
51
- const [write, read] = await localHKDF(new Uint8Array(0));
52
- encKey = write;
53
- decKey = read;
54
- hash = Buffer.from([]);
55
- readCounter = 0;
56
- writeCounter = 0;
57
- isFinished = true;
98
+ isWaitingForTransport = true;
99
+ const [write, read] = localHKDF(new Uint8Array(0));
100
+ transport = new TransportState(write, read);
101
+ isWaitingForTransport = false;
102
+ logger.trace('Noise handler transitioned to Transport state');
103
+ if (pendingOnFrame) {
104
+ logger.trace({ length: inBytes.length }, 'Flushing buffered frames after transport ready');
105
+ await processData(pendingOnFrame);
106
+ pendingOnFrame = null;
107
+ }
108
+ };
109
+ const processData = async (onFrame) => {
110
+ let size;
111
+ while (true) {
112
+ if (inBytes.length < 3)
113
+ return;
114
+ size = (inBytes[0] << 16) | (inBytes[1] << 8) | inBytes[2];
115
+ if (inBytes.length < size + 3)
116
+ return;
117
+ let frame = inBytes.subarray(3, size + 3);
118
+ inBytes = inBytes.subarray(size + 3);
119
+ if (transport) {
120
+ const result = transport.decrypt(frame);
121
+ frame = await decodeBinaryNode(result);
122
+ }
123
+ if (logger.level === 'trace') {
124
+ logger.trace({ msg: frame?.attrs?.id }, 'recv frame');
125
+ }
126
+ onFrame(frame);
127
+ }
58
128
  };
59
- const data = Buffer.from(NOISE_MODE);
60
- let hash = data.byteLength === 32 ? data : sha256(data);
61
- let salt = hash;
62
- let encKey = hash;
63
- let decKey = hash;
64
- let readCounter = 0;
65
- let writeCounter = 0;
66
- let isFinished = false;
67
- let sentIntro = false;
68
- let inBytes = Buffer.alloc(0);
69
129
  authenticate(NOISE_HEADER);
70
130
  authenticate(publicKey);
71
131
  return {
@@ -74,73 +134,67 @@ export const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publi
74
134
  authenticate,
75
135
  mixIntoKey,
76
136
  finishInit,
77
- processHandshake: async ({ serverHello }, noiseKey) => {
137
+ processHandshake: ({ serverHello }, noiseKey) => {
78
138
  authenticate(serverHello.ephemeral);
79
- await mixIntoKey(Curve.sharedKey(privateKey, serverHello.ephemeral));
139
+ mixIntoKey(Curve.sharedKey(privateKey, serverHello.ephemeral));
80
140
  const decStaticContent = decrypt(serverHello.static);
81
- await mixIntoKey(Curve.sharedKey(privateKey, decStaticContent));
141
+ mixIntoKey(Curve.sharedKey(privateKey, decStaticContent));
82
142
  const certDecoded = decrypt(serverHello.payload);
83
- const { intermediate: certIntermediate /*leaf*/ } = proto.CertChain.decode(certDecoded);
84
- // TODO: handle this leaf stuff
85
- const { issuerSerial } = proto.CertChain.NoiseCertificate.Details.decode(certIntermediate.details);
143
+ const { intermediate: certIntermediate, leaf } = proto.CertChain.decode(certDecoded);
144
+ // leaf
145
+ if (!leaf?.details || !leaf?.signature) {
146
+ throw new Boom('invalid noise leaf certificate', { statusCode: 400 });
147
+ }
148
+ if (!certIntermediate?.details || !certIntermediate?.signature) {
149
+ throw new Boom('invalid noise intermediate certificate', { statusCode: 400 });
150
+ }
151
+ const details = proto.CertChain.NoiseCertificate.Details.decode(certIntermediate.details);
152
+ const { issuerSerial } = details;
153
+ const verify = Curve.verify(details.key, leaf.details, leaf.signature);
154
+ const verifyIntermediate = Curve.verify(WA_CERT_DETAILS.PUBLIC_KEY, certIntermediate.details, certIntermediate.signature);
155
+ if (!verify) {
156
+ throw new Boom('noise certificate signature invalid', { statusCode: 400 });
157
+ }
158
+ if (!verifyIntermediate) {
159
+ throw new Boom('noise intermediate certificate signature invalid', { statusCode: 400 });
160
+ }
86
161
  if (issuerSerial !== WA_CERT_DETAILS.SERIAL) {
87
162
  throw new Boom('certification match failed', { statusCode: 400 });
88
163
  }
89
164
  const keyEnc = encrypt(noiseKey.public);
90
- await mixIntoKey(Curve.sharedKey(noiseKey.private, serverHello.ephemeral));
165
+ mixIntoKey(Curve.sharedKey(noiseKey.private, serverHello.ephemeral));
91
166
  return keyEnc;
92
167
  },
93
168
  encodeFrame: (data) => {
94
- if (isFinished) {
95
- data = encrypt(data);
96
- }
97
- let header;
98
- if (routingInfo) {
99
- header = Buffer.alloc(7);
100
- header.write('ED', 0, 'utf8');
101
- header.writeUint8(0, 2);
102
- header.writeUint8(1, 3);
103
- header.writeUint8(routingInfo.byteLength >> 16, 4);
104
- header.writeUint16BE(routingInfo.byteLength & 65535, 5);
105
- header = Buffer.concat([header, routingInfo, NOISE_HEADER]);
106
- }
107
- else {
108
- header = Buffer.from(NOISE_HEADER);
169
+ if (transport) {
170
+ data = transport.encrypt(data);
109
171
  }
110
- const introSize = sentIntro ? 0 : header.length;
111
- const frame = Buffer.alloc(introSize + 3 + data.byteLength);
172
+ const dataLen = data.byteLength;
173
+ const introSize = sentIntro ? 0 : introHeader.length;
174
+ const frame = Buffer.allocUnsafe(introSize + 3 + dataLen);
112
175
  if (!sentIntro) {
113
- frame.set(header);
176
+ frame.set(introHeader);
114
177
  sentIntro = true;
115
178
  }
116
- frame.writeUInt8(data.byteLength >> 16, introSize);
117
- frame.writeUInt16BE(65535 & data.byteLength, introSize + 1);
179
+ frame[introSize] = (dataLen >>> 16) & 0xff;
180
+ frame[introSize + 1] = (dataLen >>> 8) & 0xff;
181
+ frame[introSize + 2] = dataLen & 0xff;
118
182
  frame.set(data, introSize + 3);
119
183
  return frame;
120
184
  },
121
185
  decodeFrame: async (newData, onFrame) => {
122
- // the binary protocol uses its own framing mechanism
123
- // on top of the WS frames
124
- // so we get this data and separate out the frames
125
- const getBytesSize = () => {
126
- if (inBytes.length >= 3) {
127
- return (inBytes.readUInt8() << 16) | inBytes.readUInt16BE(1);
128
- }
129
- };
130
- inBytes = Buffer.concat([inBytes, newData]);
131
- logger.trace(`recv ${newData.length} bytes, total recv ${inBytes.length} bytes`);
132
- let size = getBytesSize();
133
- while (size && inBytes.length >= size + 3) {
134
- let frame = inBytes.slice(3, size + 3);
135
- inBytes = inBytes.slice(size + 3);
136
- if (isFinished) {
137
- const result = decrypt(frame);
138
- frame = await decodeBinaryNode(result);
139
- }
140
- logger.trace({ msg: frame?.attrs?.id }, 'recv frame');
141
- onFrame(frame);
142
- size = getBytesSize();
186
+ if (isWaitingForTransport) {
187
+ inBytes = Buffer.concat([inBytes, newData]);
188
+ pendingOnFrame = onFrame;
189
+ return;
190
+ }
191
+ if (inBytes.length === 0) {
192
+ inBytes = Buffer.from(newData);
193
+ }
194
+ else {
195
+ inBytes = Buffer.concat([inBytes, newData]);
143
196
  }
197
+ await processData(onFrame);
144
198
  }
145
199
  };
146
200
  };
@@ -167,7 +167,13 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
167
167
  ]
168
168
  });
169
169
  }
170
- const data = await downloadAndProcessHistorySyncNotification(histNotification, options);
170
+ const data = await downloadAndProcessHistorySyncNotification(histNotification, options, logger);
171
+ if (data.lidPnMappings?.length) {
172
+ logger?.debug({ count: data.lidPnMappings.length }, 'processing LID-PN mappings from history sync');
173
+ await signalRepository.lidMapping
174
+ .storeLIDPNMappings(data.lidPnMappings)
175
+ .catch(err => logger?.warn({ err }, 'failed to store LID-PN mappings from history sync'));
176
+ }
171
177
  ev.emit('messaging-history.set', {
172
178
  ...data,
173
179
  isLatest: histNotification.syncType !== proto.HistorySync.HistorySyncType.ON_DEMAND ? isLatest : undefined,
@@ -215,23 +221,48 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
215
221
  case proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_RESPONSE_MESSAGE:
216
222
  const response = protocolMsg.peerDataOperationRequestResponseMessage;
217
223
  if (response) {
218
- await placeholderResendCache?.del(response.stanzaId);
219
224
  // TODO: IMPLEMENT HISTORY SYNC ETC (sticker uploads etc.).
220
- const { peerDataOperationResult } = response;
225
+ const peerDataOperationResult = response.peerDataOperationResult || [];
221
226
  for (const result of peerDataOperationResult) {
222
- const { placeholderMessageResendResponse: retryResponse } = result;
227
+ const retryResponse = result?.placeholderMessageResendResponse;
228
+ //eslint-disable-next-line max-depth
229
+ if (!retryResponse?.webMessageInfoBytes) {
230
+ continue;
231
+ }
223
232
  //eslint-disable-next-line max-depth
224
- if (retryResponse) {
233
+ try {
225
234
  const webMessageInfo = proto.WebMessageInfo.decode(retryResponse.webMessageInfoBytes);
226
- // wait till another upsert event is available, don't want it to be part of the PDO response message
227
- // TODO: parse through proper message handling utilities (to add relevant key fields)
228
- setTimeout(() => {
229
- ev.emit('messages.upsert', {
230
- messages: [webMessageInfo],
231
- type: 'notify',
232
- requestId: response.stanzaId
233
- });
234
- }, 500);
235
+ const msgId = webMessageInfo.key?.id;
236
+ // Retrieve cached original message data (preserves LID details,
237
+ // timestamps, etc. that the phone may omit in its PDO response)
238
+ const cachedData = msgId ? await placeholderResendCache?.get(msgId) : undefined;
239
+ //eslint-disable-next-line max-depth
240
+ if (msgId) {
241
+ await placeholderResendCache?.del(msgId);
242
+ }
243
+ let finalMsg;
244
+ //eslint-disable-next-line max-depth
245
+ if (cachedData && typeof cachedData === 'object') {
246
+ // Apply decoded message content onto cached metadata (preserves LID etc.)
247
+ cachedData.message = webMessageInfo.message;
248
+ //eslint-disable-next-line max-depth
249
+ if (webMessageInfo.messageTimestamp) {
250
+ cachedData.messageTimestamp = webMessageInfo.messageTimestamp;
251
+ }
252
+ finalMsg = cachedData;
253
+ }
254
+ else {
255
+ finalMsg = webMessageInfo;
256
+ }
257
+ logger?.debug({ msgId, requestId: response.stanzaId }, 'received placeholder resend');
258
+ ev.emit('messages.upsert', {
259
+ messages: [finalMsg],
260
+ type: 'notify',
261
+ requestId: response.stanzaId
262
+ });
263
+ }
264
+ catch (err) {
265
+ logger?.warn({ err, stanzaId: response.stanzaId }, 'failed to decode placeholder resend response');
235
266
  }
236
267
  }
237
268
  }
@@ -254,6 +285,18 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
254
285
  }
255
286
  ]);
256
287
  break;
288
+ case proto.Message.ProtocolMessage.Type.GROUP_MEMBER_LABEL_CHANGE:
289
+ const labelAssociationMsg = protocolMsg.memberLabel;
290
+ if (labelAssociationMsg?.label) {
291
+ ev.emit('group.member-tag.update', {
292
+ groupId: chat.id,
293
+ label: labelAssociationMsg.label,
294
+ participant: message.key.participant,
295
+ participantAlt: message.key.participantAlt,
296
+ messageTimestamp: Number(message.messageTimestamp)
297
+ });
298
+ }
299
+ break;
257
300
  case proto.Message.ProtocolMessage.Type.LID_MIGRATION_MAPPING_SYNC:
258
301
  const encodedPayload = protocolMsg.lidMigrationMappingSyncMessage?.encodedMappingPayload;
259
302
  const { pnToLidMappings, chatDbMigrationTimestamp } = proto.LIDMigrationMappingSyncPayload.decode(encodedPayload);
@@ -0,0 +1,258 @@
1
+ import { createHmac } from 'crypto';
2
+ import { proto } from '../../WAProto/index.js';
3
+ import { hkdf } from './crypto.js';
4
+ const reportingFields = [
5
+ { f: 1 },
6
+ {
7
+ f: 3,
8
+ s: [{ f: 2 }, { f: 3 }, { f: 8 }, { f: 11 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }, { f: 25 }]
9
+ },
10
+ { f: 4, s: [{ f: 1 }, { f: 16 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }] },
11
+ { f: 5, s: [{ f: 3 }, { f: 4 }, { f: 5 }, { f: 16 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }] },
12
+ { f: 6, s: [{ f: 1 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }, { f: 30 }] },
13
+ { f: 7, s: [{ f: 2 }, { f: 7 }, { f: 10 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }, { f: 20 }] },
14
+ { f: 8, s: [{ f: 2 }, { f: 7 }, { f: 9 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }, { f: 21 }] },
15
+ { f: 9, s: [{ f: 2 }, { f: 6 }, { f: 7 }, { f: 13 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }, { f: 20 }] },
16
+ { f: 12, s: [{ f: 1 }, { f: 2 }, { f: 14, m: true }, { f: 15 }] },
17
+ { f: 18, s: [{ f: 6 }, { f: 16 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }] },
18
+ { f: 26, s: [{ f: 4 }, { f: 5 }, { f: 8 }, { f: 13 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }] },
19
+ { f: 28, s: [{ f: 1 }, { f: 2 }, { f: 4 }, { f: 5 }, { f: 6 }, { f: 7, s: [{ f: 21 }, { f: 22 }] }] },
20
+ { f: 37, s: [{ f: 1, m: true }] },
21
+ {
22
+ f: 49,
23
+ s: [
24
+ { f: 2 },
25
+ { f: 3, s: [{ f: 1 }, { f: 2 }] },
26
+ { f: 5, s: [{ f: 21 }, { f: 22 }] },
27
+ { f: 8, s: [{ f: 1 }, { f: 2 }] }
28
+ ]
29
+ },
30
+ { f: 53, s: [{ f: 1, m: true }] },
31
+ { f: 55, s: [{ f: 1, m: true }] },
32
+ { f: 58, s: [{ f: 1, m: true }] },
33
+ { f: 59, s: [{ f: 1, m: true }] },
34
+ {
35
+ f: 60,
36
+ s: [
37
+ { f: 2 },
38
+ { f: 3, s: [{ f: 1 }, { f: 2 }] },
39
+ { f: 5, s: [{ f: 21 }, { f: 22 }] },
40
+ { f: 8, s: [{ f: 1 }, { f: 2 }] }
41
+ ]
42
+ },
43
+ {
44
+ f: 64,
45
+ s: [
46
+ { f: 2 },
47
+ { f: 3, s: [{ f: 1 }, { f: 2 }] },
48
+ { f: 5, s: [{ f: 21 }, { f: 22 }] },
49
+ { f: 8, s: [{ f: 1 }, { f: 2 }] }
50
+ ]
51
+ },
52
+ { f: 66, s: [{ f: 2 }, { f: 6 }, { f: 7 }, { f: 13 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }, { f: 20 }] },
53
+ { f: 74, s: [{ f: 1, m: true }] },
54
+ { f: 87, s: [{ f: 1, m: true }] },
55
+ { f: 88, s: [{ f: 1 }, { f: 2, s: [{ f: 1 }] }, { f: 3, s: [{ f: 21 }, { f: 22 }] }] },
56
+ { f: 92, s: [{ f: 1, m: true }] },
57
+ { f: 93, s: [{ f: 1, m: true }] },
58
+ { f: 94, s: [{ f: 1, m: true }] }
59
+ ];
60
+ const compileReportingFields = (fields) => {
61
+ const map = new Map();
62
+ for (const f of fields) {
63
+ map.set(f.f, {
64
+ m: f.m,
65
+ children: f.s ? compileReportingFields(f.s) : undefined
66
+ });
67
+ }
68
+ return map;
69
+ };
70
+ const compiledReportingFields = compileReportingFields(reportingFields);
71
+ const EMPTY_MAP = new Map();
72
+ const ENC_SECRET_REPORT_TOKEN = 'Report Token';
73
+ const WIRE = {
74
+ VARINT: 0,
75
+ FIXED64: 1,
76
+ BYTES: 2,
77
+ FIXED32: 5
78
+ };
79
+ export const shouldIncludeReportingToken = (message) => !message.reactionMessage &&
80
+ !message.encReactionMessage &&
81
+ !message.encEventResponseMessage &&
82
+ !message.pollUpdateMessage;
83
+ const generateMsgSecretKey = (modificationType, origMsgId, origMsgSender, modificationSender, origMsgSecret) => {
84
+ const useCaseSecret = Buffer.concat([
85
+ Buffer.from(origMsgId, 'utf8'),
86
+ Buffer.from(origMsgSender, 'utf8'),
87
+ Buffer.from(modificationSender, 'utf8'),
88
+ Buffer.from(modificationType, 'utf8')
89
+ ]);
90
+ return hkdf(origMsgSecret, 32, { info: useCaseSecret.toString('latin1') });
91
+ };
92
+ const extractReportingTokenContent = (data, cfg) => {
93
+ const out = [];
94
+ let i = 0;
95
+ while (i < data.length) {
96
+ const tag = decodeVarint(data, i);
97
+ if (!tag.ok) {
98
+ return null;
99
+ }
100
+ const fieldNum = tag.value >> 3;
101
+ const wireType = tag.value & 0x7;
102
+ const fieldStart = i;
103
+ i += tag.bytes;
104
+ const fieldCfg = cfg.get(fieldNum);
105
+ const pushSlice = (end) => {
106
+ if (end > data.length) {
107
+ return false;
108
+ }
109
+ out.push({ num: fieldNum, bytes: data.subarray(fieldStart, end) });
110
+ i = end;
111
+ return true;
112
+ };
113
+ const skip = (end) => {
114
+ if (end > data.length) {
115
+ return false;
116
+ }
117
+ i = end;
118
+ return true;
119
+ };
120
+ if (wireType === WIRE.VARINT) {
121
+ const v = decodeVarint(data, i);
122
+ if (!v.ok) {
123
+ return null;
124
+ }
125
+ const end = i + v.bytes;
126
+ if (!fieldCfg) {
127
+ if (!skip(end)) {
128
+ return null;
129
+ }
130
+ continue;
131
+ }
132
+ if (!pushSlice(end)) {
133
+ return null;
134
+ }
135
+ continue;
136
+ }
137
+ if (wireType === WIRE.FIXED64) {
138
+ const end = i + 8;
139
+ if (!fieldCfg) {
140
+ if (!skip(end)) {
141
+ return null;
142
+ }
143
+ continue;
144
+ }
145
+ if (!pushSlice(end)) {
146
+ return null;
147
+ }
148
+ continue;
149
+ }
150
+ if (wireType === WIRE.FIXED32) {
151
+ const end = i + 4;
152
+ if (!fieldCfg) {
153
+ if (!skip(end)) {
154
+ return null;
155
+ }
156
+ continue;
157
+ }
158
+ if (!pushSlice(end)) {
159
+ return null;
160
+ }
161
+ continue;
162
+ }
163
+ if (wireType === WIRE.BYTES) {
164
+ const len = decodeVarint(data, i);
165
+ if (!len.ok) {
166
+ return null;
167
+ }
168
+ const valStart = i + len.bytes;
169
+ const valEnd = valStart + len.value;
170
+ if (valEnd > data.length) {
171
+ return null;
172
+ }
173
+ if (!fieldCfg) {
174
+ i = valEnd;
175
+ continue;
176
+ }
177
+ if (fieldCfg.m || fieldCfg.children) {
178
+ const sub = extractReportingTokenContent(data.subarray(valStart, valEnd), fieldCfg.children ?? EMPTY_MAP);
179
+ if (sub === null) {
180
+ return null;
181
+ }
182
+ if (sub.length > 0) {
183
+ const newTag = encodeVarint(tag.value);
184
+ const newLen = encodeVarint(sub.length);
185
+ out.push({
186
+ num: fieldNum,
187
+ bytes: Buffer.concat([newTag, newLen, sub])
188
+ });
189
+ }
190
+ i = valEnd;
191
+ continue;
192
+ }
193
+ out.push({ num: fieldNum, bytes: data.subarray(fieldStart, valEnd) });
194
+ i = valEnd;
195
+ continue;
196
+ }
197
+ return null;
198
+ }
199
+ if (out.length === 0) {
200
+ return Buffer.alloc(0);
201
+ }
202
+ out.sort((a, b) => a.num - b.num);
203
+ return Buffer.concat(out.map(f => f.bytes));
204
+ };
205
+ const decodeVarint = (buffer, offset) => {
206
+ let value = 0;
207
+ let bytes = 0;
208
+ let shift = 0;
209
+ while (offset + bytes < buffer.length) {
210
+ const current = buffer[offset + bytes];
211
+ value |= (current & 0x7f) << shift;
212
+ bytes++;
213
+ if ((current & 0x80) === 0) {
214
+ return { value, bytes, ok: true };
215
+ }
216
+ shift += 7;
217
+ if (shift > 35) {
218
+ return { value: 0, bytes: 0, ok: false };
219
+ }
220
+ }
221
+ return { value: 0, bytes: 0, ok: false };
222
+ };
223
+ const encodeVarint = (value) => {
224
+ const parts = [];
225
+ let remaining = value >>> 0;
226
+ while (remaining > 0x7f) {
227
+ parts.push((remaining & 0x7f) | 0x80);
228
+ remaining >>>= 7;
229
+ }
230
+ parts.push(remaining);
231
+ return Buffer.from(parts);
232
+ };
233
+ export const getMessageReportingToken = async (msgProtobuf, message, key) => {
234
+ const msgSecret = message.messageContextInfo?.messageSecret;
235
+ if (!msgSecret || !key.id) {
236
+ return null;
237
+ }
238
+ const from = key.fromMe ? key.remoteJid : key.participant || key.remoteJid;
239
+ const to = key.fromMe ? key.participant || key.remoteJid : key.remoteJid;
240
+ const reportingSecret = generateMsgSecretKey(ENC_SECRET_REPORT_TOKEN, key.id, from, to, msgSecret);
241
+ const content = extractReportingTokenContent(msgProtobuf, compiledReportingFields);
242
+ if (!content || content.length === 0) {
243
+ return null;
244
+ }
245
+ const reportingToken = createHmac('sha256', reportingSecret).update(content).digest().subarray(0, 16);
246
+ return {
247
+ tag: 'reporting',
248
+ attrs: {},
249
+ content: [
250
+ {
251
+ tag: 'reporting_token',
252
+ attrs: { v: '2' },
253
+ content: reportingToken
254
+ }
255
+ ]
256
+ };
257
+ };
258
+ //# sourceMappingURL=reporting-utils.js.map