@meshwhisper/sdk 0.1.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 +138 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +19 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/chaff/index.d.ts +91 -0
- package/dist/chaff/index.d.ts.map +1 -0
- package/dist/chaff/index.js +268 -0
- package/dist/chaff/index.js.map +1 -0
- package/dist/cluster/index.d.ts +159 -0
- package/dist/cluster/index.d.ts.map +1 -0
- package/dist/cluster/index.js +393 -0
- package/dist/cluster/index.js.map +1 -0
- package/dist/compliance/index.d.ts +129 -0
- package/dist/compliance/index.d.ts.map +1 -0
- package/dist/compliance/index.js +315 -0
- package/dist/compliance/index.js.map +1 -0
- package/dist/crypto/index.d.ts +65 -0
- package/dist/crypto/index.d.ts.map +1 -0
- package/dist/crypto/index.js +146 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/group/index.d.ts +155 -0
- package/dist/group/index.d.ts.map +1 -0
- package/dist/group/index.js +560 -0
- package/dist/group/index.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/namespace/index.d.ts +155 -0
- package/dist/namespace/index.d.ts.map +1 -0
- package/dist/namespace/index.js +278 -0
- package/dist/namespace/index.js.map +1 -0
- package/dist/node/index.d.ts +4 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +19 -0
- package/dist/node/index.js.map +1 -0
- package/dist/packet/index.d.ts +63 -0
- package/dist/packet/index.d.ts.map +1 -0
- package/dist/packet/index.js +244 -0
- package/dist/packet/index.js.map +1 -0
- package/dist/permissions/index.d.ts +107 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +282 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/persistence/idb-storage.d.ts +27 -0
- package/dist/persistence/idb-storage.d.ts.map +1 -0
- package/dist/persistence/idb-storage.js +75 -0
- package/dist/persistence/idb-storage.js.map +1 -0
- package/dist/persistence/index.d.ts +4 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +3 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/node-storage.d.ts +33 -0
- package/dist/persistence/node-storage.d.ts.map +1 -0
- package/dist/persistence/node-storage.js +90 -0
- package/dist/persistence/node-storage.js.map +1 -0
- package/dist/persistence/serialization.d.ts +4 -0
- package/dist/persistence/serialization.d.ts.map +1 -0
- package/dist/persistence/serialization.js +49 -0
- package/dist/persistence/serialization.js.map +1 -0
- package/dist/persistence/types.d.ts +29 -0
- package/dist/persistence/types.d.ts.map +1 -0
- package/dist/persistence/types.js +5 -0
- package/dist/persistence/types.js.map +1 -0
- package/dist/ratchet/index.d.ts +80 -0
- package/dist/ratchet/index.d.ts.map +1 -0
- package/dist/ratchet/index.js +259 -0
- package/dist/ratchet/index.js.map +1 -0
- package/dist/reciprocity/index.d.ts +109 -0
- package/dist/reciprocity/index.d.ts.map +1 -0
- package/dist/reciprocity/index.js +311 -0
- package/dist/reciprocity/index.js.map +1 -0
- package/dist/relay/index.d.ts +87 -0
- package/dist/relay/index.d.ts.map +1 -0
- package/dist/relay/index.js +286 -0
- package/dist/relay/index.js.map +1 -0
- package/dist/routing/index.d.ts +136 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/index.js +478 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/sdk/index.d.ts +322 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +1530 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/sybil/index.d.ts +123 -0
- package/dist/sybil/index.d.ts.map +1 -0
- package/dist/sybil/index.js +491 -0
- package/dist/sybil/index.js.map +1 -0
- package/dist/transport/browser/index.d.ts +34 -0
- package/dist/transport/browser/index.d.ts.map +1 -0
- package/dist/transport/browser/index.js +176 -0
- package/dist/transport/browser/index.js.map +1 -0
- package/dist/transport/local/index.d.ts +57 -0
- package/dist/transport/local/index.d.ts.map +1 -0
- package/dist/transport/local/index.js +442 -0
- package/dist/transport/local/index.js.map +1 -0
- package/dist/transport/negotiator/index.d.ts +79 -0
- package/dist/transport/negotiator/index.d.ts.map +1 -0
- package/dist/transport/negotiator/index.js +289 -0
- package/dist/transport/negotiator/index.js.map +1 -0
- package/dist/transport/node/index.d.ts +56 -0
- package/dist/transport/node/index.d.ts.map +1 -0
- package/dist/transport/node/index.js +209 -0
- package/dist/transport/node/index.js.map +1 -0
- package/dist/transport/noop/index.d.ts +11 -0
- package/dist/transport/noop/index.d.ts.map +1 -0
- package/dist/transport/noop/index.js +20 -0
- package/dist/transport/noop/index.js.map +1 -0
- package/dist/transport/p2p/index.d.ts +109 -0
- package/dist/transport/p2p/index.d.ts.map +1 -0
- package/dist/transport/p2p/index.js +237 -0
- package/dist/transport/p2p/index.js.map +1 -0
- package/dist/transport/websocket/index.d.ts +89 -0
- package/dist/transport/websocket/index.d.ts.map +1 -0
- package/dist/transport/websocket/index.js +498 -0
- package/dist/transport/websocket/index.js.map +1 -0
- package/dist/transport/websocket/serialize.d.ts +5 -0
- package/dist/transport/websocket/serialize.d.ts.map +1 -0
- package/dist/transport/websocket/serialize.js +55 -0
- package/dist/transport/websocket/serialize.js.map +1 -0
- package/dist/types.d.ts +215 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/dist/x3dh/index.d.ts +120 -0
- package/dist/x3dh/index.d.ts.map +1 -0
- package/dist/x3dh/index.js +290 -0
- package/dist/x3dh/index.js.map +1 -0
- package/package.json +59 -0
- package/src/browser/index.ts +19 -0
- package/src/chaff/index.ts +340 -0
- package/src/cluster/index.ts +482 -0
- package/src/compliance/index.ts +407 -0
- package/src/crypto/index.ts +193 -0
- package/src/group/index.ts +719 -0
- package/src/index.ts +87 -0
- package/src/lz4js.d.ts +58 -0
- package/src/namespace/index.ts +336 -0
- package/src/node/index.ts +19 -0
- package/src/packet/index.ts +326 -0
- package/src/permissions/index.ts +405 -0
- package/src/persistence/idb-storage.ts +83 -0
- package/src/persistence/index.ts +3 -0
- package/src/persistence/node-storage.ts +96 -0
- package/src/persistence/serialization.ts +75 -0
- package/src/persistence/types.ts +33 -0
- package/src/ratchet/index.ts +363 -0
- package/src/reciprocity/index.ts +371 -0
- package/src/relay/index.ts +382 -0
- package/src/routing/index.ts +577 -0
- package/src/sdk/index.ts +1994 -0
- package/src/sybil/index.ts +661 -0
- package/src/transport/browser/index.ts +201 -0
- package/src/transport/local/index.ts +540 -0
- package/src/transport/negotiator/index.ts +397 -0
- package/src/transport/node/index.ts +234 -0
- package/src/transport/noop/index.ts +22 -0
- package/src/transport/p2p/index.ts +345 -0
- package/src/transport/websocket/index.ts +660 -0
- package/src/transport/websocket/serialize.ts +68 -0
- package/src/types.ts +275 -0
- package/src/x3dh/index.ts +388 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// MeshWhisper SDK — Chaff Generator & Traffic Analysis Defense
|
|
3
|
+
// Emits a constant stream of encrypted chaff packets that are
|
|
4
|
+
// byte-for-byte indistinguishable from real encrypted messages,
|
|
5
|
+
// defeating traffic analysis as described in PRD section 8.4.
|
|
6
|
+
// ============================================================
|
|
7
|
+
|
|
8
|
+
import { Packet, PacketFlags, ChaffRate, RelayWillingness } from '../types.js';
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Constants
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
/** Destination hash length in bytes (truncated BLAKE3). */
|
|
15
|
+
const DEST_HASH_LENGTH = 8;
|
|
16
|
+
|
|
17
|
+
/** Sender ephemeral ID length in bytes. */
|
|
18
|
+
const SENDER_EPHEMERAL_ID_LENGTH = 16;
|
|
19
|
+
|
|
20
|
+
/** Packet version used across the SDK. */
|
|
21
|
+
const PACKET_VERSION = 1;
|
|
22
|
+
|
|
23
|
+
/** Default minimum chaff payload size in bytes. */
|
|
24
|
+
const DEFAULT_MIN_PACKET_SIZE = 32;
|
|
25
|
+
|
|
26
|
+
/** Default maximum chaff payload size in bytes. */
|
|
27
|
+
const DEFAULT_MAX_PACKET_SIZE = 256;
|
|
28
|
+
|
|
29
|
+
/** Default burst variance (0.0-1.0). */
|
|
30
|
+
const DEFAULT_BURST_VARIANCE = 0.3;
|
|
31
|
+
|
|
32
|
+
/** Base emission intervals per rate, in milliseconds. */
|
|
33
|
+
const RATE_INTERVALS: Record<ChaffRate, number> = {
|
|
34
|
+
low: 60_000, // ~1 packet / 60 s → ~1 KB/h
|
|
35
|
+
normal: 30_000, // ~1 packet / 30 s → ~2 KB/h
|
|
36
|
+
high: 10_000, // ~1 packet / 10 s → ~6 KB/h
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Maps relay willingness to the appropriate chaff rate. */
|
|
40
|
+
const WILLINGNESS_TO_RATE: Record<RelayWillingness, ChaffRate | null> = {
|
|
41
|
+
eager: 'high',
|
|
42
|
+
willing: 'normal',
|
|
43
|
+
reluctant: 'low',
|
|
44
|
+
unavailable: null,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Options
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
export interface ChaffOptions {
|
|
52
|
+
/** Emission rate preset. */
|
|
53
|
+
rate: ChaffRate;
|
|
54
|
+
/** Maximum chaff payload size in bytes. Defaults to 256. */
|
|
55
|
+
maxPacketSize?: number;
|
|
56
|
+
/** Minimum chaff payload size in bytes. Defaults to 32. */
|
|
57
|
+
minPacketSize?: number;
|
|
58
|
+
/** Randomness applied to inter-packet timing (0.0-1.0). Defaults to 0.3. */
|
|
59
|
+
burstVariance?: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Statistics
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
export interface ChaffStats {
|
|
67
|
+
packetsGenerated: number;
|
|
68
|
+
bytesGenerated: number;
|
|
69
|
+
/** Elapsed time since start() was first called, in milliseconds. */
|
|
70
|
+
uptime: number;
|
|
71
|
+
currentRate: ChaffRate;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Crypto helpers (isomorphic: works in Node.js and browsers)
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Fill a Uint8Array with cryptographically secure random bytes.
|
|
80
|
+
* Uses globalThis.crypto (Web Crypto) when available, otherwise
|
|
81
|
+
* falls back to Node.js crypto module.
|
|
82
|
+
*/
|
|
83
|
+
function secureRandomBytes(length: number): Uint8Array {
|
|
84
|
+
const buf = new Uint8Array(length);
|
|
85
|
+
|
|
86
|
+
if (typeof globalThis !== 'undefined' && globalThis.crypto?.getRandomValues) {
|
|
87
|
+
globalThis.crypto.getRandomValues(buf);
|
|
88
|
+
} else {
|
|
89
|
+
// Node.js environments where globalThis.crypto may not exist (older Node)
|
|
90
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
91
|
+
const nodeCrypto = require('node:crypto') as typeof import('node:crypto');
|
|
92
|
+
nodeCrypto.randomFillSync(buf);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return buf;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Return a uniformly distributed random integer in [min, max] (inclusive).
|
|
100
|
+
*/
|
|
101
|
+
function randomInt(min: number, max: number): number {
|
|
102
|
+
const range = max - min + 1;
|
|
103
|
+
const bytes = secureRandomBytes(4);
|
|
104
|
+
const value = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0;
|
|
105
|
+
return min + (value % range);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Return a random floating-point value in [0, 1).
|
|
110
|
+
*/
|
|
111
|
+
function randomFloat(): number {
|
|
112
|
+
const bytes = secureRandomBytes(4);
|
|
113
|
+
const value = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0;
|
|
114
|
+
return value / 0x1_0000_0000;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// ChaffGenerator
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
export class ChaffGenerator {
|
|
122
|
+
private readonly minPacketSize: number;
|
|
123
|
+
private readonly maxPacketSize: number;
|
|
124
|
+
private readonly burstVariance: number;
|
|
125
|
+
|
|
126
|
+
private rate: ChaffRate;
|
|
127
|
+
private running = false;
|
|
128
|
+
private timer: ReturnType<typeof setTimeout> | null = null;
|
|
129
|
+
private startedAt: number | null = null;
|
|
130
|
+
|
|
131
|
+
private packetsGenerated = 0;
|
|
132
|
+
private bytesGenerated = 0;
|
|
133
|
+
|
|
134
|
+
private emitCallback: ((packet: Packet) => void) | null = null;
|
|
135
|
+
|
|
136
|
+
constructor(options?: ChaffOptions) {
|
|
137
|
+
this.rate = options?.rate ?? 'normal';
|
|
138
|
+
this.minPacketSize = options?.minPacketSize ?? DEFAULT_MIN_PACKET_SIZE;
|
|
139
|
+
this.maxPacketSize = options?.maxPacketSize ?? DEFAULT_MAX_PACKET_SIZE;
|
|
140
|
+
this.burstVariance = Math.max(0, Math.min(1, options?.burstVariance ?? DEFAULT_BURST_VARIANCE));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// -----------------------------------------------------------------------
|
|
144
|
+
// Packet generation
|
|
145
|
+
// -----------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Generate a single chaff packet.
|
|
149
|
+
*
|
|
150
|
+
* The packet is constructed to be byte-for-byte indistinguishable from
|
|
151
|
+
* a real encrypted message to any external observer: all variable-length
|
|
152
|
+
* fields are filled with cryptographically random data, the flags field
|
|
153
|
+
* is set to CHAFF (which the local node recognises but a relay treats
|
|
154
|
+
* identically to DATA), and the TTL is kept low so chaff doesn't
|
|
155
|
+
* propagate far.
|
|
156
|
+
*/
|
|
157
|
+
generateChaffPacket(): Packet {
|
|
158
|
+
const payloadSize = randomInt(this.minPacketSize, this.maxPacketSize);
|
|
159
|
+
return this.buildChaffPacket(payloadSize);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// -----------------------------------------------------------------------
|
|
163
|
+
// Emission scheduling
|
|
164
|
+
// -----------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
/** Begin emitting chaff on a jittered schedule. */
|
|
167
|
+
start(): void {
|
|
168
|
+
if (this.running) return;
|
|
169
|
+
this.running = true;
|
|
170
|
+
if (this.startedAt === null) {
|
|
171
|
+
this.startedAt = Date.now();
|
|
172
|
+
}
|
|
173
|
+
this.scheduleNext();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Stop chaff emission. */
|
|
177
|
+
stop(): void {
|
|
178
|
+
this.running = false;
|
|
179
|
+
if (this.timer !== null) {
|
|
180
|
+
clearTimeout(this.timer);
|
|
181
|
+
this.timer = null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Whether the generator is currently emitting. */
|
|
186
|
+
isRunning(): boolean {
|
|
187
|
+
return this.running;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// -----------------------------------------------------------------------
|
|
191
|
+
// Rate control
|
|
192
|
+
// -----------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
/** Change the emission rate. Takes effect on the next scheduling cycle. */
|
|
195
|
+
setRate(rate: ChaffRate): void {
|
|
196
|
+
this.rate = rate;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Automatically adjust the chaff rate based on the device's relay
|
|
201
|
+
* willingness setting.
|
|
202
|
+
*
|
|
203
|
+
* - eager → high
|
|
204
|
+
* - willing → normal
|
|
205
|
+
* - reluctant → low
|
|
206
|
+
* - unavailable → stop entirely
|
|
207
|
+
*/
|
|
208
|
+
adaptToRelayWillingness(willingness: RelayWillingness): void {
|
|
209
|
+
const mapped = WILLINGNESS_TO_RATE[willingness];
|
|
210
|
+
if (mapped === null) {
|
|
211
|
+
this.stop();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
this.rate = mapped;
|
|
215
|
+
// If already running, let the current timer expire naturally — the
|
|
216
|
+
// new rate will be picked up on the next scheduling cycle.
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// -----------------------------------------------------------------------
|
|
220
|
+
// Real message camouflage
|
|
221
|
+
// -----------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Surround a real packet with 0-2 chaff packets whose payload sizes
|
|
225
|
+
* approximate the real packet's size, making it harder for an observer
|
|
226
|
+
* to distinguish the real message in a burst.
|
|
227
|
+
*
|
|
228
|
+
* Returns an array of 1-3 packets with the real packet placed at a
|
|
229
|
+
* random position.
|
|
230
|
+
*/
|
|
231
|
+
camouflageRealMessage(realPacket: Packet): Packet[] {
|
|
232
|
+
const chaffCount = randomInt(0, 2);
|
|
233
|
+
if (chaffCount === 0) return [realPacket];
|
|
234
|
+
|
|
235
|
+
const realSize = realPacket.encryptedPayload.length;
|
|
236
|
+
|
|
237
|
+
// Build chaff with sizes close (±20%) to the real payload.
|
|
238
|
+
const sizeFloor = Math.max(1, Math.round(realSize * 0.8));
|
|
239
|
+
const sizeCeil = Math.round(realSize * 1.2);
|
|
240
|
+
const chaffPackets: Packet[] = [];
|
|
241
|
+
for (let i = 0; i < chaffCount; i++) {
|
|
242
|
+
const size = randomInt(sizeFloor, sizeCeil);
|
|
243
|
+
chaffPackets.push(this.buildChaffPacket(size));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Insert the real packet at a random position in the burst.
|
|
247
|
+
const insertionIndex = randomInt(0, chaffPackets.length);
|
|
248
|
+
chaffPackets.splice(insertionIndex, 0, realPacket);
|
|
249
|
+
|
|
250
|
+
return chaffPackets;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// -----------------------------------------------------------------------
|
|
254
|
+
// Callback registration
|
|
255
|
+
// -----------------------------------------------------------------------
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Register a callback that receives each generated chaff packet.
|
|
259
|
+
* The transport layer will call this to enqueue chaff for sending.
|
|
260
|
+
*/
|
|
261
|
+
onChaffGenerated(callback: (packet: Packet) => void): void {
|
|
262
|
+
this.emitCallback = callback;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// -----------------------------------------------------------------------
|
|
266
|
+
// Statistics
|
|
267
|
+
// -----------------------------------------------------------------------
|
|
268
|
+
|
|
269
|
+
/** Return runtime statistics for monitoring / diagnostics. */
|
|
270
|
+
getStats(): ChaffStats {
|
|
271
|
+
return {
|
|
272
|
+
packetsGenerated: this.packetsGenerated,
|
|
273
|
+
bytesGenerated: this.bytesGenerated,
|
|
274
|
+
uptime: this.startedAt !== null ? Date.now() - this.startedAt : 0,
|
|
275
|
+
currentRate: this.rate,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// -----------------------------------------------------------------------
|
|
280
|
+
// Private helpers
|
|
281
|
+
// -----------------------------------------------------------------------
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Build a chaff packet with the given payload size.
|
|
285
|
+
*/
|
|
286
|
+
private buildChaffPacket(payloadSize: number): Packet {
|
|
287
|
+
const encryptedPayload = secureRandomBytes(payloadSize);
|
|
288
|
+
const packet: Packet = {
|
|
289
|
+
version: PACKET_VERSION,
|
|
290
|
+
flags: PacketFlags.CHAFF,
|
|
291
|
+
destHash: secureRandomBytes(DEST_HASH_LENGTH),
|
|
292
|
+
senderEphemeralId: secureRandomBytes(SENDER_EPHEMERAL_ID_LENGTH),
|
|
293
|
+
ttl: randomInt(1, 3),
|
|
294
|
+
payloadLength: payloadSize,
|
|
295
|
+
encryptedPayload,
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
this.packetsGenerated++;
|
|
299
|
+
this.bytesGenerated += payloadSize;
|
|
300
|
+
|
|
301
|
+
return packet;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Compute the next emission delay in milliseconds, adding jitter
|
|
306
|
+
* proportional to burstVariance so inter-packet timing is not
|
|
307
|
+
* predictable.
|
|
308
|
+
*/
|
|
309
|
+
private computeNextDelay(): number {
|
|
310
|
+
const base = RATE_INTERVALS[this.rate];
|
|
311
|
+
const jitterRange = base * this.burstVariance;
|
|
312
|
+
// Uniform jitter in [-jitterRange, +jitterRange]
|
|
313
|
+
const jitter = (randomFloat() * 2 - 1) * jitterRange;
|
|
314
|
+
return Math.max(1, Math.round(base + jitter));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Schedule the next chaff emission.
|
|
319
|
+
*/
|
|
320
|
+
private scheduleNext(): void {
|
|
321
|
+
if (!this.running) return;
|
|
322
|
+
|
|
323
|
+
const delay = this.computeNextDelay();
|
|
324
|
+
this.timer = setTimeout(() => {
|
|
325
|
+
if (!this.running) return;
|
|
326
|
+
|
|
327
|
+
const packet = this.generateChaffPacket();
|
|
328
|
+
if (this.emitCallback) {
|
|
329
|
+
this.emitCallback(packet);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
this.scheduleNext();
|
|
333
|
+
}, delay);
|
|
334
|
+
|
|
335
|
+
// Prevent the timer from keeping the process alive in Node.js.
|
|
336
|
+
if (typeof this.timer === 'object' && 'unref' in this.timer) {
|
|
337
|
+
(this.timer as NodeJS.Timeout).unref();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|