@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,326 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// MeshWhisper SDK — Binary Packet Codec
|
|
3
|
+
// Encodes/decodes the wire format defined in PRD section 9.
|
|
4
|
+
// ============================================================
|
|
5
|
+
|
|
6
|
+
import { Packet, PacketFlags } from '../types.js';
|
|
7
|
+
import lz4 from 'lz4js';
|
|
8
|
+
import { randomBytes } from 'node:crypto';
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Constants
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
/** Current protocol wire version. */
|
|
15
|
+
export const PROTOCOL_VERSION = 0x01;
|
|
16
|
+
|
|
17
|
+
/** Fixed header size in bytes (version + flags + dest_hash + sender_eph_id + ttl + payload_length). */
|
|
18
|
+
export const HEADER_SIZE = 29;
|
|
19
|
+
|
|
20
|
+
/** Maximum hop count. */
|
|
21
|
+
export const MAX_TTL = 7;
|
|
22
|
+
|
|
23
|
+
/** Maximum encrypted payload size (uint16 max). */
|
|
24
|
+
export const MAX_PAYLOAD_SIZE = 65535;
|
|
25
|
+
|
|
26
|
+
// Internal field offsets within the header.
|
|
27
|
+
const OFF_VERSION = 0;
|
|
28
|
+
const OFF_FLAGS = 1;
|
|
29
|
+
const OFF_DEST_HASH = 2;
|
|
30
|
+
const OFF_SENDER_EPH_ID = 10; // 2 + 8
|
|
31
|
+
const OFF_TTL = 26; // 10 + 16
|
|
32
|
+
const OFF_PAYLOAD_LEN = 27; // 26 + 1
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Validation helpers
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
function assertUint8Array(value: Uint8Array, expectedLen: number, name: string): void {
|
|
39
|
+
if (!(value instanceof Uint8Array)) {
|
|
40
|
+
throw new TypeError(`${name} must be a Uint8Array`);
|
|
41
|
+
}
|
|
42
|
+
if (value.length !== expectedLen) {
|
|
43
|
+
throw new RangeError(`${name} must be exactly ${expectedLen} bytes, got ${value.length}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function assertValidFlags(flags: number): asserts flags is PacketFlags {
|
|
48
|
+
const valid: number[] = [
|
|
49
|
+
PacketFlags.DATA,
|
|
50
|
+
PacketFlags.ACK,
|
|
51
|
+
PacketFlags.CHAFF,
|
|
52
|
+
PacketFlags.HANDSHAKE,
|
|
53
|
+
PacketFlags.ROUTE_REQUEST,
|
|
54
|
+
PacketFlags.ROUTE_OFFER,
|
|
55
|
+
];
|
|
56
|
+
if (!valid.includes(flags)) {
|
|
57
|
+
throw new RangeError(`Invalid packet flags: 0x${flags.toString(16).padStart(2, '0')}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Encoding
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Serialize a {@link Packet} into its binary wire representation.
|
|
67
|
+
*
|
|
68
|
+
* Layout (29-byte header + variable payload):
|
|
69
|
+
* | version (1) | flags (1) | dest_hash (8) | sender_ephemeral_id (16) | ttl (1) | payload_length (2 BE) | encrypted_payload (N) |
|
|
70
|
+
*/
|
|
71
|
+
export function encodePacket(packet: Packet): Uint8Array {
|
|
72
|
+
// --- Field validation ---
|
|
73
|
+
if (packet.version !== PROTOCOL_VERSION) {
|
|
74
|
+
throw new RangeError(`Unsupported protocol version: ${packet.version}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
assertValidFlags(packet.flags);
|
|
78
|
+
assertUint8Array(packet.destHash, 8, 'destHash');
|
|
79
|
+
assertUint8Array(packet.senderEphemeralId, 16, 'senderEphemeralId');
|
|
80
|
+
|
|
81
|
+
if (!Number.isInteger(packet.ttl) || packet.ttl < 0 || packet.ttl > MAX_TTL) {
|
|
82
|
+
throw new RangeError(`ttl must be an integer between 0 and ${MAX_TTL}, got ${packet.ttl}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!(packet.encryptedPayload instanceof Uint8Array)) {
|
|
86
|
+
throw new TypeError('encryptedPayload must be a Uint8Array');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (packet.encryptedPayload.length > MAX_PAYLOAD_SIZE) {
|
|
90
|
+
throw new RangeError(
|
|
91
|
+
`encryptedPayload exceeds maximum size of ${MAX_PAYLOAD_SIZE} bytes (got ${packet.encryptedPayload.length})`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// --- Serialise ---
|
|
96
|
+
const payloadLen = packet.encryptedPayload.length;
|
|
97
|
+
const buf = new Uint8Array(HEADER_SIZE + payloadLen);
|
|
98
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
99
|
+
|
|
100
|
+
buf[OFF_VERSION] = packet.version;
|
|
101
|
+
buf[OFF_FLAGS] = packet.flags;
|
|
102
|
+
buf.set(packet.destHash, OFF_DEST_HASH);
|
|
103
|
+
buf.set(packet.senderEphemeralId, OFF_SENDER_EPH_ID);
|
|
104
|
+
buf[OFF_TTL] = packet.ttl;
|
|
105
|
+
view.setUint16(OFF_PAYLOAD_LEN, payloadLen, false); // big-endian
|
|
106
|
+
buf.set(packet.encryptedPayload, HEADER_SIZE);
|
|
107
|
+
|
|
108
|
+
return buf;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Decoding
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Deserialize a binary buffer into a {@link Packet}.
|
|
117
|
+
*
|
|
118
|
+
* @throws on malformed input (too short, wrong version, invalid flags, length mismatch).
|
|
119
|
+
*/
|
|
120
|
+
export function decodePacket(data: Uint8Array): Packet {
|
|
121
|
+
if (!(data instanceof Uint8Array)) {
|
|
122
|
+
throw new TypeError('data must be a Uint8Array');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (data.length < HEADER_SIZE) {
|
|
126
|
+
throw new RangeError(
|
|
127
|
+
`Packet too short: expected at least ${HEADER_SIZE} bytes, got ${data.length}`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
132
|
+
|
|
133
|
+
const version = data[OFF_VERSION];
|
|
134
|
+
if (version !== PROTOCOL_VERSION) {
|
|
135
|
+
throw new RangeError(`Unsupported protocol version: ${version}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const flags = data[OFF_FLAGS];
|
|
139
|
+
assertValidFlags(flags);
|
|
140
|
+
|
|
141
|
+
const destHash = data.slice(OFF_DEST_HASH, OFF_DEST_HASH + 8);
|
|
142
|
+
const senderEphemeralId = data.slice(OFF_SENDER_EPH_ID, OFF_SENDER_EPH_ID + 16);
|
|
143
|
+
const ttl = data[OFF_TTL];
|
|
144
|
+
|
|
145
|
+
if (ttl > MAX_TTL) {
|
|
146
|
+
throw new RangeError(`ttl exceeds maximum of ${MAX_TTL}: got ${ttl}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const payloadLength = view.getUint16(OFF_PAYLOAD_LEN, false); // big-endian
|
|
150
|
+
|
|
151
|
+
if (data.length < HEADER_SIZE + payloadLength) {
|
|
152
|
+
throw new RangeError(
|
|
153
|
+
`Packet truncated: header declares ${payloadLength} payload bytes, but only ${data.length - HEADER_SIZE} available`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const encryptedPayload = data.slice(HEADER_SIZE, HEADER_SIZE + payloadLength);
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
version,
|
|
161
|
+
flags,
|
|
162
|
+
destHash,
|
|
163
|
+
senderEphemeralId,
|
|
164
|
+
ttl,
|
|
165
|
+
payloadLength,
|
|
166
|
+
encryptedPayload,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
// LZ4 Compression (applied to plaintext BEFORE encryption)
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Compress a plaintext payload with LZ4 (frame format).
|
|
176
|
+
*/
|
|
177
|
+
export function compressPayload(plaintext: Uint8Array): Uint8Array {
|
|
178
|
+
if (!(plaintext instanceof Uint8Array)) {
|
|
179
|
+
throw new TypeError('plaintext must be a Uint8Array');
|
|
180
|
+
}
|
|
181
|
+
// lz4js.compress returns a Buffer/Uint8Array of the compressed frame.
|
|
182
|
+
const compressed: Uint8Array = lz4.compress(plaintext);
|
|
183
|
+
return new Uint8Array(compressed);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Decompress an LZ4 compressed payload back to plaintext.
|
|
188
|
+
*/
|
|
189
|
+
export function decompressPayload(compressed: Uint8Array): Uint8Array {
|
|
190
|
+
if (!(compressed instanceof Uint8Array)) {
|
|
191
|
+
throw new TypeError('compressed must be a Uint8Array');
|
|
192
|
+
}
|
|
193
|
+
const decompressed: Uint8Array = lz4.decompress(compressed);
|
|
194
|
+
return new Uint8Array(decompressed);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// Packet builders
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Build a DATA packet.
|
|
203
|
+
*
|
|
204
|
+
* @param destHash 8-byte truncated BLAKE3 destination hash
|
|
205
|
+
* @param senderEphId 16-byte rotating ephemeral sender identifier
|
|
206
|
+
* @param payload Encrypted payload bytes
|
|
207
|
+
* @param ttl Hop count (0-7, defaults to {@link MAX_TTL})
|
|
208
|
+
*/
|
|
209
|
+
export function createDataPacket(
|
|
210
|
+
destHash: Uint8Array,
|
|
211
|
+
senderEphId: Uint8Array,
|
|
212
|
+
payload: Uint8Array,
|
|
213
|
+
ttl: number = MAX_TTL,
|
|
214
|
+
): Packet {
|
|
215
|
+
return buildPacket(PacketFlags.DATA, destHash, senderEphId, payload, ttl);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Build an ACK packet (ttl fixed at 0 since ACKs are single-hop).
|
|
220
|
+
*/
|
|
221
|
+
export function createAckPacket(
|
|
222
|
+
destHash: Uint8Array,
|
|
223
|
+
senderEphId: Uint8Array,
|
|
224
|
+
ackPayload: Uint8Array,
|
|
225
|
+
): Packet {
|
|
226
|
+
return buildPacket(PacketFlags.ACK, destHash, senderEphId, ackPayload, 0);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Build a CHAFF (cover-traffic) packet with random destination and payload.
|
|
231
|
+
*
|
|
232
|
+
* The payload length is randomly chosen between 32 and 256 bytes to mimic
|
|
233
|
+
* realistic traffic without being trivially distinguishable.
|
|
234
|
+
*/
|
|
235
|
+
export function createChaffPacket(senderEphId: Uint8Array): Packet {
|
|
236
|
+
const destHash = randomUint8Array(8);
|
|
237
|
+
const payloadLen = 32 + (randomUint8Array(1)[0] % 225); // 32..256
|
|
238
|
+
const payload = randomUint8Array(payloadLen);
|
|
239
|
+
return buildPacket(PacketFlags.CHAFF, destHash, senderEphId, payload, 0);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Build a ROUTE_REQUEST packet.
|
|
244
|
+
*/
|
|
245
|
+
export function createRouteRequestPacket(
|
|
246
|
+
destHash: Uint8Array,
|
|
247
|
+
senderEphId: Uint8Array,
|
|
248
|
+
requestPayload: Uint8Array,
|
|
249
|
+
): Packet {
|
|
250
|
+
return buildPacket(PacketFlags.ROUTE_REQUEST, destHash, senderEphId, requestPayload, MAX_TTL);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Build a ROUTE_OFFER packet.
|
|
255
|
+
*/
|
|
256
|
+
export function createRouteOfferPacket(
|
|
257
|
+
destHash: Uint8Array,
|
|
258
|
+
senderEphId: Uint8Array,
|
|
259
|
+
offerPayload: Uint8Array,
|
|
260
|
+
): Packet {
|
|
261
|
+
return buildPacket(PacketFlags.ROUTE_OFFER, destHash, senderEphId, offerPayload, MAX_TTL);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Build a HANDSHAKE packet.
|
|
266
|
+
*/
|
|
267
|
+
export function createHandshakePacket(
|
|
268
|
+
destHash: Uint8Array,
|
|
269
|
+
senderEphId: Uint8Array,
|
|
270
|
+
handshakePayload: Uint8Array,
|
|
271
|
+
): Packet {
|
|
272
|
+
return buildPacket(PacketFlags.HANDSHAKE, destHash, senderEphId, handshakePayload, MAX_TTL);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
// Internal helpers
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
function buildPacket(
|
|
280
|
+
flags: PacketFlags,
|
|
281
|
+
destHash: Uint8Array,
|
|
282
|
+
senderEphId: Uint8Array,
|
|
283
|
+
encryptedPayload: Uint8Array,
|
|
284
|
+
ttl: number,
|
|
285
|
+
): Packet {
|
|
286
|
+
assertUint8Array(destHash, 8, 'destHash');
|
|
287
|
+
assertUint8Array(senderEphId, 16, 'senderEphemeralId');
|
|
288
|
+
|
|
289
|
+
if (!Number.isInteger(ttl) || ttl < 0 || ttl > MAX_TTL) {
|
|
290
|
+
throw new RangeError(`ttl must be an integer between 0 and ${MAX_TTL}, got ${ttl}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!(encryptedPayload instanceof Uint8Array)) {
|
|
294
|
+
throw new TypeError('payload must be a Uint8Array');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (encryptedPayload.length > MAX_PAYLOAD_SIZE) {
|
|
298
|
+
throw new RangeError(
|
|
299
|
+
`Payload exceeds maximum size of ${MAX_PAYLOAD_SIZE} bytes (got ${encryptedPayload.length})`,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
version: PROTOCOL_VERSION,
|
|
305
|
+
flags,
|
|
306
|
+
destHash,
|
|
307
|
+
senderEphemeralId: senderEphId,
|
|
308
|
+
ttl,
|
|
309
|
+
payloadLength: encryptedPayload.length,
|
|
310
|
+
encryptedPayload,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Generate a Uint8Array filled with cryptographically secure random bytes.
|
|
316
|
+
*/
|
|
317
|
+
function randomUint8Array(length: number): Uint8Array {
|
|
318
|
+
// Works in both Node.js and browser contexts.
|
|
319
|
+
if (typeof globalThis.crypto !== 'undefined' && typeof globalThis.crypto.getRandomValues === 'function') {
|
|
320
|
+
const buf = new Uint8Array(length);
|
|
321
|
+
globalThis.crypto.getRandomValues(buf);
|
|
322
|
+
return buf;
|
|
323
|
+
}
|
|
324
|
+
// Fallback: Node.js crypto module (already imported).
|
|
325
|
+
return new Uint8Array(randomBytes(length));
|
|
326
|
+
}
|