@sideband/secure-relay 0.2.1 → 0.2.3

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.
Files changed (46) hide show
  1. package/README.md +27 -8
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/constants.d.ts +49 -0
  4. package/dist/constants.d.ts.map +1 -0
  5. package/dist/constants.js +51 -0
  6. package/dist/constants.js.map +1 -0
  7. package/dist/crypto.d.ts +70 -0
  8. package/dist/crypto.d.ts.map +1 -0
  9. package/dist/crypto.js +144 -0
  10. package/dist/crypto.js.map +1 -0
  11. package/dist/frame.d.ts +213 -0
  12. package/dist/frame.d.ts.map +1 -0
  13. package/dist/frame.js +547 -0
  14. package/dist/frame.js.map +1 -0
  15. package/dist/handshake.d.ts +39 -0
  16. package/dist/handshake.d.ts.map +1 -0
  17. package/dist/handshake.js +93 -0
  18. package/dist/handshake.js.map +1 -0
  19. package/dist/index.d.ts +46 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +12 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/replay.d.ts +32 -0
  24. package/dist/replay.d.ts.map +1 -0
  25. package/dist/replay.js +88 -0
  26. package/dist/replay.js.map +1 -0
  27. package/dist/session.d.ts +67 -0
  28. package/dist/session.d.ts.map +1 -0
  29. package/dist/session.js +122 -0
  30. package/dist/session.js.map +1 -0
  31. package/dist/types.d.ts +119 -0
  32. package/dist/types.d.ts.map +1 -0
  33. package/dist/types.js +80 -0
  34. package/dist/types.js.map +1 -0
  35. package/package.json +4 -4
  36. package/src/constants.ts +3 -3
  37. package/src/crypto.test.ts +5 -5
  38. package/src/crypto.ts +9 -9
  39. package/src/frame.test.ts +59 -10
  40. package/src/frame.ts +101 -77
  41. package/src/handshake.test.ts +29 -41
  42. package/src/handshake.ts +25 -27
  43. package/src/index.ts +4 -10
  44. package/src/integration.test.ts +97 -138
  45. package/src/session.test.ts +12 -10
  46. package/src/types.ts +1 -12
