@floegence/flowersec-core 2.5.3 → 3.0.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 +46 -18
- package/dist/browser/connectSessionV3.d.ts +13 -0
- package/dist/browser/connectSessionV3.js +75 -0
- package/dist/browser/index.d.ts +3 -2
- package/dist/browser/index.js +2 -1
- package/dist/browser/v2.d.ts +3 -0
- package/dist/browser/v2.js +2 -0
- package/dist/facade.d.ts +15 -8
- package/dist/facade.js +5 -4
- package/dist/framing/jsonframe.d.ts +1 -1
- package/dist/framing/jsonframe.js +3 -1
- package/dist/interop/proxyServerPeer.js +1 -1
- package/dist/interop/serverParityPeer.js +8 -1
- package/dist/node/acceptor.d.ts +10 -0
- package/dist/node/acceptor.js +53 -7
- package/dist/node/acceptorV3.d.ts +57 -0
- package/dist/node/acceptorV3.js +227 -0
- package/dist/node/connectSessionV3.d.ts +21 -0
- package/dist/node/connectSessionV3.js +73 -0
- package/dist/node/index.d.ts +15 -10
- package/dist/node/index.js +6 -5
- package/dist/node/nativeTransportAddon.d.ts +39 -1
- package/dist/node/nativeTransportAddon.js +29 -5
- package/dist/node/rawQuicAdapterV3.d.ts +10 -0
- package/dist/node/rawQuicAdapterV3.js +76 -0
- package/dist/node/rawQuicServerV3.d.ts +24 -0
- package/dist/node/rawQuicServerV3.js +34 -0
- package/dist/node/runtimeAuthorizationV3.d.ts +29 -0
- package/dist/node/runtimeAuthorizationV3.js +126 -0
- package/dist/node/tunnelRuntimeV3.d.ts +57 -0
- package/dist/node/tunnelRuntimeV3.js +857 -0
- package/dist/node/v2.d.ts +11 -0
- package/dist/node/v2.js +6 -0
- package/dist/node/webSocketServerV3.d.ts +27 -0
- package/dist/node/webSocketServerV3.js +219 -0
- package/dist/proxy/runtime.js +39 -6
- package/dist/proxy/serviceWorker.d.ts +2 -0
- package/dist/proxy/serviceWorker.js +63 -7
- package/dist/proxy/windowBridge.d.ts +1 -0
- package/dist/proxy/windowBridge.js +93 -33
- package/dist/proxy/wsPatch.d.ts +1 -0
- package/dist/proxy/wsPatch.js +39 -6
- package/dist/public/streamHandlers.d.ts +3 -0
- package/dist/public/streamHandlers.js +6 -1
- package/dist/rpc/client.js +9 -7
- package/dist/rpc/server.js +44 -20
- package/dist/rpc/validate.d.ts +1 -0
- package/dist/rpc/validate.js +18 -6
- package/dist/v2/index.d.ts +14 -0
- package/dist/v2/index.js +7 -0
- package/dist/v2/protocol.js +23 -32
- package/dist/v2/publicSession.js +4 -0
- package/dist/v2/session.d.ts +5 -0
- package/dist/v2/session.js +94 -35
- package/dist/v3/admissionError.d.ts +4 -0
- package/dist/v3/admissionError.js +8 -0
- package/dist/v3/artifact.d.ts +145 -0
- package/dist/v3/artifact.js +1162 -0
- package/dist/v3/artifactLease.d.ts +17 -0
- package/dist/v3/artifactLease.js +109 -0
- package/dist/v3/browserRuntime.d.ts +37 -0
- package/dist/v3/browserRuntime.js +197 -0
- package/dist/v3/browserSessionRuntime.d.ts +2 -0
- package/dist/v3/browserSessionRuntime.js +21 -0
- package/dist/v3/capability.d.ts +32 -0
- package/dist/v3/capability.js +345 -0
- package/dist/v3/carrier.d.ts +84 -0
- package/dist/v3/carrier.js +411 -0
- package/dist/v3/connectionController.d.ts +87 -0
- package/dist/v3/connectionController.js +805 -0
- package/dist/v3/contract.d.ts +65 -0
- package/dist/v3/contract.js +3 -0
- package/dist/v3/controller.d.ts +43 -0
- package/dist/v3/controller.js +303 -0
- package/dist/v3/handshake.d.ts +81 -0
- package/dist/v3/handshake.js +471 -0
- package/dist/v3/jcs.d.ts +6 -0
- package/dist/v3/jcs.js +69 -0
- package/dist/v3/jsonPreflight.d.ts +1 -0
- package/dist/v3/jsonPreflight.js +251 -0
- package/dist/v3/nodeRuntime.d.ts +40 -0
- package/dist/v3/nodeRuntime.js +315 -0
- package/dist/v3/nodeSessionRuntime.d.ts +2 -0
- package/dist/v3/nodeSessionRuntime.js +13 -0
- package/dist/v3/protocol.d.ts +99 -0
- package/dist/v3/protocol.js +773 -0
- package/dist/v3/publicApi.d.ts +12 -0
- package/dist/v3/publicApi.js +46 -0
- package/dist/v3/publicSession.d.ts +1 -0
- package/dist/v3/publicSession.js +281 -0
- package/dist/v3/retryDisposition.d.ts +16 -0
- package/dist/v3/retryDisposition.js +30 -0
- package/dist/v3/runtimeAdapters.d.ts +9 -0
- package/dist/v3/runtimeAdapters.js +109 -0
- package/dist/v3/security.d.ts +34 -0
- package/dist/v3/security.js +100 -0
- package/dist/v3/serverAdmission.d.ts +38 -0
- package/dist/v3/serverAdmission.js +223 -0
- package/dist/v3/session.d.ts +318 -0
- package/dist/v3/session.js +2289 -0
- package/dist/v3/sessionConnector.d.ts +37 -0
- package/dist/v3/sessionConnector.js +440 -0
- package/dist/v3/streamLifetimeLedger.d.ts +31 -0
- package/dist/v3/streamLifetimeLedger.js +131 -0
- package/dist/v3/streamMetadata.d.ts +1 -0
- package/dist/v3/streamMetadata.js +7 -0
- package/dist/v3/transportConstants.d.ts +56 -0
- package/dist/v3/transportConstants.js +59 -0
- package/dist/v3/unreliableMessage.d.ts +44 -0
- package/dist/v3/unreliableMessage.js +284 -0
- package/dist/v3/webSocketCarrier.d.ts +31 -0
- package/dist/v3/webSocketCarrier.js +370 -0
- package/dist/v3/webTransportCarrier.d.ts +27 -0
- package/dist/v3/webTransportCarrier.js +534 -0
- package/dist/yamux/stream.d.ts +3 -0
- package/dist/yamux/stream.js +17 -4
- package/package.json +4 -4
- package/sbom/cyclonedx.json +6 -6
- package/sbom/spdx.json +11 -11
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
2
|
+
import { concatBytes, u32be } from "../utils/bin.js";
|
|
3
|
+
import { canonicalizeJCSV3 } from "./jcs.js";
|
|
4
|
+
import { preflightJSONV3 } from "./jsonPreflight.js";
|
|
5
|
+
import { FLOWERSEC_V3_CRYPTO_LABELS } from "./transportConstants.js";
|
|
6
|
+
const encoder = new TextEncoder();
|
|
7
|
+
const carriers = Object.freeze(["raw_quic", "websocket", "webtransport"]);
|
|
8
|
+
const token = /^[a-z][a-z0-9_]{0,127}$/;
|
|
9
|
+
const identities = new Set([
|
|
10
|
+
"go/native",
|
|
11
|
+
"rust/native",
|
|
12
|
+
"swift/ios",
|
|
13
|
+
"swift/linux",
|
|
14
|
+
"swift/macos",
|
|
15
|
+
"typescript/browser",
|
|
16
|
+
"typescript/node",
|
|
17
|
+
]);
|
|
18
|
+
const reasons = new Set([
|
|
19
|
+
"adapter_not_composed",
|
|
20
|
+
"browser_no_raw_udp",
|
|
21
|
+
"browser_websocket_api_unavailable",
|
|
22
|
+
"browser_webtransport_api_unavailable",
|
|
23
|
+
"driver_unavailable",
|
|
24
|
+
"node_native_transport_unavailable",
|
|
25
|
+
"node_webtransport_driver_unavailable",
|
|
26
|
+
"swift_apple_client_profile_excludes_raw_quic",
|
|
27
|
+
"swift_apple_client_profile_excludes_webtransport",
|
|
28
|
+
"websocket_adapter_not_supported_on_linux",
|
|
29
|
+
]);
|
|
30
|
+
export function defineRuntimeCapabilityDescriptorV3(runtime, tuples, unsupported) {
|
|
31
|
+
const descriptor = freezeDescriptor({
|
|
32
|
+
language: "typescript",
|
|
33
|
+
runtime,
|
|
34
|
+
schemaVersion: 3,
|
|
35
|
+
tuples,
|
|
36
|
+
unsupported,
|
|
37
|
+
});
|
|
38
|
+
validateRuntimeCapabilityDescriptorV3(descriptor);
|
|
39
|
+
return descriptor;
|
|
40
|
+
}
|
|
41
|
+
export function encodeRuntimeCapabilityDescriptorV3(descriptor) {
|
|
42
|
+
validateRuntimeCapabilityDescriptorV3(descriptor);
|
|
43
|
+
return canonicalizeJCSV3(descriptor);
|
|
44
|
+
}
|
|
45
|
+
export function decodeRuntimeCapabilityDescriptorV3(raw) {
|
|
46
|
+
let text;
|
|
47
|
+
try {
|
|
48
|
+
text = typeof raw === "string" ? raw : new TextDecoder("utf-8", { fatal: true }).decode(raw);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
throw new TypeError("runtime capability descriptor is not UTF-8");
|
|
52
|
+
}
|
|
53
|
+
let parsed;
|
|
54
|
+
try {
|
|
55
|
+
preflightJSONV3(text);
|
|
56
|
+
parsed = JSON.parse(text);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
throw new TypeError("invalid runtime capability JSON");
|
|
60
|
+
}
|
|
61
|
+
const descriptor = decodeDescriptor(parsed);
|
|
62
|
+
if (encodeRuntimeCapabilityDescriptorV3(descriptor) !== text) {
|
|
63
|
+
throw new TypeError("runtime capability descriptor is not canonical JCS");
|
|
64
|
+
}
|
|
65
|
+
return descriptor;
|
|
66
|
+
}
|
|
67
|
+
export function runtimeCapabilityDigestV3(descriptor) {
|
|
68
|
+
const canonical = encoder.encode(encodeRuntimeCapabilityDescriptorV3(descriptor));
|
|
69
|
+
return sha256(concatBytes([
|
|
70
|
+
encoder.encode(FLOWERSEC_V3_CRYPTO_LABELS["runtime-capability"]),
|
|
71
|
+
u32be(canonical.length),
|
|
72
|
+
canonical,
|
|
73
|
+
]));
|
|
74
|
+
}
|
|
75
|
+
export function runtimeCapabilityDigestHexV3(descriptor) {
|
|
76
|
+
return Array.from(runtimeCapabilityDigestV3(descriptor), (value) => value.toString(16).padStart(2, "0")).join("");
|
|
77
|
+
}
|
|
78
|
+
export function supportsCandidateSecurityV3(descriptor, carrier, path, sessionRole, mode) {
|
|
79
|
+
validateRuntimeCapabilityDescriptorV3(descriptor);
|
|
80
|
+
return descriptor.tuples.some((tuple) => tuple.carrier === carrier && tuple.networkMode === "dial" && tuple.path === path &&
|
|
81
|
+
tuple.sessionRole === sessionRole && tuple.securityModes.includes(mode));
|
|
82
|
+
}
|
|
83
|
+
export function validateRuntimeCapabilityDescriptorV3(descriptor) {
|
|
84
|
+
if (descriptor.schemaVersion !== 3 || !token.test(descriptor.language) || !token.test(descriptor.runtime) ||
|
|
85
|
+
!identities.has(`${descriptor.language}/${descriptor.runtime}`)) {
|
|
86
|
+
throw new TypeError("invalid runtime capability descriptor identity");
|
|
87
|
+
}
|
|
88
|
+
const supported = new Set();
|
|
89
|
+
let previousTuple;
|
|
90
|
+
for (const tuple of descriptor.tuples) {
|
|
91
|
+
validateTuple(tuple);
|
|
92
|
+
if (previousTuple !== undefined && compareTuple(previousTuple, tuple) >= 0) {
|
|
93
|
+
throw new TypeError("runtime capability tuples must be unique and canonically sorted");
|
|
94
|
+
}
|
|
95
|
+
supported.add(tuple.carrier);
|
|
96
|
+
previousTuple = tuple;
|
|
97
|
+
}
|
|
98
|
+
const unavailable = new Set();
|
|
99
|
+
let previousUnsupported;
|
|
100
|
+
for (const item of descriptor.unsupported) {
|
|
101
|
+
if (!carriers.includes(item.carrier) || !reasons.has(item.reason) || supported.has(item.carrier)) {
|
|
102
|
+
throw new TypeError("invalid unsupported runtime carrier");
|
|
103
|
+
}
|
|
104
|
+
if (previousUnsupported !== undefined && compareASCII(previousUnsupported.carrier, item.carrier) >= 0) {
|
|
105
|
+
throw new TypeError("unsupported runtime carriers must be unique and canonically sorted");
|
|
106
|
+
}
|
|
107
|
+
unavailable.add(item.carrier);
|
|
108
|
+
previousUnsupported = item;
|
|
109
|
+
}
|
|
110
|
+
for (const carrier of carriers) {
|
|
111
|
+
if (supported.has(carrier) === unavailable.has(carrier)) {
|
|
112
|
+
throw new TypeError(`runtime capability must support or explicitly reject ${carrier}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
validateRuntimeShape(descriptor);
|
|
116
|
+
}
|
|
117
|
+
function validateTuple(tuple) {
|
|
118
|
+
if (!carriers.includes(tuple.carrier) || !["dial", "listen"].includes(tuple.networkMode) ||
|
|
119
|
+
!["direct", "tunnel"].includes(tuple.path) || !["client", "server"].includes(tuple.sessionRole) ||
|
|
120
|
+
tuple.reliableStreams !== true || typeof tuple.datagrams !== "boolean" || typeof tuple.migration !== "boolean") {
|
|
121
|
+
throw new TypeError("invalid runtime capability tuple");
|
|
122
|
+
}
|
|
123
|
+
const deployment = tuple.path === "direct"
|
|
124
|
+
? (tuple.networkMode === "dial" && tuple.sessionRole === "client") ||
|
|
125
|
+
(tuple.networkMode === "listen" && tuple.sessionRole === "server")
|
|
126
|
+
: tuple.networkMode === "dial";
|
|
127
|
+
if (!deployment)
|
|
128
|
+
throw new TypeError("invalid runtime capability deployment role");
|
|
129
|
+
if (tuple.carrier === "websocket" && (tuple.datagrams || tuple.migration)) {
|
|
130
|
+
throw new TypeError("WebSocket cannot advertise datagrams or migration");
|
|
131
|
+
}
|
|
132
|
+
const modes = tuple.securityModes;
|
|
133
|
+
if (!Array.isArray(modes) || (tuple.networkMode === "listen"
|
|
134
|
+
? modes.length !== 0
|
|
135
|
+
: !(equalStrings(modes, ["ca"]) || equalStrings(modes, ["pin"]) || equalStrings(modes, ["ca", "pin"])))) {
|
|
136
|
+
throw new TypeError("invalid runtime capability security modes");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function validateRuntimeShape(descriptor) {
|
|
140
|
+
const id = `${descriptor.language}/${descriptor.runtime}`;
|
|
141
|
+
validateRegisteredTupleSets(descriptor, id);
|
|
142
|
+
if (id === "typescript/browser") {
|
|
143
|
+
validateReason(descriptor, "raw_quic", "browser_no_raw_udp");
|
|
144
|
+
for (const tuple of descriptor.tuples) {
|
|
145
|
+
if (tuple.carrier === "websocket" && !equalStrings(tuple.securityModes, ["ca"])) {
|
|
146
|
+
throw new TypeError("browser WebSocket is CA-only");
|
|
147
|
+
}
|
|
148
|
+
if (tuple.carrier === "webtransport" &&
|
|
149
|
+
!(equalStrings(tuple.securityModes, ["ca"]) || equalStrings(tuple.securityModes, ["ca", "pin"]))) {
|
|
150
|
+
throw new TypeError("invalid browser WebTransport security modes");
|
|
151
|
+
}
|
|
152
|
+
if (tuple.networkMode !== "dial" || tuple.migration ||
|
|
153
|
+
tuple.datagrams !== (tuple.carrier === "webtransport")) {
|
|
154
|
+
throw new TypeError("invalid browser tuple");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else if (id === "typescript/node") {
|
|
159
|
+
validateReason(descriptor, "webtransport", "node_webtransport_driver_unavailable");
|
|
160
|
+
for (const tuple of descriptor.tuples) {
|
|
161
|
+
if (!equalStrings(tuple.securityModes, tuple.networkMode === "listen" ? [] : ["ca", "pin"]) ||
|
|
162
|
+
tuple.migration || tuple.datagrams !== (tuple.carrier === "raw_quic")) {
|
|
163
|
+
throw new TypeError("invalid Node tuple");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function validateRegisteredTupleSets(descriptor, id) {
|
|
169
|
+
const expected = registeredTupleSets(id);
|
|
170
|
+
if (expected === undefined)
|
|
171
|
+
throw new TypeError("invalid runtime capability identity");
|
|
172
|
+
for (const carrier of carriers) {
|
|
173
|
+
const actual = descriptor.tuples.filter((tuple) => tuple.carrier === carrier);
|
|
174
|
+
const unsupported = descriptor.unsupported.find((entry) => entry.carrier === carrier);
|
|
175
|
+
const carrierExpected = expected[carrier];
|
|
176
|
+
if (unsupported !== undefined) {
|
|
177
|
+
if (actual.length !== 0 || carrierExpected.unsupportedReasons?.includes(unsupported.reason) !== true) {
|
|
178
|
+
throw new TypeError("runtime capability does not match its registered carrier set");
|
|
179
|
+
}
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (carrierExpected.tupleSets?.some((set) => equalTupleSets(actual, set)) !== true) {
|
|
183
|
+
throw new TypeError("runtime capability does not match its registered tuple set");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function registeredTupleSets(id) {
|
|
188
|
+
const ca = ["ca"];
|
|
189
|
+
const caPin = ["ca", "pin"];
|
|
190
|
+
const websocket = (modes) => registeredTuples("websocket", false, false, true, modes);
|
|
191
|
+
const websocket3 = (modes) => registeredTuples("websocket", false, false, false, modes);
|
|
192
|
+
const rawQuic4M = registeredTuples("raw_quic", true, true, true, caPin);
|
|
193
|
+
const rawQuic4N = registeredTuples("raw_quic", true, false, true, caPin);
|
|
194
|
+
const webTransport4 = registeredTuples("webtransport", true, false, true, caPin);
|
|
195
|
+
const webTransport3CA = registeredTuples("webtransport", true, false, false, ca);
|
|
196
|
+
const webTransport3Pin = registeredTuples("webtransport", true, false, false, caPin);
|
|
197
|
+
switch (id) {
|
|
198
|
+
case "go/native":
|
|
199
|
+
return {
|
|
200
|
+
raw_quic: { tupleSets: [rawQuic4M] },
|
|
201
|
+
websocket: { tupleSets: [websocket(caPin)] },
|
|
202
|
+
webtransport: { tupleSets: [webTransport4] },
|
|
203
|
+
};
|
|
204
|
+
case "typescript/browser":
|
|
205
|
+
return {
|
|
206
|
+
raw_quic: { unsupportedReasons: ["browser_no_raw_udp"] },
|
|
207
|
+
websocket: { tupleSets: [websocket3(ca)], unsupportedReasons: ["browser_websocket_api_unavailable"] },
|
|
208
|
+
webtransport: { tupleSets: [webTransport3CA, webTransport3Pin], unsupportedReasons: ["browser_webtransport_api_unavailable"] },
|
|
209
|
+
};
|
|
210
|
+
case "typescript/node":
|
|
211
|
+
return {
|
|
212
|
+
raw_quic: { tupleSets: [rawQuic4N], unsupportedReasons: ["node_native_transport_unavailable"] },
|
|
213
|
+
websocket: { tupleSets: [websocket(caPin)] },
|
|
214
|
+
webtransport: { unsupportedReasons: ["node_webtransport_driver_unavailable"] },
|
|
215
|
+
};
|
|
216
|
+
case "rust/native":
|
|
217
|
+
return {
|
|
218
|
+
raw_quic: { tupleSets: [rawQuic4M] },
|
|
219
|
+
websocket: { tupleSets: [websocket(caPin)] },
|
|
220
|
+
webtransport: { unsupportedReasons: ["driver_unavailable"] },
|
|
221
|
+
};
|
|
222
|
+
case "swift/ios":
|
|
223
|
+
case "swift/macos":
|
|
224
|
+
return {
|
|
225
|
+
raw_quic: { unsupportedReasons: ["swift_apple_client_profile_excludes_raw_quic"] },
|
|
226
|
+
websocket: { tupleSets: [websocket3(caPin)] },
|
|
227
|
+
webtransport: { unsupportedReasons: ["swift_apple_client_profile_excludes_webtransport"] },
|
|
228
|
+
};
|
|
229
|
+
case "swift/linux":
|
|
230
|
+
return {
|
|
231
|
+
raw_quic: { unsupportedReasons: ["swift_apple_client_profile_excludes_raw_quic"] },
|
|
232
|
+
websocket: { unsupportedReasons: ["websocket_adapter_not_supported_on_linux"] },
|
|
233
|
+
webtransport: { unsupportedReasons: ["swift_apple_client_profile_excludes_webtransport"] },
|
|
234
|
+
};
|
|
235
|
+
default:
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function registeredTuples(carrier, datagrams, migration, includeListener, securityModes) {
|
|
240
|
+
const tuples = [
|
|
241
|
+
{ carrier, datagrams, migration, networkMode: "dial", path: "direct", reliableStreams: true, securityModes, sessionRole: "client" },
|
|
242
|
+
{ carrier, datagrams, migration, networkMode: "dial", path: "tunnel", reliableStreams: true, securityModes, sessionRole: "client" },
|
|
243
|
+
{ carrier, datagrams, migration, networkMode: "dial", path: "tunnel", reliableStreams: true, securityModes, sessionRole: "server" },
|
|
244
|
+
];
|
|
245
|
+
if (includeListener) {
|
|
246
|
+
tuples.push({ carrier, datagrams, migration: false, networkMode: "listen", path: "direct", reliableStreams: true, securityModes: [], sessionRole: "server" });
|
|
247
|
+
}
|
|
248
|
+
return tuples;
|
|
249
|
+
}
|
|
250
|
+
function equalTupleSets(actual, expected) {
|
|
251
|
+
return actual.length === expected.length && actual.every((tuple, index) => tupleKey(tuple) === tupleKey(expected[index]));
|
|
252
|
+
}
|
|
253
|
+
function tupleKey(tuple) {
|
|
254
|
+
return JSON.stringify([
|
|
255
|
+
tuple.carrier, tuple.networkMode, tuple.sessionRole, tuple.path,
|
|
256
|
+
tuple.reliableStreams, tuple.datagrams, tuple.migration, [...tuple.securityModes],
|
|
257
|
+
]);
|
|
258
|
+
}
|
|
259
|
+
function validateReason(descriptor, carrier, expected) {
|
|
260
|
+
const item = descriptor.unsupported.find((entry) => entry.carrier === carrier);
|
|
261
|
+
if (item !== undefined && item.reason !== expected && !(carrier === "raw_quic" && descriptor.runtime === "browser" && item.reason === "browser_no_raw_udp"))
|
|
262
|
+
throw new TypeError("invalid unsupported reason for runtime");
|
|
263
|
+
}
|
|
264
|
+
function decodeDescriptor(input) {
|
|
265
|
+
const object = exactObject(input, ["language", "runtime", "schemaVersion", "tuples", "unsupported"]);
|
|
266
|
+
if (typeof object.language !== "string" || typeof object.runtime !== "string" || object.schemaVersion !== 3 ||
|
|
267
|
+
!Array.isArray(object.tuples) || !Array.isArray(object.unsupported)) {
|
|
268
|
+
throw new TypeError("invalid runtime capability descriptor");
|
|
269
|
+
}
|
|
270
|
+
const tuples = object.tuples.map((input) => {
|
|
271
|
+
const value = exactObject(input, [
|
|
272
|
+
"carrier", "datagrams", "migration", "networkMode", "path", "reliableStreams", "securityModes", "sessionRole",
|
|
273
|
+
]);
|
|
274
|
+
if (typeof value.carrier !== "string" || typeof value.networkMode !== "string" ||
|
|
275
|
+
typeof value.path !== "string" || typeof value.sessionRole !== "string" ||
|
|
276
|
+
typeof value.datagrams !== "boolean" || typeof value.migration !== "boolean" ||
|
|
277
|
+
value.reliableStreams !== true || !Array.isArray(value.securityModes) ||
|
|
278
|
+
value.securityModes.some((mode) => mode !== "ca" && mode !== "pin")) {
|
|
279
|
+
throw new TypeError("invalid runtime capability tuple");
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
carrier: value.carrier,
|
|
283
|
+
datagrams: value.datagrams,
|
|
284
|
+
migration: value.migration,
|
|
285
|
+
networkMode: value.networkMode,
|
|
286
|
+
path: value.path,
|
|
287
|
+
reliableStreams: true,
|
|
288
|
+
securityModes: value.securityModes,
|
|
289
|
+
sessionRole: value.sessionRole,
|
|
290
|
+
};
|
|
291
|
+
});
|
|
292
|
+
const unsupported = object.unsupported.map((input) => {
|
|
293
|
+
const value = exactObject(input, ["carrier", "reason"]);
|
|
294
|
+
if (typeof value.carrier !== "string" || typeof value.reason !== "string") {
|
|
295
|
+
throw new TypeError("invalid unsupported runtime carrier");
|
|
296
|
+
}
|
|
297
|
+
return { carrier: value.carrier, reason: value.reason };
|
|
298
|
+
});
|
|
299
|
+
const descriptor = freezeDescriptor({
|
|
300
|
+
language: object.language,
|
|
301
|
+
runtime: object.runtime,
|
|
302
|
+
schemaVersion: 3,
|
|
303
|
+
tuples,
|
|
304
|
+
unsupported,
|
|
305
|
+
});
|
|
306
|
+
validateRuntimeCapabilityDescriptorV3(descriptor);
|
|
307
|
+
return descriptor;
|
|
308
|
+
}
|
|
309
|
+
function freezeDescriptor(descriptor) {
|
|
310
|
+
return Object.freeze({
|
|
311
|
+
...descriptor,
|
|
312
|
+
tuples: Object.freeze(descriptor.tuples.map((tuple) => Object.freeze({
|
|
313
|
+
...tuple,
|
|
314
|
+
securityModes: Object.freeze([...tuple.securityModes]),
|
|
315
|
+
}))),
|
|
316
|
+
unsupported: Object.freeze(descriptor.unsupported.map((entry) => Object.freeze({ ...entry }))),
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
function exactObject(input, keys) {
|
|
320
|
+
if (typeof input !== "object" || input === null || Array.isArray(input))
|
|
321
|
+
throw new TypeError("expected object");
|
|
322
|
+
const object = input;
|
|
323
|
+
const actual = Object.keys(object).sort(compareASCII);
|
|
324
|
+
const expected = [...keys].sort(compareASCII);
|
|
325
|
+
if (!equalStrings(actual, expected))
|
|
326
|
+
throw new TypeError("object has unknown or missing fields");
|
|
327
|
+
return object;
|
|
328
|
+
}
|
|
329
|
+
function compareTuple(left, right) {
|
|
330
|
+
return compareLists([left.carrier, left.networkMode, left.sessionRole, left.path], [right.carrier, right.networkMode, right.sessionRole, right.path]);
|
|
331
|
+
}
|
|
332
|
+
function compareLists(left, right) {
|
|
333
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
334
|
+
const order = compareASCII(left[index], right[index]);
|
|
335
|
+
if (order !== 0)
|
|
336
|
+
return order;
|
|
337
|
+
}
|
|
338
|
+
return 0;
|
|
339
|
+
}
|
|
340
|
+
function compareASCII(left, right) {
|
|
341
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
342
|
+
}
|
|
343
|
+
function equalStrings(left, right) {
|
|
344
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
345
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { CarrierKind, OperationOptionsV3, PathKind } from "./contract.js";
|
|
2
|
+
export type CarrierErrorCode = "aborted" | "closed" | "reset" | "stop_sending" | "stop_sending_unavailable" | "write_closed" | "datagram_unavailable";
|
|
3
|
+
export declare class CarrierError extends Error {
|
|
4
|
+
readonly code: CarrierErrorCode;
|
|
5
|
+
readonly cause?: unknown | undefined;
|
|
6
|
+
constructor(code: CarrierErrorCode, message: string, cause?: unknown | undefined);
|
|
7
|
+
}
|
|
8
|
+
export interface CarrierStreamV3 {
|
|
9
|
+
read(options?: OperationOptionsV3): Promise<Uint8Array | null>;
|
|
10
|
+
write(data: Uint8Array, options?: OperationOptionsV3): Promise<number>;
|
|
11
|
+
closeWrite(): Promise<void>;
|
|
12
|
+
stopSending(): Promise<void>;
|
|
13
|
+
reset(): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Synchronously initiates idempotent forced teardown. Pending and future
|
|
16
|
+
* stream primitives must settle, but this call does not await cleanup.
|
|
17
|
+
*/
|
|
18
|
+
abort(error?: Error): void;
|
|
19
|
+
}
|
|
20
|
+
export interface CarrierUnreliableDatagramsV3 {
|
|
21
|
+
readonly maxDatagramSize: number;
|
|
22
|
+
send(data: Uint8Array, options?: Readonly<{
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
expiresAt?: number;
|
|
25
|
+
}>): Promise<"accepted" | "dropped_budget" | "dropped_expired" | "dropped_carrier">;
|
|
26
|
+
receive(options?: OperationOptionsV3): Promise<Uint8Array>;
|
|
27
|
+
}
|
|
28
|
+
export interface CarrierSessionV3 {
|
|
29
|
+
readonly kind: CarrierKind;
|
|
30
|
+
readonly path: PathKind;
|
|
31
|
+
readonly inboundBidirectionalStreamCapacity: number;
|
|
32
|
+
readonly unreliableDatagrams?: CarrierUnreliableDatagramsV3 | undefined;
|
|
33
|
+
openStream(options?: OperationOptionsV3): Promise<CarrierStreamV3>;
|
|
34
|
+
acceptStream(options?: OperationOptionsV3): Promise<CarrierStreamV3>;
|
|
35
|
+
waitTermination(): Promise<void>;
|
|
36
|
+
close(error?: Readonly<{
|
|
37
|
+
code: number;
|
|
38
|
+
reason: string;
|
|
39
|
+
}>): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Synchronously initiates idempotent forced teardown. Pending and future
|
|
42
|
+
* session and stream primitives must settle, but this call does not await cleanup.
|
|
43
|
+
*/
|
|
44
|
+
abort(error?: Readonly<{
|
|
45
|
+
code: number;
|
|
46
|
+
reason: string;
|
|
47
|
+
}>): void;
|
|
48
|
+
}
|
|
49
|
+
export type NativeCarrierStreamV3 = Readonly<{
|
|
50
|
+
read(): Promise<Uint8Array | null>;
|
|
51
|
+
write(data: Uint8Array): Promise<number>;
|
|
52
|
+
closeWrite(): Promise<void>;
|
|
53
|
+
stopSending(): Promise<void>;
|
|
54
|
+
reset(): Promise<void>;
|
|
55
|
+
/** Cancels pending local primitives without initiating peer-visible teardown. */
|
|
56
|
+
cancelPending?(): void;
|
|
57
|
+
/** See {@link CarrierStreamV3.abort}. */
|
|
58
|
+
abort(error?: Error): void;
|
|
59
|
+
}>;
|
|
60
|
+
export type NativeCarrierSessionV3 = Readonly<{
|
|
61
|
+
kind: "webtransport" | "raw_quic";
|
|
62
|
+
path: PathKind;
|
|
63
|
+
inboundBidirectionalStreamCapacity: number;
|
|
64
|
+
unreliableDatagrams?: CarrierUnreliableDatagramsV3 | undefined;
|
|
65
|
+
openStream(options?: OperationOptionsV3): Promise<NativeCarrierStreamV3>;
|
|
66
|
+
acceptStream(options?: OperationOptionsV3): Promise<NativeCarrierStreamV3>;
|
|
67
|
+
waitTermination(): Promise<void>;
|
|
68
|
+
close(error?: Readonly<{
|
|
69
|
+
code: number;
|
|
70
|
+
reason: string;
|
|
71
|
+
}>): Promise<void>;
|
|
72
|
+
/** See {@link CarrierSessionV3.abort}. */
|
|
73
|
+
abort(error?: Readonly<{
|
|
74
|
+
code: number;
|
|
75
|
+
reason: string;
|
|
76
|
+
}>): void;
|
|
77
|
+
}>;
|
|
78
|
+
export declare function adaptNativeCarrierSessionV3(native: NativeCarrierSessionV3): CarrierSessionV3;
|
|
79
|
+
export declare function createMemoryCarrierPairV3(options: Readonly<{
|
|
80
|
+
kind: CarrierKind;
|
|
81
|
+
path: PathKind;
|
|
82
|
+
inboundBidirectionalStreamCapacity: number;
|
|
83
|
+
maxPendingStreams?: number;
|
|
84
|
+
}>): readonly [CarrierSessionV3, CarrierSessionV3];
|