@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.
- package/README.md +27 -8
- package/dist/.tsbuildinfo +1 -0
- package/dist/constants.d.ts +49 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +51 -0
- package/dist/constants.js.map +1 -0
- package/dist/crypto.d.ts +70 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +144 -0
- package/dist/crypto.js.map +1 -0
- package/dist/frame.d.ts +213 -0
- package/dist/frame.d.ts.map +1 -0
- package/dist/frame.js +547 -0
- package/dist/frame.js.map +1 -0
- package/dist/handshake.d.ts +39 -0
- package/dist/handshake.d.ts.map +1 -0
- package/dist/handshake.js +93 -0
- package/dist/handshake.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/replay.d.ts +32 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +88 -0
- package/dist/replay.js.map +1 -0
- package/dist/session.d.ts +67 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +122 -0
- package/dist/session.js.map +1 -0
- package/dist/types.d.ts +119 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +80 -0
- package/dist/types.js.map +1 -0
- package/package.json +4 -4
- package/src/constants.ts +3 -3
- package/src/crypto.test.ts +5 -5
- package/src/crypto.ts +9 -9
- package/src/frame.test.ts +59 -10
- package/src/frame.ts +101 -77
- package/src/handshake.test.ts +29 -41
- package/src/handshake.ts +25 -27
- package/src/index.ts +4 -10
- package/src/integration.test.ts +97 -138
- package/src/session.test.ts +12 -10
- 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"}
|