@peers-app/peers-sdk 0.20.5 → 0.21.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/dist/data/devices.d.ts +4 -4
- package/dist/data/voice-messages.d.ts +2 -2
- package/dist/device/binary-peer-connection-v2.d.ts +19 -2
- package/dist/device/binary-peer-connection-v2.js +127 -20
- package/dist/device/binary-peer-connection-v2.test.js +60 -0
- package/dist/device/binary-peer-connection.d.ts +13 -2
- package/dist/device/binary-peer-connection.js +77 -14
- package/dist/device/binary-peer-connection.test.js +21 -4
- package/dist/device/connection.d.ts +1 -1
- package/dist/device/device.d.ts +1 -1
- package/dist/device-pairing/constants.d.ts +34 -0
- package/dist/device-pairing/constants.js +37 -0
- package/dist/device-pairing/crypto.d.ts +45 -0
- package/dist/device-pairing/crypto.js +135 -0
- package/dist/device-pairing/device-pairing.test.d.ts +1 -0
- package/dist/device-pairing/device-pairing.test.js +174 -0
- package/dist/device-pairing/index.d.ts +6 -0
- package/dist/device-pairing/index.js +22 -0
- package/dist/device-pairing/invitation.d.ts +13 -0
- package/dist/device-pairing/invitation.js +85 -0
- package/dist/device-pairing/signaling.d.ts +9 -0
- package/dist/device-pairing/signaling.js +29 -0
- package/dist/device-pairing/transcript.d.ts +7 -0
- package/dist/device-pairing/transcript.js +38 -0
- package/dist/device-pairing/types.d.ts +1023 -0
- package/dist/device-pairing/types.js +304 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/rpc-types.d.ts +8 -0
- package/dist/rpc-types.js +7 -0
- package/dist/types/peer-device.d.ts +38 -1
- package/dist/types/peer-device.js +1 -0
- package/package.json +1 -1
package/dist/data/devices.d.ts
CHANGED
|
@@ -49,15 +49,15 @@ export declare const deviceInfoSchema: z.ZodObject<{
|
|
|
49
49
|
publicBoxKey: string;
|
|
50
50
|
userId: string;
|
|
51
51
|
deviceId: string;
|
|
52
|
-
userName?: string | undefined;
|
|
53
52
|
deviceName?: string | undefined;
|
|
53
|
+
userName?: string | undefined;
|
|
54
54
|
}, {
|
|
55
55
|
publicKey: string;
|
|
56
56
|
publicBoxKey: string;
|
|
57
57
|
userId: string;
|
|
58
58
|
deviceId: string;
|
|
59
|
-
userName?: string | undefined;
|
|
60
59
|
deviceName?: string | undefined;
|
|
60
|
+
userName?: string | undefined;
|
|
61
61
|
}>;
|
|
62
62
|
/** Signed identity and optional presentation hints advertised by a device. */
|
|
63
63
|
export type IDeviceInfo = z.infer<typeof deviceInfoSchema>;
|
|
@@ -81,8 +81,8 @@ export declare const deviceHandshakeSchema: z.ZodObject<{
|
|
|
81
81
|
deviceId: string;
|
|
82
82
|
connectionId: string;
|
|
83
83
|
serverAddress: string;
|
|
84
|
-
userName?: string | undefined;
|
|
85
84
|
deviceName?: string | undefined;
|
|
85
|
+
userName?: string | undefined;
|
|
86
86
|
}, {
|
|
87
87
|
timestamp: number;
|
|
88
88
|
publicKey: string;
|
|
@@ -91,8 +91,8 @@ export declare const deviceHandshakeSchema: z.ZodObject<{
|
|
|
91
91
|
deviceId: string;
|
|
92
92
|
connectionId: string;
|
|
93
93
|
serverAddress: string;
|
|
94
|
-
userName?: string | undefined;
|
|
95
94
|
deviceName?: string | undefined;
|
|
95
|
+
userName?: string | undefined;
|
|
96
96
|
}>;
|
|
97
97
|
/** Signed device information bound to one connection handshake. */
|
|
98
98
|
export type IDeviceHandshake = z.infer<typeof deviceHandshakeSchema>;
|
|
@@ -10,9 +10,9 @@ export declare const voiceMessageSchema: z.ZodObject<{
|
|
|
10
10
|
}, "strip", z.ZodTypeAny, {
|
|
11
11
|
content: string;
|
|
12
12
|
role: "user" | "assistant";
|
|
13
|
+
sessionId: string;
|
|
13
14
|
createdAt: Date;
|
|
14
15
|
voiceMessageId: string;
|
|
15
|
-
sessionId: string;
|
|
16
16
|
threadId?: string | undefined;
|
|
17
17
|
}, {
|
|
18
18
|
content: string;
|
|
@@ -26,8 +26,8 @@ export type IVoiceMessage = z.infer<typeof voiceMessageSchema>;
|
|
|
26
26
|
export declare function VoiceMessages(dataContext?: DataContext): import("..").Table<{
|
|
27
27
|
content: string;
|
|
28
28
|
role: "user" | "assistant";
|
|
29
|
+
sessionId: string;
|
|
29
30
|
createdAt: Date;
|
|
30
31
|
voiceMessageId: string;
|
|
31
|
-
sessionId: string;
|
|
32
32
|
threadId?: string | undefined;
|
|
33
33
|
}>;
|
|
@@ -37,6 +37,21 @@ export interface IBinaryPeer {
|
|
|
37
37
|
}
|
|
38
38
|
/** Backwards-compatible name for a WebRTC binary peer. */
|
|
39
39
|
export type IWebRTCPeer = IBinaryPeer;
|
|
40
|
+
/** Hard limits applied before allocating or decoding inbound binary messages. */
|
|
41
|
+
export interface BinaryPeerReceiveLimits {
|
|
42
|
+
/** Maximum encoded bytes in one complete RPC message. */
|
|
43
|
+
maxRpcMessageBytes: number;
|
|
44
|
+
/** Maximum bytes in one reassembled raw stream message. */
|
|
45
|
+
maxStreamMessageBytes: number;
|
|
46
|
+
/** Maximum bytes reserved across all incomplete inbound messages. */
|
|
47
|
+
maxInFlightReassemblyBytes: number;
|
|
48
|
+
/** Maximum number of incomplete inbound messages retained at once. */
|
|
49
|
+
maxConcurrentReassemblies: number;
|
|
50
|
+
/** Maximum time an incomplete inbound message may retain memory. */
|
|
51
|
+
reassemblyTimeoutMs: number;
|
|
52
|
+
}
|
|
53
|
+
/** General-purpose receive limits for authenticated Peers connections. */
|
|
54
|
+
export declare const DEFAULT_BINARY_PEER_RECEIVE_LIMITS: Readonly<BinaryPeerReceiveLimits>;
|
|
40
55
|
/** Options for wrapping a binary transport in a verified Peers connection. */
|
|
41
56
|
export interface WrapBinaryPeerOptions {
|
|
42
57
|
/** Protocol identifier used for server address (e.g., 'wrtc' or 'ws') */
|
|
@@ -46,12 +61,14 @@ export interface WrapBinaryPeerOptions {
|
|
|
46
61
|
* When true, sets secureLocal and secureRemote to skip application-level encryption.
|
|
47
62
|
*/
|
|
48
63
|
markTransportSecure?: boolean;
|
|
64
|
+
/** Optional hard limits for untrusted inbound messages. */
|
|
65
|
+
receiveLimits?: Partial<BinaryPeerReceiveLimits>;
|
|
49
66
|
}
|
|
50
67
|
/**
|
|
51
68
|
* Creates an ISocket from a binary peer. This is the core v2 factory function.
|
|
52
69
|
* Replaces both createBinaryPeerSocket (v1) and StreamedSocket.
|
|
53
70
|
*/
|
|
54
|
-
export declare function createBinaryPeerSocket(connectionId: string, peer: IBinaryPeer, protocol?: string): ISocket;
|
|
71
|
+
export declare function createBinaryPeerSocket(connectionId: string, peer: IBinaryPeer, protocol?: string, receiveLimitOverrides?: Partial<BinaryPeerReceiveLimits>): ISocket;
|
|
55
72
|
/**
|
|
56
73
|
* Wraps a binary peer (WebRTC or WebSocket) into a Connection.
|
|
57
74
|
* This is the main implementation that handles all binary protocols.
|
|
@@ -61,5 +78,5 @@ export declare function wrapBinaryPeer(connectionId: string, peer: IBinaryPeer,
|
|
|
61
78
|
* Wraps a WebRTC peer into a Connection.
|
|
62
79
|
* WebRTC is encrypted at the transport layer (DTLS-SRTP).
|
|
63
80
|
*/
|
|
64
|
-
export declare function wrapWrtc(connectionId: string, peer: IWebRTCPeer, localDevice: Device, initiator: boolean, getTrustLevel?: ITrustLevelFn): Connection;
|
|
81
|
+
export declare function wrapWrtc(connectionId: string, peer: IWebRTCPeer, localDevice: Device, initiator: boolean, getTrustLevel?: ITrustLevelFn, receiveLimits?: Partial<BinaryPeerReceiveLimits>): Connection;
|
|
65
82
|
export {};
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* and event-loop yielding (critical for Go sidecar WebRTC pipeline).
|
|
18
18
|
*/
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.DEFAULT_BINARY_PEER_RECEIVE_LIMITS = void 0;
|
|
20
21
|
exports.createBinaryPeerSocket = createBinaryPeerSocket;
|
|
21
22
|
exports.wrapBinaryPeer = wrapBinaryPeer;
|
|
22
23
|
exports.wrapWrtc = wrapWrtc;
|
|
@@ -35,6 +36,14 @@ const RPC_CONT = 0x02; // [0x02][4B msgId][chunk data]
|
|
|
35
36
|
const STREAM_COMPLETE = 0x10; // [0x10][1B sidLen][streamId bytes][data]
|
|
36
37
|
const STREAM_START = 0x11; // [0x11][1B sidLen][streamId bytes][4B totalSize][chunk data]
|
|
37
38
|
const STREAM_CONT = 0x12; // [0x12][1B sidLen][streamId bytes][chunk data]
|
|
39
|
+
/** General-purpose receive limits for authenticated Peers connections. */
|
|
40
|
+
exports.DEFAULT_BINARY_PEER_RECEIVE_LIMITS = Object.freeze({
|
|
41
|
+
maxRpcMessageBytes: 256 * 1024 * 1024,
|
|
42
|
+
maxStreamMessageBytes: 256 * 1024 * 1024,
|
|
43
|
+
maxInFlightReassemblyBytes: 512 * 1024 * 1024,
|
|
44
|
+
maxConcurrentReassemblies: 64,
|
|
45
|
+
reassemblyTimeoutMs: 30_000,
|
|
46
|
+
});
|
|
38
47
|
const WRTC_CONFIG = {
|
|
39
48
|
maxFramePayload: 64 * 1024, // 64KB - safe for SCTP
|
|
40
49
|
highWaterMark: 128 * 1024, // 128KB
|
|
@@ -227,7 +236,7 @@ function buildStreamCont(sidBytes, chunkData) {
|
|
|
227
236
|
* Creates an ISocket from a binary peer. This is the core v2 factory function.
|
|
228
237
|
* Replaces both createBinaryPeerSocket (v1) and StreamedSocket.
|
|
229
238
|
*/
|
|
230
|
-
function createBinaryPeerSocket(connectionId, peer, protocol = "binary") {
|
|
239
|
+
function createBinaryPeerSocket(connectionId, peer, protocol = "binary", receiveLimitOverrides = {}) {
|
|
231
240
|
const handlers = {};
|
|
232
241
|
const callbacks = {};
|
|
233
242
|
const rawBytesHandlers = {};
|
|
@@ -235,6 +244,15 @@ function createBinaryPeerSocket(connectionId, peer, protocol = "binary") {
|
|
|
235
244
|
const stats = (0, socket_type_1.createSocketStats)();
|
|
236
245
|
const transportType = protocol === "wrtc" ? "wrtc" : protocol === "ws" ? "ws" : "unknown";
|
|
237
246
|
const config = getTransportConfig(protocol);
|
|
247
|
+
const receiveLimits = {
|
|
248
|
+
...exports.DEFAULT_BINARY_PEER_RECEIVE_LIMITS,
|
|
249
|
+
...receiveLimitOverrides,
|
|
250
|
+
};
|
|
251
|
+
for (const [name, value] of Object.entries(receiveLimits)) {
|
|
252
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
253
|
+
throw new Error(`Invalid binary peer receive limit ${name}: ${value}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
238
256
|
// ─── Drain / Backpressure ────────────────────────────────────────
|
|
239
257
|
// Single persistent drain handler to avoid leaking listeners
|
|
240
258
|
let drainResolve = null;
|
|
@@ -311,8 +329,56 @@ function createBinaryPeerSocket(connectionId, peer, protocol = "binary") {
|
|
|
311
329
|
rpcSendQueue = rpcSendQueue.then(sendChunked, sendChunked);
|
|
312
330
|
return rpcSendQueue;
|
|
313
331
|
}
|
|
314
|
-
// RPC reassembly state: keyed by msgId
|
|
315
332
|
const rpcReassemblyBuffers = new Map();
|
|
333
|
+
let inFlightReassemblyBytes = 0;
|
|
334
|
+
function clearReassemblyState(state) {
|
|
335
|
+
clearTimeout(state.timer);
|
|
336
|
+
inFlightReassemblyBytes -= state.buffer.length;
|
|
337
|
+
}
|
|
338
|
+
function clearAllReassemblies() {
|
|
339
|
+
for (const state of rpcReassemblyBuffers.values()) {
|
|
340
|
+
clearTimeout(state.timer);
|
|
341
|
+
}
|
|
342
|
+
rpcReassemblyBuffers.clear();
|
|
343
|
+
for (const state of streamReassemblyBuffers.values()) {
|
|
344
|
+
clearTimeout(state.timer);
|
|
345
|
+
}
|
|
346
|
+
streamReassemblyBuffers.clear();
|
|
347
|
+
inFlightReassemblyBytes = 0;
|
|
348
|
+
}
|
|
349
|
+
function closeForProtocolViolation(reason) {
|
|
350
|
+
if (isClosed) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
console.warn(`Closing ${protocol} peer after binary protocol violation: ${reason}`);
|
|
354
|
+
isClosed = true;
|
|
355
|
+
clearAllReassemblies();
|
|
356
|
+
socket.connected = false;
|
|
357
|
+
peer.destroy();
|
|
358
|
+
}
|
|
359
|
+
function reserveReassembly(key, totalSize, maximumMessageBytes, onTimeout) {
|
|
360
|
+
const concurrentCount = rpcReassemblyBuffers.size + streamReassemblyBuffers.size;
|
|
361
|
+
if (totalSize > maximumMessageBytes) {
|
|
362
|
+
closeForProtocolViolation(`${key} exceeds the message-size limit`);
|
|
363
|
+
return undefined;
|
|
364
|
+
}
|
|
365
|
+
if (concurrentCount >= receiveLimits.maxConcurrentReassemblies) {
|
|
366
|
+
closeForProtocolViolation("too many concurrent message reassemblies");
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
if (inFlightReassemblyBytes + totalSize > receiveLimits.maxInFlightReassemblyBytes) {
|
|
370
|
+
closeForProtocolViolation("aggregate reassembly memory limit exceeded");
|
|
371
|
+
return undefined;
|
|
372
|
+
}
|
|
373
|
+
const state = {
|
|
374
|
+
buffer: new Uint8Array(totalSize),
|
|
375
|
+
offset: 0,
|
|
376
|
+
timer: setTimeout(onTimeout, receiveLimits.reassemblyTimeoutMs),
|
|
377
|
+
};
|
|
378
|
+
state.timer.unref?.();
|
|
379
|
+
inFlightReassemblyBytes += totalSize;
|
|
380
|
+
return state;
|
|
381
|
+
}
|
|
316
382
|
function handleIncomingRPC(bytes) {
|
|
317
383
|
const message = decodeMessage(bytes);
|
|
318
384
|
if (message.type === "callback") {
|
|
@@ -407,6 +473,10 @@ function createBinaryPeerSocket(connectionId, peer, protocol = "binary") {
|
|
|
407
473
|
// ── RPC frames ──────────────────────────────────────────
|
|
408
474
|
case RPC_COMPLETE: {
|
|
409
475
|
const rpcBytes = bytes.subarray(1);
|
|
476
|
+
if (rpcBytes.length > receiveLimits.maxRpcMessageBytes) {
|
|
477
|
+
closeForProtocolViolation("complete RPC exceeds the message-size limit");
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
410
480
|
handleIncomingRPC(rpcBytes);
|
|
411
481
|
break;
|
|
412
482
|
}
|
|
@@ -417,14 +487,22 @@ function createBinaryPeerSocket(connectionId, peer, protocol = "binary") {
|
|
|
417
487
|
const msgId = view.getUint32(1, false);
|
|
418
488
|
const totalSize = view.getUint32(5, false);
|
|
419
489
|
const chunkData = bytes.subarray(9);
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
490
|
+
if (totalSize === 0 || chunkData.length > totalSize || rpcReassemblyBuffers.has(msgId)) {
|
|
491
|
+
closeForProtocolViolation("invalid or duplicate RPC start frame");
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const state = reserveReassembly(`RPC ${msgId}`, totalSize, receiveLimits.maxRpcMessageBytes, () => closeForProtocolViolation(`RPC ${msgId} reassembly timed out`));
|
|
495
|
+
if (!state) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
state.buffer.set(chunkData, 0);
|
|
499
|
+
state.offset = chunkData.length;
|
|
500
|
+
if (state.offset === totalSize) {
|
|
501
|
+
clearReassemblyState(state);
|
|
502
|
+
handleIncomingRPC(state.buffer);
|
|
425
503
|
}
|
|
426
504
|
else {
|
|
427
|
-
rpcReassemblyBuffers.set(msgId,
|
|
505
|
+
rpcReassemblyBuffers.set(msgId, state);
|
|
428
506
|
}
|
|
429
507
|
break;
|
|
430
508
|
}
|
|
@@ -437,10 +515,15 @@ function createBinaryPeerSocket(connectionId, peer, protocol = "binary") {
|
|
|
437
515
|
const state = rpcReassemblyBuffers.get(msgId);
|
|
438
516
|
if (!state)
|
|
439
517
|
return; // no START received, discard
|
|
518
|
+
if (chunkData.length > state.buffer.length - state.offset) {
|
|
519
|
+
closeForProtocolViolation(`RPC ${msgId} continuation exceeds declared size`);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
440
522
|
state.buffer.set(chunkData, state.offset);
|
|
441
523
|
state.offset += chunkData.length;
|
|
442
|
-
if (state.offset
|
|
524
|
+
if (state.offset === state.buffer.length) {
|
|
443
525
|
rpcReassemblyBuffers.delete(msgId);
|
|
526
|
+
clearReassemblyState(state);
|
|
444
527
|
handleIncomingRPC(state.buffer);
|
|
445
528
|
}
|
|
446
529
|
break;
|
|
@@ -455,6 +538,10 @@ function createBinaryPeerSocket(connectionId, peer, protocol = "binary") {
|
|
|
455
538
|
const sidBytes = bytes.subarray(2, 2 + sidLen);
|
|
456
539
|
const streamId = decodeStreamId(sidBytes);
|
|
457
540
|
const payload = bytes.subarray(2 + sidLen);
|
|
541
|
+
if (payload.length > receiveLimits.maxStreamMessageBytes) {
|
|
542
|
+
closeForProtocolViolation("complete stream message exceeds the message-size limit");
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
458
545
|
const handler = rawBytesHandlers[streamId];
|
|
459
546
|
if (handler)
|
|
460
547
|
handler(payload);
|
|
@@ -471,16 +558,26 @@ function createBinaryPeerSocket(connectionId, peer, protocol = "binary") {
|
|
|
471
558
|
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
472
559
|
const totalSize = view.getUint32(2 + sidLen, false);
|
|
473
560
|
const chunkData = bytes.subarray(2 + sidLen + 4);
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
561
|
+
if (totalSize === 0 ||
|
|
562
|
+
chunkData.length > totalSize ||
|
|
563
|
+
streamReassemblyBuffers.has(streamId)) {
|
|
564
|
+
closeForProtocolViolation("invalid or duplicate stream start frame");
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const state = reserveReassembly(`stream ${streamId}`, totalSize, receiveLimits.maxStreamMessageBytes, () => closeForProtocolViolation(`stream ${streamId} reassembly timed out`));
|
|
568
|
+
if (!state) {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
state.buffer.set(chunkData, 0);
|
|
572
|
+
state.offset = chunkData.length;
|
|
573
|
+
if (state.offset === totalSize) {
|
|
574
|
+
clearReassemblyState(state);
|
|
478
575
|
const handler = rawBytesHandlers[streamId];
|
|
479
576
|
if (handler)
|
|
480
|
-
handler(buffer);
|
|
577
|
+
handler(state.buffer);
|
|
481
578
|
}
|
|
482
579
|
else {
|
|
483
|
-
streamReassemblyBuffers.set(streamId,
|
|
580
|
+
streamReassemblyBuffers.set(streamId, state);
|
|
484
581
|
}
|
|
485
582
|
break;
|
|
486
583
|
}
|
|
@@ -496,10 +593,15 @@ function createBinaryPeerSocket(connectionId, peer, protocol = "binary") {
|
|
|
496
593
|
const state = streamReassemblyBuffers.get(streamId);
|
|
497
594
|
if (!state)
|
|
498
595
|
return; // no START received, discard
|
|
596
|
+
if (chunkData.length > state.buffer.length - state.offset) {
|
|
597
|
+
closeForProtocolViolation(`stream ${streamId} continuation exceeds declared size`);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
499
600
|
state.buffer.set(chunkData, state.offset);
|
|
500
601
|
state.offset += chunkData.length;
|
|
501
|
-
if (state.offset
|
|
602
|
+
if (state.offset === state.buffer.length) {
|
|
502
603
|
streamReassemblyBuffers.delete(streamId);
|
|
604
|
+
clearReassemblyState(state);
|
|
503
605
|
const handler = rawBytesHandlers[streamId];
|
|
504
606
|
if (handler)
|
|
505
607
|
handler(state.buffer);
|
|
@@ -512,6 +614,10 @@ function createBinaryPeerSocket(connectionId, peer, protocol = "binary") {
|
|
|
512
614
|
// legacy format (no discriminator), try to decode as RPC.
|
|
513
615
|
// This handles the transition period where v1 peers send raw RPC without
|
|
514
616
|
// a frame type byte.
|
|
617
|
+
if (bytes.length > receiveLimits.maxRpcMessageBytes) {
|
|
618
|
+
closeForProtocolViolation("legacy RPC exceeds the message-size limit");
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
515
621
|
handleIncomingRPC(bytes);
|
|
516
622
|
break;
|
|
517
623
|
}
|
|
@@ -526,6 +632,7 @@ function createBinaryPeerSocket(connectionId, peer, protocol = "binary") {
|
|
|
526
632
|
if (isClosed)
|
|
527
633
|
return;
|
|
528
634
|
isClosed = true;
|
|
635
|
+
clearAllReassemblies();
|
|
529
636
|
socket.connected = false;
|
|
530
637
|
});
|
|
531
638
|
// ─── ISocket Implementation ──────────────────────────────────────
|
|
@@ -574,8 +681,8 @@ function createBinaryPeerSocket(connectionId, peer, protocol = "binary") {
|
|
|
574
681
|
* This is the main implementation that handles all binary protocols.
|
|
575
682
|
*/
|
|
576
683
|
function wrapBinaryPeer(connectionId, peer, localDevice, initiator, options, getTrustLevel) {
|
|
577
|
-
const { protocol, markTransportSecure } = options;
|
|
578
|
-
const socket = createBinaryPeerSocket(connectionId, peer, protocol);
|
|
684
|
+
const { protocol, markTransportSecure, receiveLimits } = options;
|
|
685
|
+
const socket = createBinaryPeerSocket(connectionId, peer, protocol, receiveLimits);
|
|
579
686
|
const serverAddress = `${protocol}://${connectionId}`;
|
|
580
687
|
const localServerAddresses = initiator ? undefined : [serverAddress];
|
|
581
688
|
if (process.env.NODE_ENV !== "test") {
|
|
@@ -592,6 +699,6 @@ function wrapBinaryPeer(connectionId, peer, localDevice, initiator, options, get
|
|
|
592
699
|
* Wraps a WebRTC peer into a Connection.
|
|
593
700
|
* WebRTC is encrypted at the transport layer (DTLS-SRTP).
|
|
594
701
|
*/
|
|
595
|
-
function wrapWrtc(connectionId, peer, localDevice, initiator, getTrustLevel) {
|
|
596
|
-
return wrapBinaryPeer(connectionId, peer, localDevice, initiator, { protocol: "wrtc", markTransportSecure: true }, getTrustLevel);
|
|
702
|
+
function wrapWrtc(connectionId, peer, localDevice, initiator, getTrustLevel, receiveLimits) {
|
|
703
|
+
return wrapBinaryPeer(connectionId, peer, localDevice, initiator, { protocol: "wrtc", markTransportSecure: true, receiveLimits }, getTrustLevel);
|
|
597
704
|
}
|
|
@@ -11,6 +11,7 @@ class MockBinaryPeer {
|
|
|
11
11
|
drainHandlers = [];
|
|
12
12
|
otherPeer;
|
|
13
13
|
_bufferedAmount = 0;
|
|
14
|
+
destroyed = false;
|
|
14
15
|
on(event, handler) {
|
|
15
16
|
if (event === "data") {
|
|
16
17
|
this.dataHandlers.push(handler);
|
|
@@ -41,6 +42,7 @@ class MockBinaryPeer {
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
destroy() {
|
|
45
|
+
this.destroyed = true;
|
|
44
46
|
for (const handler of this.closeHandlers) {
|
|
45
47
|
handler();
|
|
46
48
|
}
|
|
@@ -50,6 +52,20 @@ class MockBinaryPeer {
|
|
|
50
52
|
getBufferedAmount() {
|
|
51
53
|
return this._bufferedAmount;
|
|
52
54
|
}
|
|
55
|
+
receive(data) {
|
|
56
|
+
for (const handler of this.dataHandlers) {
|
|
57
|
+
handler(data);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function rpcStartFrame(messageId, totalSize, chunk = new Uint8Array()) {
|
|
62
|
+
const frame = new Uint8Array(9 + chunk.length);
|
|
63
|
+
frame[0] = 0x01;
|
|
64
|
+
const view = new DataView(frame.buffer);
|
|
65
|
+
view.setUint32(1, messageId, false);
|
|
66
|
+
view.setUint32(5, totalSize, false);
|
|
67
|
+
frame.set(chunk, 9);
|
|
68
|
+
return frame;
|
|
53
69
|
}
|
|
54
70
|
async function getMockPeerPair() {
|
|
55
71
|
const iPeer = new MockBinaryPeer();
|
|
@@ -270,6 +286,50 @@ describe("binary-peer-connection-v2", () => {
|
|
|
270
286
|
rPeer.destroy();
|
|
271
287
|
});
|
|
272
288
|
});
|
|
289
|
+
describe("receive limits", () => {
|
|
290
|
+
it("closes before allocating an oversized declared RPC", async () => {
|
|
291
|
+
const { iPeer } = await getMockPeerPair();
|
|
292
|
+
(0, binary_peer_connection_v2_1.createBinaryPeerSocket)("limited", iPeer, "wrtc", {
|
|
293
|
+
maxRpcMessageBytes: 32,
|
|
294
|
+
});
|
|
295
|
+
iPeer.receive(rpcStartFrame(1, 33));
|
|
296
|
+
expect(iPeer.destroyed).toBe(true);
|
|
297
|
+
});
|
|
298
|
+
it("bounds aggregate and concurrent incomplete reassemblies", async () => {
|
|
299
|
+
const { iPeer } = await getMockPeerPair();
|
|
300
|
+
(0, binary_peer_connection_v2_1.createBinaryPeerSocket)("limited", iPeer, "wrtc", {
|
|
301
|
+
maxRpcMessageBytes: 32,
|
|
302
|
+
maxInFlightReassemblyBytes: 16,
|
|
303
|
+
maxConcurrentReassemblies: 2,
|
|
304
|
+
});
|
|
305
|
+
iPeer.receive(rpcStartFrame(1, 12, new Uint8Array([1])));
|
|
306
|
+
expect(iPeer.destroyed).toBe(false);
|
|
307
|
+
iPeer.receive(rpcStartFrame(2, 12, new Uint8Array([2])));
|
|
308
|
+
expect(iPeer.destroyed).toBe(true);
|
|
309
|
+
});
|
|
310
|
+
it("closes when an incomplete reassembly outlives its deadline", async () => {
|
|
311
|
+
const { iPeer } = await getMockPeerPair();
|
|
312
|
+
jest.useFakeTimers();
|
|
313
|
+
try {
|
|
314
|
+
(0, binary_peer_connection_v2_1.createBinaryPeerSocket)("limited", iPeer, "wrtc", {
|
|
315
|
+
reassemblyTimeoutMs: 10,
|
|
316
|
+
});
|
|
317
|
+
iPeer.receive(rpcStartFrame(1, 32, new Uint8Array([1])));
|
|
318
|
+
jest.advanceTimersByTime(10);
|
|
319
|
+
expect(iPeer.destroyed).toBe(true);
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
jest.useRealTimers();
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
it("closes when continuation bytes exceed the declared RPC size", async () => {
|
|
326
|
+
const { iPeer } = await getMockPeerPair();
|
|
327
|
+
(0, binary_peer_connection_v2_1.createBinaryPeerSocket)("limited", iPeer, "wrtc");
|
|
328
|
+
iPeer.receive(rpcStartFrame(1, 2, new Uint8Array([1])));
|
|
329
|
+
iPeer.receive(new Uint8Array([0x02, 0, 0, 0, 1, 2, 3]));
|
|
330
|
+
expect(iPeer.destroyed).toBe(true);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
273
333
|
describe("createBinaryPeerSocket raw bytes (stream mode)", () => {
|
|
274
334
|
it("should send/receive raw bytes", async () => {
|
|
275
335
|
const { iPeer, rPeer } = await getMockPeerPair();
|
|
@@ -27,12 +27,23 @@ export interface WrapBinaryPeerOptions {
|
|
|
27
27
|
* When true, sets secureLocal and secureRemote to skip application-level encryption.
|
|
28
28
|
*/
|
|
29
29
|
markTransportSecure?: boolean;
|
|
30
|
+
/** Optional hard limits for untrusted inbound messages. */
|
|
31
|
+
receiveLimits?: Partial<BinaryPeerReceiveLimits>;
|
|
32
|
+
}
|
|
33
|
+
/** Hard receive limits for the legacy binary peer parser. */
|
|
34
|
+
export interface BinaryPeerReceiveLimits {
|
|
35
|
+
/** Maximum bytes in one complete or reassembled RPC message. */
|
|
36
|
+
maxRpcMessageBytes: number;
|
|
37
|
+
/** Maximum bytes in one raw stream frame. */
|
|
38
|
+
maxStreamMessageBytes: number;
|
|
39
|
+
/** Maximum time an incomplete RPC message may retain memory. */
|
|
40
|
+
reassemblyTimeoutMs: number;
|
|
30
41
|
}
|
|
31
42
|
/**
|
|
32
43
|
* Creates an ISocket from a binary peer without creating a full Connection.
|
|
33
44
|
* Useful when you need to pass custom localServerAddresses to the Connection.
|
|
34
45
|
*/
|
|
35
|
-
export declare function createBinaryPeerSocket(connectionId: string, peer: IBinaryPeer, protocol?: string): ISocket;
|
|
46
|
+
export declare function createBinaryPeerSocket(connectionId: string, peer: IBinaryPeer, protocol?: string, receiveLimitOverrides?: Partial<BinaryPeerReceiveLimits>): ISocket;
|
|
36
47
|
/**
|
|
37
48
|
* Wraps a binary peer (WebRTC or WebSocket) into a Connection.
|
|
38
49
|
* This is the main implementation that handles all binary protocols.
|
|
@@ -43,5 +54,5 @@ export declare function wrapBinaryPeer(connectionId: string, peer: IBinaryPeer,
|
|
|
43
54
|
* Backwards-compatible wrapper around wrapBinaryPeer.
|
|
44
55
|
* WebRTC is encrypted at the transport layer (DTLS-SRTP), so we skip application-level encryption.
|
|
45
56
|
*/
|
|
46
|
-
export declare function wrapWrtc(connectionId: string, peer: IWebRTCPeer, localDevice: Device, initiator: boolean, getTrustLevel?: ITrustLevelFn): Connection;
|
|
57
|
+
export declare function wrapWrtc(connectionId: string, peer: IWebRTCPeer, localDevice: Device, initiator: boolean, getTrustLevel?: ITrustLevelFn, receiveLimits?: Partial<BinaryPeerReceiveLimits>): Connection;
|
|
47
58
|
export {};
|