package/dist/frame.js ADDED
@@ -0,0 +1,547 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Binary wire format for Sideband Relay Protocol (SBRP).
4
+ *
5
+ * Frame structure (§13):
6
+ * ```
7
+ * ┌───────────┬──────────────┬────────────────┬─────────────────────┐
8
+ * │ Type (1B) │ Length (4B) │ SessionID (8B) │ Payload (0..64KB) │
9
+ * └───────────┴──────────────┴────────────────┴─────────────────────┘
10
+ * ```
11
+ *
12
+ * All multi-byte integers are big-endian.
13
+ */
14
+ import { ED25519_PUBLIC_KEY_LENGTH, ED25519_SIGNATURE_LENGTH, FRAME_HEADER_SIZE, HANDSHAKE_ACCEPT_PAYLOAD_SIZE, HANDSHAKE_INIT_PAYLOAD_SIZE, MAX_PAYLOAD_SIZE, MAX_PING_PAYLOAD_SIZE, MIN_CONTROL_PAYLOAD_SIZE, MIN_ENCRYPTED_PAYLOAD_SIZE, SIGNAL_PAYLOAD_SIZE, X25519_PUBLIC_KEY_LENGTH, } from "./constants.js";
15
+ import { extractSequence } from "./crypto.js";
16
+ import { SbrpError, SbrpErrorCode, SignalCode, SignalReason } from "./types.js";
17
+ const textEncoder = new TextEncoder();
18
+ const textDecoder = new TextDecoder("utf-8", { fatal: false });
19
+ /**
20
+ * Frame type discriminant (wire byte).
21
+ *
22
+ * Frame types are organized by authority:
23
+ * - Endpoint frames (0x01-0x03): Forwarded by relay, E2EE
24
+ * - Signal frame (0x04): Daemon → Relay only
25
+ * - Keepalive frames (0x10-0x11): Connection-scoped, never forwarded
26
+ * - Control frame (0x20): Relay → Endpoint only
27
+ */
28
+ export const FrameType = {
29
+ // Endpoint frames (forwarded, E2EE)
30
+ HandshakeInit: 0x01,
31
+ HandshakeAccept: 0x02,
32
+ Data: 0x03, // Renamed from Encrypted for clarity
33
+ // Signal frame (daemon → relay)
34
+ Signal: 0x04,
35
+ // Keepalive frames (connection-scoped, never forwarded)
36
+ Ping: 0x10,
37
+ Pong: 0x11,
38
+ // Control frame (relay → endpoint)
39
+ Control: 0x20,
40
+ };
41
+ /**
42
+ * Wire control codes (uint16, §14).
43
+ *
44
+ * Codes use ranges for categorization:
45
+ * - 0x01xx: Authentication (terminal)
46
+ * - 0x02xx: Routing (varies)
47
+ * - 0x03xx: Session (terminal)
48
+ * - 0x04xx: Wire format (terminal)
49
+ * - 0x09xx: Rate limiting (non-terminal)
50
+ * - 0x10xx: Session state (non-terminal)
51
+ */
52
+ export const WireControlCode = {
53
+ // Authentication (0x01xx) - Terminal
54
+ Unauthorized: 0x0101,
55
+ Forbidden: 0x0102,
56
+ // Routing (0x02xx) - Varies
57
+ DaemonNotFound: 0x0201,
58
+ DaemonOffline: 0x0202, // Non-terminal
59
+ // Session (0x03xx) - Terminal
60
+ SessionNotFound: 0x0301,
61
+ SessionExpired: 0x0302,
62
+ // Wire Format (0x04xx) - Terminal
63
+ MalformedFrame: 0x0401,
64
+ PayloadTooLarge: 0x0402,
65
+ InvalidFrameType: 0x0403,
66
+ InvalidSessionId: 0x0404,
67
+ DisallowedSender: 0x0405,
68
+ // Internal (0x06xx) - Terminal
69
+ InternalError: 0x0601,
70
+ // Rate Limiting (0x09xx) - Non-terminal
71
+ RateLimited: 0x0901,
72
+ // Session State (0x10xx) - Non-terminal
73
+ SessionPaused: 0x1001,
74
+ SessionResumed: 0x1002,
75
+ SessionEnded: 0x1003,
76
+ SessionPending: 0x1004,
77
+ };
78
+ /** Check if a control code is terminal (closes connection) */
79
+ export function isTerminalCode(code) {
80
+ switch (code) {
81
+ case WireControlCode.DaemonOffline:
82
+ case WireControlCode.RateLimited:
83
+ case WireControlCode.SessionPaused:
84
+ case WireControlCode.SessionResumed:
85
+ case WireControlCode.SessionEnded:
86
+ case WireControlCode.SessionPending:
87
+ return false;
88
+ default:
89
+ return true;
90
+ }
91
+ }
92
+ // ============================================================================
93
+ // Control code mapping
94
+ // ============================================================================
95
+ const sbrpToWire = {
96
+ // Authentication
97
+ [SbrpErrorCode.Unauthorized]: WireControlCode.Unauthorized,
98
+ [SbrpErrorCode.Forbidden]: WireControlCode.Forbidden,
99
+ // Routing
100
+ [SbrpErrorCode.DaemonNotFound]: WireControlCode.DaemonNotFound,
101
+ [SbrpErrorCode.DaemonOffline]: WireControlCode.DaemonOffline,
102
+ // Session
103
+ [SbrpErrorCode.SessionNotFound]: WireControlCode.SessionNotFound,
104
+ [SbrpErrorCode.SessionExpired]: WireControlCode.SessionExpired,
105
+ // Wire Format
106
+ [SbrpErrorCode.MalformedFrame]: WireControlCode.MalformedFrame,
107
+ [SbrpErrorCode.PayloadTooLarge]: WireControlCode.PayloadTooLarge,
108
+ [SbrpErrorCode.InvalidFrameType]: WireControlCode.InvalidFrameType,
109
+ [SbrpErrorCode.InvalidSessionId]: WireControlCode.InvalidSessionId,
110
+ [SbrpErrorCode.DisallowedSender]: WireControlCode.DisallowedSender,
111
+ // Internal
112
+ [SbrpErrorCode.InternalError]: WireControlCode.InternalError,
113
+ // Rate Limiting
114
+ [SbrpErrorCode.RateLimited]: WireControlCode.RateLimited,
115
+ // Session State
116
+ [SbrpErrorCode.SessionPaused]: WireControlCode.SessionPaused,
117
+ [SbrpErrorCode.SessionResumed]: WireControlCode.SessionResumed,
118
+ [SbrpErrorCode.SessionEnded]: WireControlCode.SessionEnded,
119
+ [SbrpErrorCode.SessionPending]: WireControlCode.SessionPending,
120
+ };
121
+ const wireToSbrp = {
122
+ // Authentication
123
+ [WireControlCode.Unauthorized]: SbrpErrorCode.Unauthorized,
124
+ [WireControlCode.Forbidden]: SbrpErrorCode.Forbidden,
125
+ // Routing
126
+ [WireControlCode.DaemonNotFound]: SbrpErrorCode.DaemonNotFound,
127
+ [WireControlCode.DaemonOffline]: SbrpErrorCode.DaemonOffline,
128
+ // Session
129
+ [WireControlCode.SessionNotFound]: SbrpErrorCode.SessionNotFound,
130
+ [WireControlCode.SessionExpired]: SbrpErrorCode.SessionExpired,
131
+ // Wire Format
132
+ [WireControlCode.MalformedFrame]: SbrpErrorCode.MalformedFrame,
133
+ [WireControlCode.PayloadTooLarge]: SbrpErrorCode.PayloadTooLarge,
134
+ [WireControlCode.InvalidFrameType]: SbrpErrorCode.InvalidFrameType,
135
+ [WireControlCode.InvalidSessionId]: SbrpErrorCode.InvalidSessionId,
136
+ [WireControlCode.DisallowedSender]: SbrpErrorCode.DisallowedSender,
137
+ // Internal
138
+ [WireControlCode.InternalError]: SbrpErrorCode.InternalError,
139
+ // Rate Limiting
140
+ [WireControlCode.RateLimited]: SbrpErrorCode.RateLimited,
141
+ // Session State
142
+ [WireControlCode.SessionPaused]: SbrpErrorCode.SessionPaused,
143
+ [WireControlCode.SessionResumed]: SbrpErrorCode.SessionResumed,
144
+ [WireControlCode.SessionEnded]: SbrpErrorCode.SessionEnded,
145
+ [WireControlCode.SessionPending]: SbrpErrorCode.SessionPending,
146
+ };
147
+ /** Convert SbrpErrorCode to wire format (for relay-transmittable codes only) */
148
+ export function toWireControlCode(code) {
149
+ const wire = sbrpToWire[code];
150
+ if (wire === undefined) {
151
+ throw new Error(`Unknown or non-wire SbrpErrorCode: ${code}`);
152
+ }
153
+ return wire;
154
+ }
155
+ /** Convert wire control code to SbrpErrorCode */
156
+ export function fromWireControlCode(code) {
157
+ const sbrp = wireToSbrp[code];
158
+ if (sbrp === undefined) {
159
+ throw new Error(`Unknown WireControlCode: 0x${code.toString(16)}`);
160
+ }
161
+ return sbrp;
162
+ }
163
+ // ============================================================================
164
+ // Validation helpers
165
+ // ============================================================================
166
+ const MAX_UINT64 = 0xffffffffffffffffn;
167
+ /**
168
+ * Check if frame type requires non-zero sessionId (§13.2).
169
+ *
170
+ * Session-bound: HandshakeInit, HandshakeAccept, Data, Signal
171
+ * Connection-scoped (sessionId must be 0): Ping, Pong
172
+ * Variable (depends on content): Control
173
+ */
174
+ function isSessionBound(type) {
175
+ return (type === FrameType.HandshakeInit ||
176
+ type === FrameType.HandshakeAccept ||
177
+ type === FrameType.Data ||
178
+ type === FrameType.Signal);
179
+ }
180
+ /** Check if frame type requires sessionId = 0 (connection-scoped) */
181
+ function isConnectionScoped(type) {
182
+ return type === FrameType.Ping || type === FrameType.Pong;
183
+ }
184
+ function validateSessionId(sessionId, type) {
185
+ if (sessionId < 0n || sessionId > MAX_UINT64) {
186
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `SessionId out of uint64 range: ${sessionId}`);
187
+ }
188
+ if (isSessionBound(type) && sessionId === 0n) {
189
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `Session-bound frame type 0x${type.toString(16).padStart(2, "0")} requires non-zero sessionId`);
190
+ }
191
+ if (isConnectionScoped(type) && sessionId !== 0n) {
192
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `Connection-scoped frame type 0x${type.toString(16).padStart(2, "0")} requires sessionId = 0`);
193
+ }
194
+ }
195
+ // ============================================================================
196
+ // Low-level frame encoding/decoding
197
+ // ============================================================================
198
+ /**
199
+ * Encode a frame to binary wire format.
200
+ *
201
+ * @throws {SbrpError} if payload exceeds MAX_PAYLOAD_SIZE or sessionId is invalid
202
+ */
203
+ export function encodeFrame(type, sessionId, payload) {
204
+ validateSessionId(sessionId, type);
205
+ if (payload.length > MAX_PAYLOAD_SIZE) {
206
+ throw new SbrpError(SbrpErrorCode.PayloadTooLarge, `Payload size ${payload.length} exceeds maximum ${MAX_PAYLOAD_SIZE}`);
207
+ }
208
+ const frame = new Uint8Array(FRAME_HEADER_SIZE + payload.length);
209
+ const view = new DataView(frame.buffer);
210
+ frame[0] = type;
211
+ view.setUint32(1, payload.length, false);
212
+ view.setBigUint64(5, sessionId, false);
213
+ frame.set(payload, FRAME_HEADER_SIZE);
214
+ return frame;
215
+ }
216
+ /**
217
+ * Read frame header without decoding payload.
218
+ * Useful for routing decisions before full decode.
219
+ *
220
+ * Validates wire format constraints including non-zero sessionId
221
+ * for session-bound frames (§13.2).
222
+ *
223
+ * @throws {SbrpError} if buffer is too short, length exceeds max, or sessionId invalid
224
+ */
225
+ export function readFrameHeader(data) {
226
+ if (data.length < FRAME_HEADER_SIZE) {
227
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `Frame too short: ${data.length} < ${FRAME_HEADER_SIZE}`);
228
+ }
229
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
230
+ const type = data[0];
231
+ switch (type) {
232
+ case FrameType.HandshakeInit:
233
+ case FrameType.HandshakeAccept:
234
+ case FrameType.Data:
235
+ case FrameType.Signal:
236
+ case FrameType.Ping:
237
+ case FrameType.Pong:
238
+ case FrameType.Control:
239
+ break;
240
+ default:
241
+ throw new SbrpError(SbrpErrorCode.InvalidFrameType, `Unknown frame type: 0x${type.toString(16).padStart(2, "0")}`);
242
+ }
243
+ const length = view.getUint32(1, false);
244
+ const sessionId = view.getBigUint64(5, false);
245
+ if (length > MAX_PAYLOAD_SIZE) {
246
+ throw new SbrpError(SbrpErrorCode.PayloadTooLarge, `Payload length ${length} exceeds maximum ${MAX_PAYLOAD_SIZE}`);
247
+ }
248
+ // Wire format constraint: session-bound frames require non-zero sessionId
249
+ if (isSessionBound(type) && sessionId === 0n) {
250
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `Session-bound frame type 0x${type.toString(16).padStart(2, "0")} requires non-zero sessionId`);
251
+ }
252
+ // Wire format constraint: connection-scoped frames require sessionId = 0
253
+ if (isConnectionScoped(type) && sessionId !== 0n) {
254
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `Connection-scoped frame type 0x${type.toString(16).padStart(2, "0")} requires sessionId = 0`);
255
+ }
256
+ return { type, length, sessionId };
257
+ }
258
+ /**
259
+ * Decode a complete frame from binary data.
260
+ *
261
+ * Rejects trailing bytes to catch framing mistakes. Use `FrameDecoder`
262
+ * for streaming scenarios with multiple frames per buffer.
263
+ *
264
+ * @throws {SbrpError} if frame is malformed or has trailing bytes
265
+ */
266
+ export function decodeFrame(data) {
267
+ const header = readFrameHeader(data);
268
+ const expectedSize = FRAME_HEADER_SIZE + header.length;
269
+ if (data.length < expectedSize) {
270
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `Frame truncated: got ${data.length}, expected ${expectedSize}`);
271
+ }
272
+ if (data.length > expectedSize) {
273
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `Frame has ${data.length - expectedSize} trailing bytes`);
274
+ }
275
+ const payload = data.subarray(FRAME_HEADER_SIZE, expectedSize);
276
+ return { ...header, payload };
277
+ }
278
+ // ============================================================================
279
+ // High-level frame encoding (typed message → binary)
280
+ // ============================================================================
281
+ /**
282
+ * Encode HandshakeInit to wire frame.
283
+ *
284
+ * @throws {SbrpError} if initPublicKey is not exactly 32 bytes or sessionId is invalid
285
+ */
286
+ export function encodeHandshakeInit(sessionId, init) {
287
+ if (init.initPublicKey.length !== HANDSHAKE_INIT_PAYLOAD_SIZE) {
288
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `initPublicKey must be ${HANDSHAKE_INIT_PAYLOAD_SIZE} bytes, got ${init.initPublicKey.length}`);
289
+ }
290
+ return encodeFrame(FrameType.HandshakeInit, sessionId, init.initPublicKey);
291
+ }
292
+ /**
293
+ * Encode HandshakeAccept to wire frame.
294
+ *
295
+ * Wire layout (128 bytes): identityPublicKey(32) + acceptPublicKey(32) + signature(64)
296
+ *
297
+ * @throws {SbrpError} if field sizes are wrong or sessionId is invalid
298
+ */
299
+ export function encodeHandshakeAccept(sessionId, accept) {
300
+ if (accept.identityPublicKey.length !== ED25519_PUBLIC_KEY_LENGTH) {
301
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `identityPublicKey must be ${ED25519_PUBLIC_KEY_LENGTH} bytes, got ${accept.identityPublicKey.length}`);
302
+ }
303
+ if (accept.acceptPublicKey.length !== X25519_PUBLIC_KEY_LENGTH) {
304
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `acceptPublicKey must be ${X25519_PUBLIC_KEY_LENGTH} bytes, got ${accept.acceptPublicKey.length}`);
305
+ }
306
+ if (accept.signature.length !== ED25519_SIGNATURE_LENGTH) {
307
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `signature must be ${ED25519_SIGNATURE_LENGTH} bytes, got ${accept.signature.length}`);
308
+ }
309
+ const payload = new Uint8Array(HANDSHAKE_ACCEPT_PAYLOAD_SIZE);
310
+ payload.set(accept.identityPublicKey, 0);
311
+ payload.set(accept.acceptPublicKey, ED25519_PUBLIC_KEY_LENGTH);
312
+ payload.set(accept.signature, ED25519_PUBLIC_KEY_LENGTH + X25519_PUBLIC_KEY_LENGTH);
313
+ return encodeFrame(FrameType.HandshakeAccept, sessionId, payload);
314
+ }
315
+ /**
316
+ * Encode Data frame (encrypted message).
317
+ *
318
+ * @throws {SbrpError} if data is too short (< nonce + authTag) or sessionId is invalid
319
+ */
320
+ export function encodeData(sessionId, message) {
321
+ if (message.data.length < MIN_ENCRYPTED_PAYLOAD_SIZE) {
322
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `Data payload must be at least ${MIN_ENCRYPTED_PAYLOAD_SIZE} bytes, got ${message.data.length}`);
323
+ }
324
+ return encodeFrame(FrameType.Data, sessionId, message.data);
325
+ }
326
+ /**
327
+ * Encode Signal frame (daemon → relay).
328
+ *
329
+ * @param sessionId Session being signaled
330
+ * @param signal Signal code (ready or close)
331
+ * @param reason Reason code (for close signal)
332
+ */
333
+ export function encodeSignal(sessionId, signal, reason = SignalReason.None) {
334
+ const payload = new Uint8Array(SIGNAL_PAYLOAD_SIZE);
335
+ payload[0] = signal;
336
+ payload[1] = reason;
337
+ return encodeFrame(FrameType.Signal, sessionId, payload);
338
+ }
339
+ /**
340
+ * Encode Ping frame (connection-scoped keepalive).
341
+ *
342
+ * @param payload Optional 0-8 byte payload for RTT measurement
343
+ */
344
+ export function encodePing(payload = new Uint8Array(0)) {
345
+ if (payload.length > MAX_PING_PAYLOAD_SIZE) {
346
+ throw new SbrpError(SbrpErrorCode.PayloadTooLarge, `Ping payload must be 0-${MAX_PING_PAYLOAD_SIZE} bytes, got ${payload.length}`);
347
+ }
348
+ return encodeFrame(FrameType.Ping, 0n, payload);
349
+ }
350
+ /**
351
+ * Encode Pong frame (connection-scoped keepalive response).
352
+ *
353
+ * @param payload Payload from corresponding Ping (must be copied)
354
+ */
355
+ export function encodePong(payload = new Uint8Array(0)) {
356
+ if (payload.length > MAX_PING_PAYLOAD_SIZE) {
357
+ throw new SbrpError(SbrpErrorCode.PayloadTooLarge, `Pong payload must be 0-${MAX_PING_PAYLOAD_SIZE} bytes, got ${payload.length}`);
358
+ }
359
+ return encodeFrame(FrameType.Pong, 0n, payload);
360
+ }
361
+ /**
362
+ * Encode Control frame (relay → endpoint).
363
+ *
364
+ * @param sessionId Session ID (non-zero for session events, 0 for connection errors)
365
+ * @param code Control code from WireControlCode
366
+ * @param message Optional diagnostic message (for errors only)
367
+ */
368
+ export function encodeControl(sessionId, code, message) {
369
+ const msgBytes = message ? textEncoder.encode(message) : null;
370
+ const payload = new Uint8Array(2 + (msgBytes?.length ?? 0));
371
+ new DataView(payload.buffer).setUint16(0, code, false);
372
+ if (msgBytes)
373
+ payload.set(msgBytes, 2);
374
+ return encodeFrame(FrameType.Control, sessionId, payload);
375
+ }
376
+ // ============================================================================
377
+ // High-level frame decoding (Frame → typed message)
378
+ // ============================================================================
379
+ /**
380
+ * Decode HandshakeInit from frame.
381
+ *
382
+ * @throws {SbrpError} if frame type or payload size is invalid
383
+ */
384
+ export function decodeHandshakeInit(frame) {
385
+ if (frame.type !== FrameType.HandshakeInit) {
386
+ throw new SbrpError(SbrpErrorCode.InvalidFrameType, `Expected HandshakeInit (0x01), got 0x${frame.type.toString(16).padStart(2, "0")}`);
387
+ }
388
+ if (frame.payload.length !== HANDSHAKE_INIT_PAYLOAD_SIZE) {
389
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `HandshakeInit payload must be ${HANDSHAKE_INIT_PAYLOAD_SIZE} bytes, got ${frame.payload.length}`);
390
+ }
391
+ return {
392
+ type: "handshake.init",
393
+ initPublicKey: frame.payload.slice(),
394
+ };
395
+ }
396
+ /**
397
+ * Decode HandshakeAccept from frame.
398
+ *
399
+ * Wire layout (128 bytes): identityPublicKey(32) + acceptPublicKey(32) + signature(64)
400
+ *
401
+ * @throws {SbrpError} if frame type or payload size is invalid
402
+ */
403
+ export function decodeHandshakeAccept(frame) {
404
+ if (frame.type !== FrameType.HandshakeAccept) {
405
+ throw new SbrpError(SbrpErrorCode.InvalidFrameType, `Expected HandshakeAccept (0x02), got 0x${frame.type.toString(16).padStart(2, "0")}`);
406
+ }
407
+ if (frame.payload.length !== HANDSHAKE_ACCEPT_PAYLOAD_SIZE) {
408
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `HandshakeAccept payload must be ${HANDSHAKE_ACCEPT_PAYLOAD_SIZE} bytes, got ${frame.payload.length}`);
409
+ }
410
+ const keyEnd = ED25519_PUBLIC_KEY_LENGTH + X25519_PUBLIC_KEY_LENGTH;
411
+ return {
412
+ type: "handshake.accept",
413
+ identityPublicKey: frame.payload.slice(0, ED25519_PUBLIC_KEY_LENGTH),
414
+ acceptPublicKey: frame.payload.slice(ED25519_PUBLIC_KEY_LENGTH, keyEnd),
415
+ signature: frame.payload.slice(keyEnd, keyEnd + ED25519_SIGNATURE_LENGTH),
416
+ };
417
+ }
418
+ /**
419
+ * Decode Data frame (encrypted message).
420
+ *
421
+ * @throws {SbrpError} if frame type or payload is invalid
422
+ */
423
+ export function decodeData(frame) {
424
+ if (frame.type !== FrameType.Data) {
425
+ throw new SbrpError(SbrpErrorCode.InvalidFrameType, `Expected Data (0x03), got 0x${frame.type.toString(16).padStart(2, "0")}`);
426
+ }
427
+ if (frame.payload.length < MIN_ENCRYPTED_PAYLOAD_SIZE) {
428
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `Data payload must be at least ${MIN_ENCRYPTED_PAYLOAD_SIZE} bytes, got ${frame.payload.length}`);
429
+ }
430
+ const seq = extractSequence(frame.payload);
431
+ return {
432
+ type: "encrypted",
433
+ seq,
434
+ data: frame.payload.slice(),
435
+ };
436
+ }
437
+ /**
438
+ * Decode Signal frame (daemon → relay).
439
+ *
440
+ * @throws {SbrpError} if frame type, payload size, or signal values are invalid
441
+ */
442
+ export function decodeSignal(frame) {
443
+ if (frame.type !== FrameType.Signal) {
444
+ throw new SbrpError(SbrpErrorCode.InvalidFrameType, `Expected Signal (0x04), got 0x${frame.type.toString(16).padStart(2, "0")}`);
445
+ }
446
+ if (frame.payload.length !== SIGNAL_PAYLOAD_SIZE) {
447
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `Signal payload must be ${SIGNAL_PAYLOAD_SIZE} bytes, got ${frame.payload.length}`);
448
+ }
449
+ // Length validated above (exactly SIGNAL_PAYLOAD_SIZE = 2 bytes)
450
+ const signal = frame.payload[0];
451
+ const reason = frame.payload[1];
452
+ if (signal !== SignalCode.Ready && signal !== SignalCode.Close) {
453
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `Unknown signal code: 0x${signal.toString(16).padStart(2, "0")}`);
454
+ }
455
+ switch (reason) {
456
+ case SignalReason.None:
457
+ case SignalReason.StateLost:
458
+ case SignalReason.Shutdown:
459
+ case SignalReason.Policy:
460
+ case SignalReason.Error:
461
+ break;
462
+ default:
463
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `Unknown signal reason: 0x${reason.toString(16).padStart(2, "0")}`);
464
+ }
465
+ return { signal: signal, reason: reason };
466
+ }
467
+ /**
468
+ * Decode Control frame (relay → endpoint).
469
+ *
470
+ * Invalid UTF-8 sequences in message are replaced with U+FFFD.
471
+ *
472
+ * @throws {SbrpError} if frame type or payload is invalid
473
+ */
474
+ export function decodeControl(frame) {
475
+ if (frame.type !== FrameType.Control) {
476
+ throw new SbrpError(SbrpErrorCode.InvalidFrameType, `Expected Control (0x20), got 0x${frame.type.toString(16).padStart(2, "0")}`);
477
+ }
478
+ if (frame.payload.length < MIN_CONTROL_PAYLOAD_SIZE) {
479
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `Control payload must be at least ${MIN_CONTROL_PAYLOAD_SIZE} bytes, got ${frame.payload.length}`);
480
+ }
481
+ const view = new DataView(frame.payload.buffer, frame.payload.byteOffset, frame.payload.byteLength);
482
+ const rawCode = view.getUint16(0, false);
483
+ if (wireToSbrp[rawCode] === undefined) {
484
+ throw new SbrpError(SbrpErrorCode.MalformedFrame, `Unknown control code: 0x${rawCode.toString(16).padStart(4, "0")}`);
485
+ }
486
+ const code = rawCode;
487
+ // TextDecoder with fatal:false replaces invalid UTF-8 with U+FFFD
488
+ const message = textDecoder.decode(frame.payload.subarray(2));
489
+ return { code, message };
490
+ }
491
+ // ============================================================================
492
+ // Streaming frame decoder
493
+ // ============================================================================
494
+ /**
495
+ * Streaming frame decoder for incremental parsing.
496
+ *
497
+ * Accumulates bytes and yields complete frames. Useful when frames
498
+ * may be fragmented across WebSocket messages or TCP reads.
499
+ *
500
+ * @example
501
+ * ```typescript
502
+ * const decoder = new FrameDecoder();
503
+ * ws.on("message", (data) => {
504
+ * for (const frame of decoder.push(data)) {
505
+ * handleFrame(frame);
506
+ * }
507
+ * });
508
+ * ```
509
+ */
510
+ export class FrameDecoder {
511
+ buffer = new Uint8Array(0);
512
+ /**
513
+ * Push data and yield any complete frames.
514
+ */
515
+ *push(data) {
516
+ // Append to buffer
517
+ if (this.buffer.length === 0) {
518
+ this.buffer = data;
519
+ }
520
+ else {
521
+ const combined = new Uint8Array(this.buffer.length + data.length);
522
+ combined.set(this.buffer, 0);
523
+ combined.set(data, this.buffer.length);
524
+ this.buffer = combined;
525
+ }
526
+ // Yield complete frames
527
+ while (this.buffer.length >= FRAME_HEADER_SIZE) {
528
+ const header = readFrameHeader(this.buffer);
529
+ const frameSize = FRAME_HEADER_SIZE + header.length;
530
+ if (this.buffer.length < frameSize) {
531
+ break; // Incomplete frame, wait for more data
532
+ }
533
+ const payload = this.buffer.subarray(FRAME_HEADER_SIZE, frameSize);
534
+ yield { ...header, payload };
535
+ this.buffer = this.buffer.subarray(frameSize);
536
+ }
537
+ }
538
+ /** Reset decoder state, discarding any buffered data */
539
+ reset() {
540
+ this.buffer = new Uint8Array(0);
541
+ }
542
+ /** Number of bytes currently buffered */
543
+ get bufferedBytes() {
544
+ return this.buffer.length;
545
+ }
546
+ }
547
+ //# sourceMappingURL=frame.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame.js","sourceRoot":"","sources":["../src/frame.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { DaemonId, EphemeralKeyPair, HandshakeAccept, HandshakeInit, IdentityKeyPair, SessionKeys } from "./types.js";
2
+ /**
3
+ * Create a handshake init message (client side).
4
+ *
5
+ * Generates an ephemeral X25519 keypair for this session.
6
+ */
7
+ export declare function createHandshakeInit(): {
8
+ message: HandshakeInit;
9
+ ephemeralKeyPair: EphemeralKeyPair;
10
+ };
11
+ /**
12
+ * Process handshake init and create accept message (daemon side).
13
+ *
14
+ * 1. Generate ephemeral X25519 keypair
15
+ * 2. Sign ephemeral public key with identity key (context-bound)
16
+ * 3. Derive session keys
17
+ *
18
+ * NOTE: Callers MUST enforce a 30-second handshake timeout per SBRP §1.4.
19
+ * This function does not track time; timeout enforcement is a transport concern.
20
+ */
21
+ export declare function processHandshakeInit(init: HandshakeInit, daemonId: DaemonId, identityKeyPair: IdentityKeyPair): {
22
+ message: HandshakeAccept;
23
+ sessionKeys: SessionKeys;
24
+ };
25
+ /**
26
+ * Process handshake accept message (client side).
27
+ *
28
+ * 1. Verify signature using PINNED identity key (TOFU)
29
+ * 2. Derive session keys using same transcript hash as daemon
30
+ *
31
+ * NOTE: Callers MUST enforce a 30-second handshake timeout per SBRP §1.4.
32
+ * This function does not track time; timeout enforcement is a transport concern.
33
+ *
34
+ * @param ephemeralKeyPair The privateKey is zeroized in-place after key derivation.
35
+ * @throws {SbrpError} IdentityKeyChanged if advertised key doesn't match pinned key
36
+ * @throws {SbrpError} HandshakeFailed if signature verification fails
37
+ */
38
+ export declare function processHandshakeAccept(accept: HandshakeAccept, daemonId: DaemonId, pinnedIdentityPublicKey: Uint8Array, ephemeralKeyPair: EphemeralKeyPair): SessionKeys;
39
+ //# sourceMappingURL=handshake.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake.d.ts","sourceRoot":"","sources":["../src/handshake.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACV,QAAQ,EACR,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,eAAe,EACf,WAAW,EACZ,MAAM,YAAY,CAAC;AAGpB;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI;IACrC,OAAO,EAAE,aAAa,CAAC;IACvB,gBAAgB,EAAE,gBAAgB,CAAC;CACpC,CASA;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,aAAa,EACnB,QAAQ,EAAE,QAAQ,EAClB,eAAe,EAAE,eAAe,GAC/B;IAAE,OAAO,EAAE,eAAe,CAAC;IAAC,WAAW,EAAE,WAAW,CAAA;CAAE,CAqCxD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,QAAQ,EAClB,uBAAuB,EAAE,UAAU,EACnC,gBAAgB,EAAE,gBAAgB,GACjC,WAAW,CAoDb"}