@hamradio/meshcore 1.1.2 → 1.2.0
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.
- package/README.md +55 -3
- package/dist/index.d.mts +137 -147
- package/dist/index.d.ts +137 -147
- package/dist/index.js +563 -249
- package/dist/index.mjs +558 -249
- package/dist/packet.js +4 -4
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,51 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
1
|
+
// src/packet.types.ts
|
|
2
|
+
var RouteType = /* @__PURE__ */ ((RouteType2) => {
|
|
3
|
+
RouteType2[RouteType2["TRANSPORT_FLOOD"] = 0] = "TRANSPORT_FLOOD";
|
|
4
|
+
RouteType2[RouteType2["FLOOD"] = 1] = "FLOOD";
|
|
5
|
+
RouteType2[RouteType2["DIRECT"] = 2] = "DIRECT";
|
|
6
|
+
RouteType2[RouteType2["TRANSPORT_DIRECT"] = 3] = "TRANSPORT_DIRECT";
|
|
7
|
+
return RouteType2;
|
|
8
|
+
})(RouteType || {});
|
|
9
|
+
var PayloadType = /* @__PURE__ */ ((PayloadType2) => {
|
|
10
|
+
PayloadType2[PayloadType2["REQUEST"] = 0] = "REQUEST";
|
|
11
|
+
PayloadType2[PayloadType2["RESPONSE"] = 1] = "RESPONSE";
|
|
12
|
+
PayloadType2[PayloadType2["TEXT"] = 2] = "TEXT";
|
|
13
|
+
PayloadType2[PayloadType2["ACK"] = 3] = "ACK";
|
|
14
|
+
PayloadType2[PayloadType2["ADVERT"] = 4] = "ADVERT";
|
|
15
|
+
PayloadType2[PayloadType2["GROUP_TEXT"] = 5] = "GROUP_TEXT";
|
|
16
|
+
PayloadType2[PayloadType2["GROUP_DATA"] = 6] = "GROUP_DATA";
|
|
17
|
+
PayloadType2[PayloadType2["ANON_REQ"] = 7] = "ANON_REQ";
|
|
18
|
+
PayloadType2[PayloadType2["PATH"] = 8] = "PATH";
|
|
19
|
+
PayloadType2[PayloadType2["TRACE"] = 9] = "TRACE";
|
|
20
|
+
PayloadType2[PayloadType2["RAW_CUSTOM"] = 15] = "RAW_CUSTOM";
|
|
21
|
+
return PayloadType2;
|
|
22
|
+
})(PayloadType || {});
|
|
23
|
+
var RequestType = /* @__PURE__ */ ((RequestType2) => {
|
|
24
|
+
RequestType2[RequestType2["GET_STATS"] = 1] = "GET_STATS";
|
|
25
|
+
RequestType2[RequestType2["KEEP_ALIVE"] = 2] = "KEEP_ALIVE";
|
|
26
|
+
RequestType2[RequestType2["GET_TELEMETRY"] = 3] = "GET_TELEMETRY";
|
|
27
|
+
RequestType2[RequestType2["GET_MIN_MAX_AVG"] = 4] = "GET_MIN_MAX_AVG";
|
|
28
|
+
RequestType2[RequestType2["GET_ACL"] = 5] = "GET_ACL";
|
|
29
|
+
RequestType2[RequestType2["GET_NEIGHBORS"] = 6] = "GET_NEIGHBORS";
|
|
30
|
+
RequestType2[RequestType2["GET_OWNER_INFO"] = 7] = "GET_OWNER_INFO";
|
|
31
|
+
return RequestType2;
|
|
32
|
+
})(RequestType || {});
|
|
33
|
+
var TextType = /* @__PURE__ */ ((TextType2) => {
|
|
34
|
+
TextType2[TextType2["PLAIN_TEXT"] = 0] = "PLAIN_TEXT";
|
|
35
|
+
TextType2[TextType2["CLI_COMMAND"] = 1] = "CLI_COMMAND";
|
|
36
|
+
TextType2[TextType2["SIGNED_PLAIN_TEXT"] = 2] = "SIGNED_PLAIN_TEXT";
|
|
37
|
+
return TextType2;
|
|
38
|
+
})(TextType || {});
|
|
39
|
+
var NodeType = /* @__PURE__ */ ((NodeType2) => {
|
|
40
|
+
NodeType2[NodeType2["CHAT_NODE"] = 1] = "CHAT_NODE";
|
|
41
|
+
NodeType2[NodeType2["REPEATER"] = 2] = "REPEATER";
|
|
42
|
+
NodeType2[NodeType2["ROOM_SERVER"] = 3] = "ROOM_SERVER";
|
|
43
|
+
NodeType2[NodeType2["SENSOR_NODE"] = 4] = "SENSOR_NODE";
|
|
44
|
+
return NodeType2;
|
|
45
|
+
})(NodeType || {});
|
|
46
|
+
|
|
47
|
+
// src/packet.ts
|
|
3
48
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
4
|
-
import { hmac } from "@noble/hashes/hmac.js";
|
|
5
|
-
import { ecb } from "@noble/ciphers/aes.js";
|
|
6
49
|
|
|
7
50
|
// src/parser.ts
|
|
8
51
|
import { equalBytes } from "@noble/ciphers/utils.js";
|
|
@@ -122,7 +165,510 @@ var BufferWriter = class {
|
|
|
122
165
|
}
|
|
123
166
|
};
|
|
124
167
|
|
|
168
|
+
// src/packet.ts
|
|
169
|
+
var Packet = class _Packet {
|
|
170
|
+
constructor(header, transport, pathLength, path, payload) {
|
|
171
|
+
this.header = header;
|
|
172
|
+
this.transport = transport;
|
|
173
|
+
this.pathLength = pathLength;
|
|
174
|
+
this.path = path;
|
|
175
|
+
this.payload = payload;
|
|
176
|
+
this.routeType = header & 3;
|
|
177
|
+
this.payloadVersion = header >> 6 & 3;
|
|
178
|
+
this.payloadType = header >> 2 & 15;
|
|
179
|
+
this.pathHashSize = (pathLength >> 6) + 1;
|
|
180
|
+
this.pathHashCount = pathLength & 63;
|
|
181
|
+
this.pathHashBytes = this.pathHashCount * this.pathHashSize;
|
|
182
|
+
this.pathHashes = [];
|
|
183
|
+
for (let i = 0; i < this.pathHashBytes; i += this.pathHashSize) {
|
|
184
|
+
const hashBytes = this.path.slice(i, i + this.pathHashSize);
|
|
185
|
+
const hashHex = bytesToHex(hashBytes);
|
|
186
|
+
this.pathHashes.push(hashHex);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
static fromBytes(bytes) {
|
|
190
|
+
if (typeof bytes === "string") {
|
|
191
|
+
bytes = base64ToBytes(bytes);
|
|
192
|
+
}
|
|
193
|
+
let offset = 0;
|
|
194
|
+
const header = bytes[offset++];
|
|
195
|
+
const routeType = header & 3;
|
|
196
|
+
let transport;
|
|
197
|
+
if (_Packet.hasTransportCodes(routeType)) {
|
|
198
|
+
const uitn16View = new DataView(bytes.buffer, bytes.byteOffset + offset, 4);
|
|
199
|
+
transport = [uitn16View.getUint16(0, false), uitn16View.getUint16(2, false)];
|
|
200
|
+
offset += 4;
|
|
201
|
+
}
|
|
202
|
+
const pathLength = bytes[offset++];
|
|
203
|
+
const path = bytes.slice(offset, offset + pathLength);
|
|
204
|
+
offset += pathLength;
|
|
205
|
+
const payload = bytes.slice(offset);
|
|
206
|
+
return new _Packet(header, transport, pathLength, path, payload);
|
|
207
|
+
}
|
|
208
|
+
static hasTransportCodes(routeType) {
|
|
209
|
+
return routeType === 0 /* TRANSPORT_FLOOD */ || routeType === 3 /* TRANSPORT_DIRECT */;
|
|
210
|
+
}
|
|
211
|
+
hash() {
|
|
212
|
+
const hash = sha256.create();
|
|
213
|
+
hash.update(new Uint8Array([this.payloadType]));
|
|
214
|
+
if (this.payloadType === 9 /* TRACE */) {
|
|
215
|
+
hash.update(new Uint8Array([this.pathLength]));
|
|
216
|
+
}
|
|
217
|
+
hash.update(this.payload);
|
|
218
|
+
const digest = hash.digest();
|
|
219
|
+
return bytesToHex(digest.slice(0, 8));
|
|
220
|
+
}
|
|
221
|
+
ensureStructure() {
|
|
222
|
+
if (typeof this.structure !== "undefined") {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
let pathHashType;
|
|
226
|
+
switch (this.pathHashSize) {
|
|
227
|
+
case 1:
|
|
228
|
+
pathHashType = 6 /* BYTES */;
|
|
229
|
+
break;
|
|
230
|
+
case 2:
|
|
231
|
+
pathHashType = 7 /* WORDS */;
|
|
232
|
+
break;
|
|
233
|
+
case 4:
|
|
234
|
+
pathHashType = 8 /* DWORDS */;
|
|
235
|
+
break;
|
|
236
|
+
default:
|
|
237
|
+
throw new Error(`Unsupported path hash size: ${this.pathHashSize}`);
|
|
238
|
+
}
|
|
239
|
+
this.structure = [
|
|
240
|
+
/* Header segment */
|
|
241
|
+
{ name: "header", data: new Uint8Array([this.header, this.pathLength, ...this.path]), fields: [
|
|
242
|
+
/* Header flags */
|
|
243
|
+
{
|
|
244
|
+
name: "flags",
|
|
245
|
+
type: 0 /* BITS */,
|
|
246
|
+
size: 1,
|
|
247
|
+
bits: [
|
|
248
|
+
{ name: "route type", size: 2 },
|
|
249
|
+
{ name: "payload version", size: 2 },
|
|
250
|
+
{ name: "payload type", size: 4 }
|
|
251
|
+
]
|
|
252
|
+
},
|
|
253
|
+
/* Transport codes */
|
|
254
|
+
..._Packet.hasTransportCodes(this.routeType) ? [
|
|
255
|
+
{
|
|
256
|
+
name: "transport code 1",
|
|
257
|
+
type: 3 /* UINT16_BE */,
|
|
258
|
+
size: 2
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: "transport code 2",
|
|
262
|
+
type: 3 /* UINT16_BE */,
|
|
263
|
+
size: 2
|
|
264
|
+
}
|
|
265
|
+
] : [],
|
|
266
|
+
/* Path length and hashes */
|
|
267
|
+
{
|
|
268
|
+
name: "path length",
|
|
269
|
+
type: 1 /* UINT8 */,
|
|
270
|
+
size: 1,
|
|
271
|
+
bits: [
|
|
272
|
+
{ name: "path hash size", size: 2 },
|
|
273
|
+
{ name: "path hash count", size: 6 }
|
|
274
|
+
]
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: "path hashes",
|
|
278
|
+
type: pathHashType,
|
|
279
|
+
size: this.path.length
|
|
280
|
+
}
|
|
281
|
+
] }
|
|
282
|
+
];
|
|
283
|
+
}
|
|
284
|
+
decode(withStructure) {
|
|
285
|
+
let result;
|
|
286
|
+
switch (this.payloadType) {
|
|
287
|
+
case 0 /* REQUEST */:
|
|
288
|
+
result = this.decodeRequest(withStructure);
|
|
289
|
+
break;
|
|
290
|
+
case 1 /* RESPONSE */:
|
|
291
|
+
result = this.decodeResponse(withStructure);
|
|
292
|
+
break;
|
|
293
|
+
case 2 /* TEXT */:
|
|
294
|
+
result = this.decodeText(withStructure);
|
|
295
|
+
break;
|
|
296
|
+
case 3 /* ACK */:
|
|
297
|
+
result = this.decodeAck(withStructure);
|
|
298
|
+
break;
|
|
299
|
+
case 4 /* ADVERT */:
|
|
300
|
+
result = this.decodeAdvert(withStructure);
|
|
301
|
+
break;
|
|
302
|
+
case 5 /* GROUP_TEXT */:
|
|
303
|
+
result = this.decodeGroupText(withStructure);
|
|
304
|
+
break;
|
|
305
|
+
case 6 /* GROUP_DATA */:
|
|
306
|
+
result = this.decodeGroupData(withStructure);
|
|
307
|
+
break;
|
|
308
|
+
case 7 /* ANON_REQ */:
|
|
309
|
+
result = this.decodeAnonReq(withStructure);
|
|
310
|
+
break;
|
|
311
|
+
case 8 /* PATH */:
|
|
312
|
+
result = this.decodePath(withStructure);
|
|
313
|
+
break;
|
|
314
|
+
case 9 /* TRACE */:
|
|
315
|
+
result = this.decodeTrace(withStructure);
|
|
316
|
+
break;
|
|
317
|
+
case 15 /* RAW_CUSTOM */:
|
|
318
|
+
result = this.decodeRawCustom(withStructure);
|
|
319
|
+
break;
|
|
320
|
+
default:
|
|
321
|
+
throw new Error(`Unsupported payload type: ${this.payloadType}`);
|
|
322
|
+
}
|
|
323
|
+
console.log("packet decode with structure:", typeof withStructure, withStructure, { result });
|
|
324
|
+
if (typeof withStructure === "boolean" && withStructure && "segment" in result && "payload" in result) {
|
|
325
|
+
this.ensureStructure();
|
|
326
|
+
const structure = [...this.structure, result.segment];
|
|
327
|
+
return { payload: result.payload, structure };
|
|
328
|
+
}
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
decodeEncryptedPayload(reader) {
|
|
332
|
+
const cipherMAC = reader.readBytes(2);
|
|
333
|
+
const cipherText = reader.readBytes(reader.remainingBytes());
|
|
334
|
+
return { cipherMAC, cipherText };
|
|
335
|
+
}
|
|
336
|
+
decodeRequest(withSegment) {
|
|
337
|
+
if (this.payload.length < 4) {
|
|
338
|
+
throw new Error("Invalid request payload: too short");
|
|
339
|
+
}
|
|
340
|
+
const reader = new BufferReader(this.payload);
|
|
341
|
+
const dst = reader.readByte();
|
|
342
|
+
const src = reader.readByte();
|
|
343
|
+
const encrypted = this.decodeEncryptedPayload(reader);
|
|
344
|
+
const payload = {
|
|
345
|
+
type: 0 /* REQUEST */,
|
|
346
|
+
dst,
|
|
347
|
+
src,
|
|
348
|
+
encrypted
|
|
349
|
+
};
|
|
350
|
+
if (typeof withSegment === "boolean" && withSegment) {
|
|
351
|
+
const segment = {
|
|
352
|
+
name: "request payload",
|
|
353
|
+
data: this.payload,
|
|
354
|
+
fields: [
|
|
355
|
+
{ name: "destination hash", type: 1 /* UINT8 */, size: 1, value: dst },
|
|
356
|
+
{ name: "source hash", type: 1 /* UINT8 */, size: 1, value: src },
|
|
357
|
+
{ name: "cipher MAC", type: 6 /* BYTES */, size: 2, value: encrypted.cipherMAC },
|
|
358
|
+
{ name: "cipher text", type: 6 /* BYTES */, size: encrypted.cipherText.length, value: encrypted.cipherText }
|
|
359
|
+
]
|
|
360
|
+
};
|
|
361
|
+
return { payload, segment };
|
|
362
|
+
}
|
|
363
|
+
return payload;
|
|
364
|
+
}
|
|
365
|
+
decodeResponse(withSegment) {
|
|
366
|
+
if (this.payload.length < 4) {
|
|
367
|
+
throw new Error("Invalid response payload: too short");
|
|
368
|
+
}
|
|
369
|
+
const reader = new BufferReader(this.payload);
|
|
370
|
+
const dst = reader.readByte();
|
|
371
|
+
const src = reader.readByte();
|
|
372
|
+
const encrypted = this.decodeEncryptedPayload(reader);
|
|
373
|
+
const payload = {
|
|
374
|
+
type: 1 /* RESPONSE */,
|
|
375
|
+
dst,
|
|
376
|
+
src,
|
|
377
|
+
encrypted
|
|
378
|
+
};
|
|
379
|
+
if (typeof withSegment === "boolean" && withSegment) {
|
|
380
|
+
const segment = {
|
|
381
|
+
name: "response payload",
|
|
382
|
+
data: this.payload,
|
|
383
|
+
fields: [
|
|
384
|
+
{ name: "destination hash", type: 1 /* UINT8 */, size: 1, value: dst },
|
|
385
|
+
{ name: "source hash", type: 1 /* UINT8 */, size: 1, value: src },
|
|
386
|
+
{ name: "cipher MAC", type: 6 /* BYTES */, size: 2, value: encrypted.cipherMAC },
|
|
387
|
+
{ name: "cipher text", type: 6 /* BYTES */, size: encrypted.cipherText.length, value: encrypted.cipherText }
|
|
388
|
+
]
|
|
389
|
+
};
|
|
390
|
+
return { payload, segment };
|
|
391
|
+
}
|
|
392
|
+
return payload;
|
|
393
|
+
}
|
|
394
|
+
decodeText(withSegment) {
|
|
395
|
+
if (this.payload.length < 4) {
|
|
396
|
+
throw new Error("Invalid text payload: too short");
|
|
397
|
+
}
|
|
398
|
+
const reader = new BufferReader(this.payload);
|
|
399
|
+
const dst = reader.readByte();
|
|
400
|
+
const src = reader.readByte();
|
|
401
|
+
const encrypted = this.decodeEncryptedPayload(reader);
|
|
402
|
+
const payload = {
|
|
403
|
+
type: 2 /* TEXT */,
|
|
404
|
+
dst,
|
|
405
|
+
src,
|
|
406
|
+
encrypted
|
|
407
|
+
};
|
|
408
|
+
if (typeof withSegment === "boolean" && withSegment) {
|
|
409
|
+
const segment = {
|
|
410
|
+
name: "text payload",
|
|
411
|
+
data: this.payload,
|
|
412
|
+
fields: [
|
|
413
|
+
{ name: "destination hash", type: 1 /* UINT8 */, size: 1, value: dst },
|
|
414
|
+
{ name: "source hash", type: 1 /* UINT8 */, size: 1, value: src },
|
|
415
|
+
{ name: "cipher MAC", type: 6 /* BYTES */, size: 2, value: encrypted.cipherMAC },
|
|
416
|
+
{ name: "cipher text", type: 6 /* BYTES */, size: encrypted.cipherText.length, value: encrypted.cipherText }
|
|
417
|
+
]
|
|
418
|
+
};
|
|
419
|
+
return { payload, segment };
|
|
420
|
+
}
|
|
421
|
+
return payload;
|
|
422
|
+
}
|
|
423
|
+
decodeAck(withSegment) {
|
|
424
|
+
if (this.payload.length < 4) {
|
|
425
|
+
throw new Error("Invalid ack payload: too short");
|
|
426
|
+
}
|
|
427
|
+
const reader = new BufferReader(this.payload);
|
|
428
|
+
const checksum = reader.readBytes(4);
|
|
429
|
+
const payload = {
|
|
430
|
+
type: 3 /* ACK */,
|
|
431
|
+
checksum
|
|
432
|
+
};
|
|
433
|
+
if (typeof withSegment === "boolean" && withSegment) {
|
|
434
|
+
const segment = {
|
|
435
|
+
name: "ack payload",
|
|
436
|
+
data: this.payload,
|
|
437
|
+
fields: [
|
|
438
|
+
{ name: "checksum", type: 6 /* BYTES */, size: 4, value: checksum }
|
|
439
|
+
]
|
|
440
|
+
};
|
|
441
|
+
return { payload, segment };
|
|
442
|
+
}
|
|
443
|
+
return payload;
|
|
444
|
+
}
|
|
445
|
+
decodeAdvert(withSegment) {
|
|
446
|
+
if (this.payload.length < 4) {
|
|
447
|
+
throw new Error("Invalid advert payload: too short");
|
|
448
|
+
}
|
|
449
|
+
const reader = new BufferReader(this.payload);
|
|
450
|
+
const payload = {
|
|
451
|
+
type: 4 /* ADVERT */,
|
|
452
|
+
publicKey: reader.readBytes(32),
|
|
453
|
+
timestamp: reader.readTimestamp(),
|
|
454
|
+
signature: reader.readBytes(64)
|
|
455
|
+
};
|
|
456
|
+
let segment;
|
|
457
|
+
if (typeof withSegment === "boolean" && withSegment) {
|
|
458
|
+
segment = {
|
|
459
|
+
name: "advert payload",
|
|
460
|
+
data: this.payload,
|
|
461
|
+
fields: [
|
|
462
|
+
{ type: 6 /* BYTES */, name: "public key", size: 32 },
|
|
463
|
+
{ type: 4 /* UINT32_LE */, name: "timestamp", size: 4, value: payload.timestamp },
|
|
464
|
+
{ type: 6 /* BYTES */, name: "signature", size: 64 }
|
|
465
|
+
]
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
const flags = reader.readByte();
|
|
469
|
+
const appdata = {
|
|
470
|
+
nodeType: flags & 15,
|
|
471
|
+
hasLocation: (flags & 16 /* HAS_LOCATION */) !== 0,
|
|
472
|
+
hasFeature1: (flags & 32 /* HAS_FEATURE1 */) !== 0,
|
|
473
|
+
hasFeature2: (flags & 64 /* HAS_FEATURE2 */) !== 0,
|
|
474
|
+
hasName: (flags & 128 /* HAS_NAME */) !== 0
|
|
475
|
+
};
|
|
476
|
+
if (typeof withSegment === "boolean" && withSegment) {
|
|
477
|
+
segment.fields.push({ type: 0 /* BITS */, name: "flags", size: 1, value: flags, bits: [
|
|
478
|
+
{ size: 4, name: "node type" },
|
|
479
|
+
{ size: 1, name: "location flag" },
|
|
480
|
+
{ size: 1, name: "feature1 flag" },
|
|
481
|
+
{ size: 1, name: "feature2 flag" },
|
|
482
|
+
{ size: 1, name: "name flag" }
|
|
483
|
+
] });
|
|
484
|
+
}
|
|
485
|
+
if (appdata.hasLocation) {
|
|
486
|
+
const lat = reader.readInt32LE() / 1e5;
|
|
487
|
+
const lon = reader.readInt32LE() / 1e5;
|
|
488
|
+
appdata.location = [lat, lon];
|
|
489
|
+
if (typeof withSegment === "boolean" && withSegment) {
|
|
490
|
+
segment.fields.push({ type: 4 /* UINT32_LE */, name: "latitude", size: 4, value: lat });
|
|
491
|
+
segment.fields.push({ type: 4 /* UINT32_LE */, name: "longitude", size: 4, value: lon });
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (appdata.hasFeature1) {
|
|
495
|
+
appdata.feature1 = reader.readUint16LE();
|
|
496
|
+
if (typeof withSegment === "boolean" && withSegment) {
|
|
497
|
+
segment.fields.push({ type: 2 /* UINT16_LE */, name: "feature1", size: 2, value: appdata.feature1 });
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (appdata.hasFeature2) {
|
|
501
|
+
appdata.feature2 = reader.readUint16LE();
|
|
502
|
+
if (typeof withSegment === "boolean" && withSegment) {
|
|
503
|
+
segment.fields.push({ type: 2 /* UINT16_LE */, name: "feature2", size: 2, value: appdata.feature2 });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (appdata.hasName) {
|
|
507
|
+
const nameBytes = reader.readBytes();
|
|
508
|
+
let nullPos = nameBytes.indexOf(0);
|
|
509
|
+
if (nullPos === -1) {
|
|
510
|
+
nullPos = nameBytes.length;
|
|
511
|
+
}
|
|
512
|
+
appdata.name = new TextDecoder("utf-8").decode(nameBytes.subarray(0, nullPos));
|
|
513
|
+
if (typeof withSegment === "boolean" && withSegment) {
|
|
514
|
+
segment.fields.push({ type: 10 /* C_STRING */, name: "name", size: nameBytes.length, value: appdata.name });
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (typeof withSegment === "boolean" && withSegment && typeof segment !== "undefined") {
|
|
518
|
+
return { payload: { ...payload, appdata }, segment };
|
|
519
|
+
}
|
|
520
|
+
return { ...payload, appdata };
|
|
521
|
+
}
|
|
522
|
+
decodeGroupText(withSegment) {
|
|
523
|
+
if (this.payload.length < 3) {
|
|
524
|
+
throw new Error("Invalid group text payload: too short");
|
|
525
|
+
}
|
|
526
|
+
const reader = new BufferReader(this.payload);
|
|
527
|
+
const channelHash = reader.readByte();
|
|
528
|
+
const encrypted = this.decodeEncryptedPayload(reader);
|
|
529
|
+
const payload = {
|
|
530
|
+
type: 5 /* GROUP_TEXT */,
|
|
531
|
+
channelHash,
|
|
532
|
+
encrypted
|
|
533
|
+
};
|
|
534
|
+
if (typeof withSegment === "boolean" && withSegment) {
|
|
535
|
+
const segment = {
|
|
536
|
+
name: "group text payload",
|
|
537
|
+
data: this.payload,
|
|
538
|
+
fields: [
|
|
539
|
+
{ name: "channel hash", type: 1 /* UINT8 */, size: 1, value: channelHash },
|
|
540
|
+
{ name: "cipher MAC", type: 6 /* BYTES */, size: 2, value: encrypted.cipherMAC },
|
|
541
|
+
{ name: "cipher text", type: 6 /* BYTES */, size: encrypted.cipherText.length, value: encrypted.cipherText }
|
|
542
|
+
]
|
|
543
|
+
};
|
|
544
|
+
return { payload, segment };
|
|
545
|
+
}
|
|
546
|
+
return payload;
|
|
547
|
+
}
|
|
548
|
+
decodeGroupData(withSegment) {
|
|
549
|
+
if (this.payload.length < 3) {
|
|
550
|
+
throw new Error("Invalid group data payload: too short");
|
|
551
|
+
}
|
|
552
|
+
const reader = new BufferReader(this.payload);
|
|
553
|
+
const payload = {
|
|
554
|
+
type: 6 /* GROUP_DATA */,
|
|
555
|
+
channelHash: reader.readByte(),
|
|
556
|
+
encrypted: this.decodeEncryptedPayload(reader)
|
|
557
|
+
};
|
|
558
|
+
if (typeof withSegment === "boolean" && withSegment) {
|
|
559
|
+
const segment = {
|
|
560
|
+
name: "group data payload",
|
|
561
|
+
data: this.payload,
|
|
562
|
+
fields: [
|
|
563
|
+
{ name: "channel hash", type: 1 /* UINT8 */, size: 1, value: payload.channelHash },
|
|
564
|
+
{ name: "cipher MAC", type: 6 /* BYTES */, size: 2, value: payload.encrypted.cipherMAC },
|
|
565
|
+
{ name: "cipher text", type: 6 /* BYTES */, size: payload.encrypted.cipherText.length, value: payload.encrypted.cipherText }
|
|
566
|
+
]
|
|
567
|
+
};
|
|
568
|
+
return { payload, segment };
|
|
569
|
+
}
|
|
570
|
+
return payload;
|
|
571
|
+
}
|
|
572
|
+
decodeAnonReq(withSegment) {
|
|
573
|
+
if (this.payload.length < 1 + 32 + 2) {
|
|
574
|
+
throw new Error("Invalid anon req payload: too short");
|
|
575
|
+
}
|
|
576
|
+
const reader = new BufferReader(this.payload);
|
|
577
|
+
const payload = {
|
|
578
|
+
type: 7 /* ANON_REQ */,
|
|
579
|
+
dst: reader.readByte(),
|
|
580
|
+
publicKey: reader.readBytes(32),
|
|
581
|
+
encrypted: this.decodeEncryptedPayload(reader)
|
|
582
|
+
};
|
|
583
|
+
if (typeof withSegment === "boolean" && withSegment) {
|
|
584
|
+
const segment = {
|
|
585
|
+
name: "anon req payload",
|
|
586
|
+
data: this.payload,
|
|
587
|
+
fields: [
|
|
588
|
+
{ name: "destination hash", type: 1 /* UINT8 */, size: 1, value: payload.dst },
|
|
589
|
+
{ name: "public key", type: 6 /* BYTES */, size: 32, value: payload.publicKey },
|
|
590
|
+
{ name: "cipher MAC", type: 6 /* BYTES */, size: 2, value: payload.encrypted.cipherMAC },
|
|
591
|
+
{ name: "cipher text", type: 6 /* BYTES */, size: payload.encrypted.cipherText.length, value: payload.encrypted.cipherText }
|
|
592
|
+
]
|
|
593
|
+
};
|
|
594
|
+
return { payload, segment };
|
|
595
|
+
}
|
|
596
|
+
return payload;
|
|
597
|
+
}
|
|
598
|
+
decodePath(withSegment) {
|
|
599
|
+
if (this.payload.length < 2) {
|
|
600
|
+
throw new Error("Invalid path payload: too short");
|
|
601
|
+
}
|
|
602
|
+
const reader = new BufferReader(this.payload);
|
|
603
|
+
const payload = {
|
|
604
|
+
type: 8 /* PATH */,
|
|
605
|
+
dst: reader.readByte(),
|
|
606
|
+
src: reader.readByte()
|
|
607
|
+
};
|
|
608
|
+
if (typeof withSegment === "boolean" && withSegment) {
|
|
609
|
+
const segment = {
|
|
610
|
+
name: "path payload",
|
|
611
|
+
data: this.payload,
|
|
612
|
+
fields: [
|
|
613
|
+
{ name: "destination hash", type: 1 /* UINT8 */, size: 1, value: payload.dst },
|
|
614
|
+
{ name: "source hash", type: 1 /* UINT8 */, size: 1, value: payload.src }
|
|
615
|
+
]
|
|
616
|
+
};
|
|
617
|
+
return { payload, segment };
|
|
618
|
+
}
|
|
619
|
+
return payload;
|
|
620
|
+
}
|
|
621
|
+
decodeTrace(withSegment) {
|
|
622
|
+
if (this.payload.length < 9) {
|
|
623
|
+
throw new Error("Invalid trace payload: too short");
|
|
624
|
+
}
|
|
625
|
+
const reader = new BufferReader(this.payload);
|
|
626
|
+
const payload = {
|
|
627
|
+
type: 9 /* TRACE */,
|
|
628
|
+
tag: reader.readUint32LE() >>> 0,
|
|
629
|
+
authCode: reader.readUint32LE() >>> 0,
|
|
630
|
+
flags: reader.readByte() & 3,
|
|
631
|
+
nodes: reader.readBytes()
|
|
632
|
+
};
|
|
633
|
+
if (typeof withSegment === "boolean" && withSegment) {
|
|
634
|
+
const segment = {
|
|
635
|
+
name: "trace payload",
|
|
636
|
+
data: this.payload,
|
|
637
|
+
fields: [
|
|
638
|
+
{ name: "tag", type: 8 /* DWORDS */, size: 4, value: payload.tag },
|
|
639
|
+
{ name: "auth code", type: 8 /* DWORDS */, size: 4, value: payload.authCode },
|
|
640
|
+
{ name: "flags", type: 1 /* UINT8 */, size: 1, value: payload.flags },
|
|
641
|
+
{ name: "nodes", type: 6 /* BYTES */, size: payload.nodes.length, value: payload.nodes }
|
|
642
|
+
]
|
|
643
|
+
};
|
|
644
|
+
return { payload, segment };
|
|
645
|
+
}
|
|
646
|
+
return payload;
|
|
647
|
+
}
|
|
648
|
+
decodeRawCustom(withSegment) {
|
|
649
|
+
const payload = {
|
|
650
|
+
type: 15 /* RAW_CUSTOM */,
|
|
651
|
+
data: this.payload
|
|
652
|
+
};
|
|
653
|
+
if (typeof withSegment === "boolean" && withSegment) {
|
|
654
|
+
const segment = {
|
|
655
|
+
name: "raw custom payload",
|
|
656
|
+
data: this.payload,
|
|
657
|
+
fields: [
|
|
658
|
+
{ name: "data", type: 6 /* BYTES */, size: this.payload.length, value: this.payload }
|
|
659
|
+
]
|
|
660
|
+
};
|
|
661
|
+
return { payload, segment };
|
|
662
|
+
}
|
|
663
|
+
return payload;
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
|
|
125
667
|
// src/crypto.ts
|
|
668
|
+
import { ed25519, x25519 } from "@noble/curves/ed25519.js";
|
|
669
|
+
import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
|
|
670
|
+
import { hmac } from "@noble/hashes/hmac.js";
|
|
671
|
+
import { ecb } from "@noble/ciphers/aes.js";
|
|
126
672
|
var PUBLIC_KEY_SIZE = 32;
|
|
127
673
|
var SEED_SIZE = 32;
|
|
128
674
|
var HMAC_SIZE = 2;
|
|
@@ -141,7 +687,7 @@ var PublicKey = class _PublicKey {
|
|
|
141
687
|
}
|
|
142
688
|
}
|
|
143
689
|
toHash() {
|
|
144
|
-
return
|
|
690
|
+
return sha2562.create().update(this.key).digest()[0];
|
|
145
691
|
}
|
|
146
692
|
toBytes() {
|
|
147
693
|
return this.key;
|
|
@@ -274,7 +820,7 @@ var SharedSecret = class _SharedSecret {
|
|
|
274
820
|
return { hmac: hmac2, ciphertext };
|
|
275
821
|
}
|
|
276
822
|
calculateHmac(data) {
|
|
277
|
-
return hmac(
|
|
823
|
+
return hmac(sha2562, this.secret, data).slice(0, HMAC_SIZE);
|
|
278
824
|
}
|
|
279
825
|
static fromName(name) {
|
|
280
826
|
if (name === "Public") {
|
|
@@ -282,7 +828,7 @@ var SharedSecret = class _SharedSecret {
|
|
|
282
828
|
} else if (!/^#/.test(name)) {
|
|
283
829
|
throw new Error("Only the 'Public' group or groups starting with '#' are supported");
|
|
284
830
|
}
|
|
285
|
-
const hash =
|
|
831
|
+
const hash = sha2562.create().update(new TextEncoder().encode(name)).digest();
|
|
286
832
|
return new _SharedSecret(hash.slice(0, SHARED_SECRET_SIZE));
|
|
287
833
|
}
|
|
288
834
|
};
|
|
@@ -577,258 +1123,21 @@ var Contacts = class {
|
|
|
577
1123
|
throw new Error("Decryption failed with all known groups");
|
|
578
1124
|
}
|
|
579
1125
|
};
|
|
580
|
-
|
|
581
|
-
// src/packet.ts
|
|
582
|
-
import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
|
|
583
|
-
var Packet = class _Packet {
|
|
584
|
-
constructor(header, transport, pathLength, path, payload) {
|
|
585
|
-
this.header = header;
|
|
586
|
-
this.transport = transport;
|
|
587
|
-
this.pathLength = pathLength;
|
|
588
|
-
this.path = path;
|
|
589
|
-
this.payload = payload;
|
|
590
|
-
this.routeType = header & 3;
|
|
591
|
-
this.payloadVersion = header >> 6 & 3;
|
|
592
|
-
this.payloadType = header >> 2 & 15;
|
|
593
|
-
this.pathHashCount = (pathLength >> 6) + 1;
|
|
594
|
-
this.pathHashSize = pathLength & 63;
|
|
595
|
-
this.pathHashBytes = this.pathHashCount * this.pathHashSize;
|
|
596
|
-
this.pathHashes = [];
|
|
597
|
-
for (let i = 0; i < this.pathHashCount; i++) {
|
|
598
|
-
const hashBytes = this.path.slice(i * this.pathHashSize, (i + 1) * this.pathHashSize);
|
|
599
|
-
const hashHex = bytesToHex(hashBytes);
|
|
600
|
-
this.pathHashes.push(hashHex);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
static fromBytes(bytes) {
|
|
604
|
-
if (typeof bytes === "string") {
|
|
605
|
-
bytes = base64ToBytes(bytes);
|
|
606
|
-
}
|
|
607
|
-
let offset = 0;
|
|
608
|
-
const header = bytes[offset++];
|
|
609
|
-
const routeType = header & 3;
|
|
610
|
-
let transport;
|
|
611
|
-
if (_Packet.hasTransportCodes(routeType)) {
|
|
612
|
-
const uitn16View = new DataView(bytes.buffer, bytes.byteOffset + offset, 4);
|
|
613
|
-
transport = [uitn16View.getUint16(0, false), uitn16View.getUint16(2, false)];
|
|
614
|
-
offset += 4;
|
|
615
|
-
}
|
|
616
|
-
const pathLength = bytes[offset++];
|
|
617
|
-
const path = bytes.slice(offset, offset + pathLength);
|
|
618
|
-
offset += pathLength;
|
|
619
|
-
const payload = bytes.slice(offset);
|
|
620
|
-
return new _Packet(header, transport, pathLength, path, payload);
|
|
621
|
-
}
|
|
622
|
-
static hasTransportCodes(routeType) {
|
|
623
|
-
return routeType === 0 /* TRANSPORT_FLOOD */ || routeType === 3 /* TRANSPORT_DIRECT */;
|
|
624
|
-
}
|
|
625
|
-
hash() {
|
|
626
|
-
const hash = sha2562.create();
|
|
627
|
-
hash.update(new Uint8Array([this.payloadType]));
|
|
628
|
-
if (this.payloadType === 9 /* TRACE */) {
|
|
629
|
-
hash.update(new Uint8Array([this.pathLength]));
|
|
630
|
-
}
|
|
631
|
-
hash.update(this.payload);
|
|
632
|
-
const digest = hash.digest();
|
|
633
|
-
return bytesToHex(digest.slice(0, 8));
|
|
634
|
-
}
|
|
635
|
-
decode() {
|
|
636
|
-
switch (this.payloadType) {
|
|
637
|
-
case 0 /* REQUEST */:
|
|
638
|
-
return this.decodeRequest();
|
|
639
|
-
case 1 /* RESPONSE */:
|
|
640
|
-
return this.decodeResponse();
|
|
641
|
-
case 2 /* TEXT */:
|
|
642
|
-
return this.decodeText();
|
|
643
|
-
case 3 /* ACK */:
|
|
644
|
-
return this.decodeAck();
|
|
645
|
-
case 4 /* ADVERT */:
|
|
646
|
-
return this.decodeAdvert();
|
|
647
|
-
case 5 /* GROUP_TEXT */:
|
|
648
|
-
return this.decodeGroupText();
|
|
649
|
-
case 6 /* GROUP_DATA */:
|
|
650
|
-
return this.decodeGroupData();
|
|
651
|
-
case 7 /* ANON_REQ */:
|
|
652
|
-
return this.decodeAnonReq();
|
|
653
|
-
case 8 /* PATH */:
|
|
654
|
-
return this.decodePath();
|
|
655
|
-
case 9 /* TRACE */:
|
|
656
|
-
return this.decodeTrace();
|
|
657
|
-
case 15 /* RAW_CUSTOM */:
|
|
658
|
-
return this.decodeRawCustom();
|
|
659
|
-
default:
|
|
660
|
-
throw new Error(`Unsupported payload type: ${this.payloadType}`);
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
decodeEncryptedPayload(reader) {
|
|
664
|
-
const cipherMAC = reader.readBytes(2);
|
|
665
|
-
const cipherText = reader.readBytes(reader.remainingBytes());
|
|
666
|
-
return { cipherMAC, cipherText };
|
|
667
|
-
}
|
|
668
|
-
decodeRequest() {
|
|
669
|
-
if (this.payload.length < 4) {
|
|
670
|
-
throw new Error("Invalid request payload: too short");
|
|
671
|
-
}
|
|
672
|
-
const reader = new BufferReader(this.payload);
|
|
673
|
-
return {
|
|
674
|
-
type: 0 /* REQUEST */,
|
|
675
|
-
dst: reader.readByte(),
|
|
676
|
-
src: reader.readByte(),
|
|
677
|
-
encrypted: this.decodeEncryptedPayload(reader)
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
decodeResponse() {
|
|
681
|
-
if (this.payload.length < 4) {
|
|
682
|
-
throw new Error("Invalid response payload: too short");
|
|
683
|
-
}
|
|
684
|
-
const reader = new BufferReader(this.payload);
|
|
685
|
-
return {
|
|
686
|
-
type: 1 /* RESPONSE */,
|
|
687
|
-
dst: reader.readByte(),
|
|
688
|
-
src: reader.readByte(),
|
|
689
|
-
encrypted: this.decodeEncryptedPayload(reader)
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
decodeText() {
|
|
693
|
-
if (this.payload.length < 4) {
|
|
694
|
-
throw new Error("Invalid text payload: too short");
|
|
695
|
-
}
|
|
696
|
-
const reader = new BufferReader(this.payload);
|
|
697
|
-
return {
|
|
698
|
-
type: 2 /* TEXT */,
|
|
699
|
-
dst: reader.readByte(),
|
|
700
|
-
src: reader.readByte(),
|
|
701
|
-
encrypted: this.decodeEncryptedPayload(reader)
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
decodeAck() {
|
|
705
|
-
if (this.payload.length < 4) {
|
|
706
|
-
throw new Error("Invalid ack payload: too short");
|
|
707
|
-
}
|
|
708
|
-
const reader = new BufferReader(this.payload);
|
|
709
|
-
return {
|
|
710
|
-
type: 3 /* ACK */,
|
|
711
|
-
checksum: reader.readBytes(4)
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
decodeAdvert() {
|
|
715
|
-
if (this.payload.length < 4) {
|
|
716
|
-
throw new Error("Invalid advert payload: too short");
|
|
717
|
-
}
|
|
718
|
-
const reader = new BufferReader(this.payload);
|
|
719
|
-
const payload = {
|
|
720
|
-
type: 4 /* ADVERT */,
|
|
721
|
-
publicKey: reader.readBytes(32),
|
|
722
|
-
timestamp: reader.readTimestamp(),
|
|
723
|
-
signature: reader.readBytes(64)
|
|
724
|
-
};
|
|
725
|
-
const flags = reader.readByte();
|
|
726
|
-
const appdata = {
|
|
727
|
-
nodeType: flags & 15,
|
|
728
|
-
hasLocation: (flags & 16) !== 0,
|
|
729
|
-
hasFeature1: (flags & 32) !== 0,
|
|
730
|
-
hasFeature2: (flags & 64) !== 0,
|
|
731
|
-
hasName: (flags & 128) !== 0
|
|
732
|
-
};
|
|
733
|
-
if (appdata.hasLocation) {
|
|
734
|
-
const lat = reader.readInt32LE() / 1e5;
|
|
735
|
-
const lon = reader.readInt32LE() / 1e5;
|
|
736
|
-
appdata.location = [lat, lon];
|
|
737
|
-
}
|
|
738
|
-
if (appdata.hasFeature1) {
|
|
739
|
-
appdata.feature1 = reader.readUint16LE();
|
|
740
|
-
}
|
|
741
|
-
if (appdata.hasFeature2) {
|
|
742
|
-
appdata.feature2 = reader.readUint16LE();
|
|
743
|
-
}
|
|
744
|
-
if (appdata.hasName) {
|
|
745
|
-
const nameBytes = reader.readBytes();
|
|
746
|
-
let nullPos = nameBytes.indexOf(0);
|
|
747
|
-
if (nullPos === -1) {
|
|
748
|
-
nullPos = nameBytes.length;
|
|
749
|
-
}
|
|
750
|
-
appdata.name = new TextDecoder("utf-8").decode(nameBytes.subarray(0, nullPos));
|
|
751
|
-
}
|
|
752
|
-
return {
|
|
753
|
-
...payload,
|
|
754
|
-
appdata
|
|
755
|
-
};
|
|
756
|
-
}
|
|
757
|
-
decodeGroupText() {
|
|
758
|
-
if (this.payload.length < 3) {
|
|
759
|
-
throw new Error("Invalid group text payload: too short");
|
|
760
|
-
}
|
|
761
|
-
const reader = new BufferReader(this.payload);
|
|
762
|
-
return {
|
|
763
|
-
type: 5 /* GROUP_TEXT */,
|
|
764
|
-
channelHash: reader.readByte(),
|
|
765
|
-
encrypted: this.decodeEncryptedPayload(reader)
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
decodeGroupData() {
|
|
769
|
-
if (this.payload.length < 3) {
|
|
770
|
-
throw new Error("Invalid group data payload: too short");
|
|
771
|
-
}
|
|
772
|
-
const reader = new BufferReader(this.payload);
|
|
773
|
-
return {
|
|
774
|
-
type: 6 /* GROUP_DATA */,
|
|
775
|
-
channelHash: reader.readByte(),
|
|
776
|
-
encrypted: this.decodeEncryptedPayload(reader)
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
|
-
decodeAnonReq() {
|
|
780
|
-
if (this.payload.length < 1 + 32 + 2) {
|
|
781
|
-
throw new Error("Invalid anon req payload: too short");
|
|
782
|
-
}
|
|
783
|
-
const reader = new BufferReader(this.payload);
|
|
784
|
-
return {
|
|
785
|
-
type: 7 /* ANON_REQ */,
|
|
786
|
-
dst: reader.readByte(),
|
|
787
|
-
publicKey: reader.readBytes(32),
|
|
788
|
-
encrypted: this.decodeEncryptedPayload(reader)
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
decodePath() {
|
|
792
|
-
if (this.payload.length < 2) {
|
|
793
|
-
throw new Error("Invalid path payload: too short");
|
|
794
|
-
}
|
|
795
|
-
const reader = new BufferReader(this.payload);
|
|
796
|
-
return {
|
|
797
|
-
type: 8 /* PATH */,
|
|
798
|
-
dst: reader.readByte(),
|
|
799
|
-
src: reader.readByte()
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
decodeTrace() {
|
|
803
|
-
if (this.payload.length < 9) {
|
|
804
|
-
throw new Error("Invalid trace payload: too short");
|
|
805
|
-
}
|
|
806
|
-
const reader = new BufferReader(this.payload);
|
|
807
|
-
return {
|
|
808
|
-
type: 9 /* TRACE */,
|
|
809
|
-
tag: reader.readUint32LE() >>> 0,
|
|
810
|
-
authCode: reader.readUint32LE() >>> 0,
|
|
811
|
-
flags: reader.readByte() & 3,
|
|
812
|
-
nodes: reader.readBytes()
|
|
813
|
-
};
|
|
814
|
-
}
|
|
815
|
-
decodeRawCustom() {
|
|
816
|
-
return {
|
|
817
|
-
type: 15 /* RAW_CUSTOM */,
|
|
818
|
-
data: this.payload
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
};
|
|
822
1126
|
export {
|
|
823
1127
|
Contact,
|
|
824
1128
|
Contacts,
|
|
825
1129
|
Group,
|
|
826
1130
|
Identity,
|
|
827
1131
|
LocalIdentity,
|
|
1132
|
+
NodeType,
|
|
828
1133
|
Packet,
|
|
1134
|
+
PayloadType,
|
|
829
1135
|
PrivateKey,
|
|
830
1136
|
PublicKey,
|
|
1137
|
+
RequestType,
|
|
1138
|
+
RouteType,
|
|
831
1139
|
SharedSecret,
|
|
832
1140
|
StaticSecret,
|
|
1141
|
+
TextType,
|
|
833
1142
|
parseNodeHash
|
|
834
1143
|
};
|