@floegence/flowersec-core 0.27.0 → 2.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 +91 -22
- package/THIRD_PARTY_NOTICES.md +141 -0
- package/dist/browser/connectSession.d.ts +13 -0
- package/dist/browser/connectSession.js +40 -0
- package/dist/browser/index.d.ts +3 -10
- package/dist/browser/index.js +2 -5
- package/dist/browser/runtimeCapability.d.ts +13 -0
- package/dist/browser/runtimeCapability.js +27 -0
- package/dist/browser/sessionRuntime.d.ts +2 -0
- package/dist/browser/sessionRuntime.js +21 -0
- package/dist/browser/webTransportClient.d.ts +8 -0
- package/dist/browser/webTransportClient.js +55 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +126 -0
- package/dist/cliSpendMarker.d.ts +1 -0
- package/dist/cliSpendMarker.js +22 -0
- package/dist/connectionController.d.ts +54 -0
- package/dist/connectionController.js +438 -0
- package/dist/connector/adapters/webSocketCandidate.d.ts +7 -0
- package/dist/connector/adapters/webSocketCandidate.js +116 -0
- package/dist/connector/adapters/webTransportCandidate.d.ts +5 -0
- package/dist/connector/adapters/webTransportCandidate.js +73 -0
- package/dist/connector/admissionCommit.d.ts +31 -0
- package/dist/connector/admissionCommit.js +239 -0
- package/dist/connector/sessionAcceptor.d.ts +16 -0
- package/dist/connector/sessionAcceptor.js +114 -0
- package/dist/connector/sessionConfig.d.ts +3 -0
- package/dist/connector/sessionConfig.js +30 -0
- package/dist/connector/sessionConnector.d.ts +45 -0
- package/dist/connector/sessionConnector.js +603 -0
- package/dist/defaults.d.ts +7 -7
- package/dist/defaults.js +7 -7
- package/dist/facade.d.ts +12 -27
- package/dist/facade.js +6 -24
- package/dist/node/connectSession.d.ts +21 -0
- package/dist/node/connectSession.js +62 -0
- package/dist/node/index.d.ts +3 -11
- package/dist/node/index.js +2 -8
- package/dist/node/runtimeCapability.d.ts +7 -0
- package/dist/node/runtimeCapability.js +10 -0
- package/dist/node/sessionRuntime.d.ts +2 -0
- package/dist/node/sessionRuntime.js +13 -0
- package/dist/node/webTransportClient.d.ts +9 -0
- package/dist/node/webTransportClient.js +56 -0
- package/dist/node/webTransportServer.d.ts +22 -0
- package/dist/node/webTransportServer.js +100 -0
- package/dist/node/wsFactory.d.ts +3 -1
- package/dist/node/wsFactory.js +4 -3
- package/dist/proxy/controllerGuard.d.ts +4 -4
- package/dist/proxy/controllerGuard.js +83 -177
- package/dist/proxy/disableUpstreamServiceWorkerRegister.d.ts +3 -1
- package/dist/proxy/disableUpstreamServiceWorkerRegister.js +19 -17
- package/dist/proxy/index.d.ts +16 -15
- package/dist/proxy/index.js +8 -14
- package/dist/proxy/integration.d.ts +23 -71
- package/dist/proxy/integration.js +37 -340
- package/dist/proxy/registerServiceWorker.d.ts +1 -10
- package/dist/proxy/registerServiceWorker.js +43 -105
- package/dist/proxy/runtime.d.ts +3 -63
- package/dist/proxy/runtime.js +349 -462
- package/dist/proxy/scope.d.ts +6 -0
- package/dist/proxy/scope.js +131 -0
- package/dist/proxy/serviceWorker.d.ts +5 -16
- package/dist/proxy/serviceWorker.js +230 -617
- package/dist/proxy/stream.d.ts +9 -0
- package/dist/proxy/stream.js +40 -0
- package/dist/proxy/types.d.ts +78 -30
- package/dist/proxy/windowBridge.d.ts +61 -0
- package/dist/proxy/windowBridge.js +344 -0
- package/dist/proxy/wsPatch.d.ts +4 -14
- package/dist/proxy/wsPatch.js +173 -268
- package/dist/public/artifact.d.ts +11 -0
- package/dist/public/artifact.js +44 -0
- package/dist/public/artifactLease.d.ts +10 -0
- package/dist/public/artifactLease.js +48 -0
- package/dist/public/connectError.d.ts +6 -0
- package/dist/public/connectError.js +9 -0
- package/dist/public/contract.d.ts +83 -0
- package/dist/public/contract.js +17 -0
- package/dist/public/streamMetadata.d.ts +10 -0
- package/dist/public/streamMetadata.js +40 -0
- package/dist/rpc/caller.d.ts +1 -1
- package/dist/rpc/client.d.ts +1 -4
- package/dist/rpc/client.js +0 -21
- package/dist/rpc/rpcProxy.d.ts +1 -1
- package/dist/rpc/server.d.ts +1 -1
- package/dist/rpc/validate.d.ts +1 -1
- package/dist/rpc/wire.d.ts +15 -0
- package/dist/runtime/errors.d.ts +6 -0
- package/dist/runtime/errors.js +10 -0
- package/dist/transport/webSocketAdapter.d.ts +24 -0
- package/dist/transport/webSocketAdapter.js +234 -0
- package/dist/transport/webTransportAdapter.d.ts +27 -0
- package/dist/transport/webTransportAdapter.js +519 -0
- package/dist/utils/errors.d.ts +2 -20
- package/dist/utils/errors.js +73 -20
- package/dist/v2/admissionError.d.ts +4 -0
- package/dist/v2/admissionError.js +8 -0
- package/dist/v2/artifact.d.ts +128 -0
- package/dist/v2/artifact.js +1126 -0
- package/dist/v2/artifactLease.d.ts +1 -0
- package/dist/v2/artifactLease.js +7 -0
- package/dist/v2/capability.d.ts +29 -0
- package/dist/v2/capability.js +190 -0
- package/dist/v2/carrier.d.ts +79 -0
- package/dist/v2/carrier.js +405 -0
- package/dist/v2/contract.d.ts +37 -0
- package/dist/v2/contract.js +1 -0
- package/dist/v2/handshake.d.ts +80 -0
- package/dist/v2/handshake.js +470 -0
- package/dist/v2/opaqueArtifact.d.ts +1 -0
- package/dist/v2/opaqueArtifact.js +2 -0
- package/dist/v2/protocol.d.ts +99 -0
- package/dist/v2/protocol.js +739 -0
- package/dist/v2/publicSession.d.ts +1 -0
- package/dist/v2/publicSession.js +179 -0
- package/dist/v2/retryDisposition.d.ts +16 -0
- package/dist/v2/retryDisposition.js +30 -0
- package/dist/v2/session.d.ts +277 -0
- package/dist/v2/session.js +1879 -0
- package/dist/v2/streamLifetimeLedger.d.ts +31 -0
- package/dist/v2/streamLifetimeLedger.js +131 -0
- package/dist/v2/streamMetadata.d.ts +1 -0
- package/dist/v2/streamMetadata.js +7 -0
- package/dist/v2/unreliableMessage.d.ts +43 -0
- package/dist/v2/unreliableMessage.js +252 -0
- package/dist/vendor/tr46.d.ts +10 -0
- package/dist/vendor/tr46.js +501 -0
- package/dist/ws-client/binaryTransport.d.ts +0 -3
- package/dist/ws-client/binaryTransport.js +15 -39
- package/dist/yamux/errors.d.ts +0 -4
- package/dist/yamux/errors.js +0 -9
- package/dist/yamux/session.d.ts +0 -5
- package/dist/yamux/session.js +4 -70
- package/dist/yamux/stream.d.ts +1 -0
- package/dist/yamux/stream.js +4 -0
- package/package.json +29 -81
- package/sbom/cyclonedx.json +2764 -0
- package/sbom/spdx.json +1947 -0
- package/YAMUX_ALIGNMENT.md +0 -127
- package/dist/_examples/flowersec/demo/v1.facade.gen.d.ts +0 -12
- package/dist/_examples/flowersec/demo/v1.facade.gen.js +0 -15
- package/dist/_examples/flowersec/demo/v1.gen.d.ts +0 -16
- package/dist/_examples/flowersec/demo/v1.gen.js +0 -86
- package/dist/_examples/flowersec/demo/v1.rpc.gen.d.ts +0 -11
- package/dist/_examples/flowersec/demo/v1.rpc.gen.js +0 -22
- package/dist/browser/connect.d.ts +0 -13
- package/dist/browser/connect.js +0 -31
- package/dist/browser/reconnectConfig.d.ts +0 -15
- package/dist/browser/reconnectConfig.js +0 -20
- package/dist/client-connect/common.d.ts +0 -26
- package/dist/client-connect/common.js +0 -167
- package/dist/client-connect/connectCore.d.ts +0 -61
- package/dist/client-connect/connectCore.js +0 -572
- package/dist/client-connect/contract.d.ts +0 -8
- package/dist/client-connect/contract.js +0 -51
- package/dist/client-connect/termination.d.ts +0 -6
- package/dist/client-connect/termination.js +0 -7
- package/dist/client-connect/transportSecurity.d.ts +0 -27
- package/dist/client-connect/transportSecurity.js +0 -199
- package/dist/client-connect/tunnelAttachCloseReason.d.ts +0 -3
- package/dist/client-connect/tunnelAttachCloseReason.js +0 -23
- package/dist/client.d.ts +0 -23
- package/dist/connect/artifact.d.ts +0 -36
- package/dist/connect/artifact.js +0 -213
- package/dist/connect/resolveArtifact.d.ts +0 -22
- package/dist/connect/resolveArtifact.js +0 -87
- package/dist/controlplane/channelInit.d.ts +0 -27
- package/dist/controlplane/channelInit.js +0 -116
- package/dist/controlplane/http.d.ts +0 -16
- package/dist/controlplane/http.js +0 -59
- package/dist/controlplane/index.d.ts +0 -6
- package/dist/controlplane/index.js +0 -5
- package/dist/controlplane/issuer.d.ts +0 -18
- package/dist/controlplane/issuer.js +0 -113
- package/dist/controlplane/request.d.ts +0 -51
- package/dist/controlplane/request.js +0 -273
- package/dist/controlplane/token.d.ts +0 -33
- package/dist/controlplane/token.js +0 -119
- package/dist/direct-client/connect.d.ts +0 -9
- package/dist/direct-client/connect.js +0 -90
- package/dist/direct-client/index.d.ts +0 -1
- package/dist/direct-client/index.js +0 -1
- package/dist/e2ee/constants.d.ts +0 -9
- package/dist/e2ee/constants.js +0 -18
- package/dist/e2ee/errors.d.ts +0 -5
- package/dist/e2ee/errors.js +0 -8
- package/dist/e2ee/framing.d.ts +0 -12
- package/dist/e2ee/framing.js +0 -57
- package/dist/e2ee/handshake.d.ts +0 -88
- package/dist/e2ee/handshake.js +0 -353
- package/dist/e2ee/index.d.ts +0 -7
- package/dist/e2ee/index.js +0 -7
- package/dist/e2ee/kdf.d.ts +0 -15
- package/dist/e2ee/kdf.js +0 -39
- package/dist/e2ee/record.d.ts +0 -11
- package/dist/e2ee/record.js +0 -80
- package/dist/e2ee/secureChannel.d.ts +0 -93
- package/dist/e2ee/secureChannel.js +0 -342
- package/dist/e2ee/transcript.d.ts +0 -23
- package/dist/e2ee/transcript.js +0 -31
- package/dist/endpoint/index.d.ts +0 -84
- package/dist/endpoint/index.js +0 -557
- package/dist/endpoint/node.d.ts +0 -9
- package/dist/endpoint/node.js +0 -51
- package/dist/gen/flowersec/controlplane/v1.gen.d.ts +0 -36
- package/dist/gen/flowersec/controlplane/v1.gen.js +0 -135
- package/dist/gen/flowersec/direct/v1.gen.d.ts +0 -21
- package/dist/gen/flowersec/direct/v1.gen.js +0 -101
- package/dist/gen/flowersec/e2ee/v1.gen.d.ts +0 -68
- package/dist/gen/flowersec/e2ee/v1.gen.js +0 -194
- package/dist/gen/flowersec/rpc/v1.gen.d.ts +0 -30
- package/dist/gen/flowersec/rpc/v1.gen.js +0 -107
- package/dist/gen/flowersec/tunnel/v1.gen.d.ts +0 -23
- package/dist/gen/flowersec/tunnel/v1.gen.js +0 -104
- package/dist/index.d.ts +0 -22
- package/dist/index.js +0 -22
- package/dist/node/connect.d.ts +0 -10
- package/dist/node/connect.js +0 -29
- package/dist/node/reconnectConfig.d.ts +0 -16
- package/dist/node/reconnectConfig.js +0 -20
- package/dist/observability/index.d.ts +0 -1
- package/dist/observability/index.js +0 -1
- package/dist/observability/observer.d.ts +0 -52
- package/dist/observability/observer.js +0 -314
- package/dist/proxy/appWindow.d.ts +0 -37
- package/dist/proxy/appWindow.js +0 -273
- package/dist/proxy/bootstrap.d.ts +0 -35
- package/dist/proxy/bootstrap.js +0 -119
- package/dist/proxy/constants.d.ts +0 -7
- package/dist/proxy/constants.js +0 -8
- package/dist/proxy/controllerWindow.d.ts +0 -12
- package/dist/proxy/controllerWindow.js +0 -384
- package/dist/proxy/cookieJar.d.ts +0 -7
- package/dist/proxy/cookieJar.js +0 -136
- package/dist/proxy/headerPolicy.d.ts +0 -14
- package/dist/proxy/headerPolicy.js +0 -134
- package/dist/proxy/portStream.d.ts +0 -5
- package/dist/proxy/portStream.js +0 -256
- package/dist/proxy/preset.d.ts +0 -29
- package/dist/proxy/preset.js +0 -111
- package/dist/proxy/runtimeScope.d.ts +0 -57
- package/dist/proxy/runtimeScope.js +0 -218
- package/dist/proxy/server.d.ts +0 -28
- package/dist/proxy/server.js +0 -833
- package/dist/proxy/windowBridgeProtocol.d.ts +0 -65
- package/dist/proxy/windowBridgeProtocol.js +0 -11
- package/dist/reconnect/artifactControlplane.d.ts +0 -16
- package/dist/reconnect/artifactControlplane.js +0 -35
- package/dist/reconnect/index.d.ts +0 -36
- package/dist/reconnect/index.js +0 -388
- package/dist/rpc-proxy/index.d.ts +0 -1
- package/dist/rpc-proxy/index.js +0 -1
- package/dist/rpc-proxy/rpcProxy.d.ts +0 -1
- package/dist/rpc-proxy/rpcProxy.js +0 -1
- package/dist/streamhello/index.d.ts +0 -1
- package/dist/streamhello/index.js +0 -1
- package/dist/streamhello/streamHello.d.ts +0 -3
- package/dist/streamhello/streamHello.js +0 -13
- package/dist/streamio/index.d.ts +0 -32
- package/dist/streamio/index.js +0 -87
- package/dist/tunnel-client/connect.d.ts +0 -9
- package/dist/tunnel-client/connect.js +0 -147
- package/dist/tunnel-client/index.d.ts +0 -1
- package/dist/tunnel-client/index.js +0 -1
- package/dist/ws/index.d.ts +0 -1
- package/dist/ws/index.js +0 -1
- /package/dist/{client.js → rpc/wire.js} +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { createWebSocketCarrierSessionV2 } from "../../transport/webSocketAdapter.js";
|
|
2
|
+
import { WebSocketBinaryTransport } from "../../ws-client/binaryTransport.js";
|
|
3
|
+
export function createWebSocketCandidateFactoryV2(factory) {
|
|
4
|
+
return {
|
|
5
|
+
create(candidate, artifact) {
|
|
6
|
+
if (candidate.carrier !== "websocket")
|
|
7
|
+
throw new Error(`unsupported WebSocket carrier ${candidate.carrier}`);
|
|
8
|
+
return new WebSocketCandidateAttempt(candidate, artifact, factory);
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
class WebSocketCandidateAttempt {
|
|
13
|
+
candidate;
|
|
14
|
+
artifact;
|
|
15
|
+
factory;
|
|
16
|
+
socket;
|
|
17
|
+
transport;
|
|
18
|
+
aborted = false;
|
|
19
|
+
finalized = false;
|
|
20
|
+
constructor(candidate, artifact, factory) {
|
|
21
|
+
this.candidate = candidate;
|
|
22
|
+
this.artifact = artifact;
|
|
23
|
+
this.factory = factory;
|
|
24
|
+
}
|
|
25
|
+
async ready(signal) {
|
|
26
|
+
if (this.aborted)
|
|
27
|
+
throw new Error("WebSocket candidate aborted");
|
|
28
|
+
const path = this.artifact.path.kind;
|
|
29
|
+
const subprotocol = path === "direct" ? "flowersec.direct.v2" : "flowersec.tunnel.v2";
|
|
30
|
+
const socket = this.factory(this.candidate.normalized_url, subprotocol);
|
|
31
|
+
this.socket = socket;
|
|
32
|
+
await waitForOpen(socket, subprotocol, signal);
|
|
33
|
+
if (this.aborted)
|
|
34
|
+
throw new Error("WebSocket candidate aborted");
|
|
35
|
+
const transport = new WebSocketBinaryTransport(socket);
|
|
36
|
+
this.transport = transport;
|
|
37
|
+
const inboundBidirectionalStreamCapacity = this.artifact.session.max_inbound_streams + 2;
|
|
38
|
+
return {
|
|
39
|
+
candidate: this.candidate,
|
|
40
|
+
kind: "websocket",
|
|
41
|
+
path,
|
|
42
|
+
inboundBidirectionalStreamCapacity,
|
|
43
|
+
openAdmissionChannel: async () => ({
|
|
44
|
+
framing: "message",
|
|
45
|
+
write: async (data, options = {}) => await transport.writeBinary(data, options),
|
|
46
|
+
read: async (options = {}) => await transport.readBinary(options),
|
|
47
|
+
abort: () => transport.close(),
|
|
48
|
+
}),
|
|
49
|
+
finalize: () => {
|
|
50
|
+
if (this.finalized)
|
|
51
|
+
throw new Error("WebSocket candidate already finalized");
|
|
52
|
+
this.finalized = true;
|
|
53
|
+
return createWebSocketCarrierSessionV2(transport, {
|
|
54
|
+
path,
|
|
55
|
+
client: this.artifact.path.kind !== "tunnel" || this.artifact.path.role !== 2,
|
|
56
|
+
inboundBidirectionalStreamCapacity,
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
close: async () => transport.close(),
|
|
60
|
+
abort: () => transport.close(),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
abort() {
|
|
64
|
+
this.aborted = true;
|
|
65
|
+
this.transport?.close();
|
|
66
|
+
this.socket?.close();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function waitForOpen(socket, expectedProtocol, signal) {
|
|
70
|
+
if (socket.readyState === 1) {
|
|
71
|
+
validateSubprotocol(socket, expectedProtocol);
|
|
72
|
+
return Promise.resolve();
|
|
73
|
+
}
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
let settled = false;
|
|
76
|
+
const cleanup = () => {
|
|
77
|
+
socket.removeEventListener("open", open);
|
|
78
|
+
socket.removeEventListener("error", error);
|
|
79
|
+
socket.removeEventListener("close", close);
|
|
80
|
+
signal?.removeEventListener("abort", abort);
|
|
81
|
+
};
|
|
82
|
+
const finish = (failure) => {
|
|
83
|
+
if (settled)
|
|
84
|
+
return;
|
|
85
|
+
settled = true;
|
|
86
|
+
cleanup();
|
|
87
|
+
failure === undefined ? resolve() : reject(failure);
|
|
88
|
+
};
|
|
89
|
+
const open = () => {
|
|
90
|
+
try {
|
|
91
|
+
validateSubprotocol(socket, expectedProtocol);
|
|
92
|
+
finish();
|
|
93
|
+
}
|
|
94
|
+
catch (cause) {
|
|
95
|
+
finish(asError(cause));
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const error = () => finish(new Error("WebSocket candidate failed before ready"));
|
|
99
|
+
const close = () => finish(new Error("WebSocket candidate closed before ready"));
|
|
100
|
+
const abort = () => { socket.close(); finish(new Error("WebSocket candidate aborted")); };
|
|
101
|
+
socket.addEventListener("open", open);
|
|
102
|
+
socket.addEventListener("error", error);
|
|
103
|
+
socket.addEventListener("close", close);
|
|
104
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
105
|
+
if (signal?.aborted === true)
|
|
106
|
+
abort();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function validateSubprotocol(socket, expected) {
|
|
110
|
+
if (socket.protocol !== undefined && socket.protocol !== expected) {
|
|
111
|
+
throw new Error(`unexpected WebSocket subprotocol ${socket.protocol}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function asError(error) {
|
|
115
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
116
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ArtifactV2, CanonicalArtifactCandidateV2 } from "../../v2/artifact.js";
|
|
2
|
+
import { type NativeCarrierSessionV2 } from "../../v2/carrier.js";
|
|
3
|
+
import type { CandidateAttemptFactoryV2 } from "../sessionConnector.js";
|
|
4
|
+
export type WebTransportClientFactoryV2 = (candidate: CanonicalArtifactCandidateV2, artifact: ArtifactV2, signal: AbortSignal) => Promise<NativeCarrierSessionV2>;
|
|
5
|
+
export declare function createWebTransportCandidateFactoryV2(factory: WebTransportClientFactoryV2): CandidateAttemptFactoryV2;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { adaptNativeCarrierSessionV2, } from "../../v2/carrier.js";
|
|
2
|
+
export function createWebTransportCandidateFactoryV2(factory) {
|
|
3
|
+
return {
|
|
4
|
+
create(candidate, artifact) {
|
|
5
|
+
if (candidate.carrier !== "webtransport") {
|
|
6
|
+
throw new Error(`unsupported WebTransport carrier ${candidate.carrier}`);
|
|
7
|
+
}
|
|
8
|
+
return new WebTransportCandidateAttempt(candidate, artifact, factory);
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
class WebTransportCandidateAttempt {
|
|
13
|
+
candidate;
|
|
14
|
+
artifact;
|
|
15
|
+
factory;
|
|
16
|
+
controller = new AbortController();
|
|
17
|
+
carrier;
|
|
18
|
+
finalized = false;
|
|
19
|
+
constructor(candidate, artifact, factory) {
|
|
20
|
+
this.candidate = candidate;
|
|
21
|
+
this.artifact = artifact;
|
|
22
|
+
this.factory = factory;
|
|
23
|
+
}
|
|
24
|
+
async ready(signal) {
|
|
25
|
+
const unlink = linkAbort(signal, this.controller);
|
|
26
|
+
try {
|
|
27
|
+
const carrier = await this.factory(this.candidate, this.artifact, this.controller.signal);
|
|
28
|
+
this.carrier = carrier;
|
|
29
|
+
return {
|
|
30
|
+
candidate: this.candidate,
|
|
31
|
+
kind: carrier.kind,
|
|
32
|
+
path: carrier.path,
|
|
33
|
+
inboundBidirectionalStreamCapacity: carrier.inboundBidirectionalStreamCapacity,
|
|
34
|
+
openAdmissionChannel: async (channelSignal) => {
|
|
35
|
+
const options = channelSignal === undefined ? {} : { signal: channelSignal };
|
|
36
|
+
const stream = carrier.kind === "webtransport"
|
|
37
|
+
? await carrier.acceptStream(options)
|
|
38
|
+
: await carrier.openStream(options);
|
|
39
|
+
return {
|
|
40
|
+
framing: "stream",
|
|
41
|
+
stream,
|
|
42
|
+
abort: (error) => stream.abort(error),
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
finalize: () => {
|
|
46
|
+
if (this.finalized)
|
|
47
|
+
throw new Error("WebTransport candidate already finalized");
|
|
48
|
+
this.finalized = true;
|
|
49
|
+
return adaptNativeCarrierSessionV2(carrier);
|
|
50
|
+
},
|
|
51
|
+
close: async () => await carrier.close(),
|
|
52
|
+
abort: () => carrier.abort({ code: 6, reason: "candidate aborted" }),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
unlink();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
abort() {
|
|
60
|
+
this.controller.abort(new Error("WebTransport candidate aborted"));
|
|
61
|
+
this.carrier?.abort({ code: 6, reason: "candidate aborted" });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function linkAbort(parent, child) {
|
|
65
|
+
if (parent === undefined)
|
|
66
|
+
return () => undefined;
|
|
67
|
+
const abort = () => child.abort(parent.reason);
|
|
68
|
+
if (parent.aborted)
|
|
69
|
+
abort();
|
|
70
|
+
else
|
|
71
|
+
parent.addEventListener("abort", abort, { once: true });
|
|
72
|
+
return () => parent.removeEventListener("abort", abort);
|
|
73
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type CanonicalArtifactCandidateV2 } from "../v2/artifact.js";
|
|
2
|
+
import type { CarrierSessionV2, NativeCarrierStreamV2 } from "../v2/carrier.js";
|
|
3
|
+
import type { CarrierKind, OperationOptionsV2, PathKind } from "../v2/contract.js";
|
|
4
|
+
import type { SessionConfigV2 } from "../v2/session.js";
|
|
5
|
+
import { type ArtifactLeaseV2 } from "../v2/artifactLease.js";
|
|
6
|
+
export declare class CredentialCommitError extends Error {
|
|
7
|
+
constructor(cause: unknown);
|
|
8
|
+
}
|
|
9
|
+
export type ClientAdmissionChannelV2 = Readonly<{
|
|
10
|
+
framing: "message";
|
|
11
|
+
write(data: Uint8Array, options?: OperationOptionsV2): Promise<void>;
|
|
12
|
+
read(options?: OperationOptionsV2): Promise<Uint8Array>;
|
|
13
|
+
abort(error: Error): void;
|
|
14
|
+
}> | Readonly<{
|
|
15
|
+
framing: "stream";
|
|
16
|
+
stream: NativeCarrierStreamV2;
|
|
17
|
+
abort(error: Error): void;
|
|
18
|
+
}>;
|
|
19
|
+
export type ReadyAdmissionTransportV2 = Readonly<{
|
|
20
|
+
candidate: CanonicalArtifactCandidateV2;
|
|
21
|
+
kind: CarrierKind;
|
|
22
|
+
path: PathKind;
|
|
23
|
+
inboundBidirectionalStreamCapacity: number;
|
|
24
|
+
openAdmissionChannel(signal?: AbortSignal): Promise<ClientAdmissionChannelV2>;
|
|
25
|
+
finalize(): CarrierSessionV2;
|
|
26
|
+
close(): Promise<void>;
|
|
27
|
+
abort(): void;
|
|
28
|
+
}>;
|
|
29
|
+
export declare function commitClientAdmissionV2(ready: ReadyAdmissionTransportV2, lease: ArtifactLeaseV2, assertCredentialValid: () => void, rawFSB2: Uint8Array, config: SessionConfigV2, signal?: AbortSignal): Promise<CarrierSessionV2>;
|
|
30
|
+
export declare function requireExactCarrierCapacityV2(physical: number, logical: number): void;
|
|
31
|
+
export declare function validateOutboundAdmissionV2(rawFSB2: Uint8Array, config: SessionConfigV2, carrier: CarrierKind): void;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { AdmissionStatusV2, computeSessionContractHashV2, decodeFSB2RequestV2, decodeFSA2ResponseV2, } from "../v2/artifact.js";
|
|
2
|
+
import { AdmissionSessionV2Error } from "../v2/admissionError.js";
|
|
3
|
+
import { base64urlDecode, base64urlEncode } from "../utils/base64url.js";
|
|
4
|
+
import { commitArtifactLeaseSpendV2, } from "../v2/artifactLease.js";
|
|
5
|
+
export class CredentialCommitError extends Error {
|
|
6
|
+
constructor(cause) {
|
|
7
|
+
super("durable credential spend failed", { cause });
|
|
8
|
+
this.name = "CredentialCommitError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export async function commitClientAdmissionV2(ready, lease, assertCredentialValid, rawFSB2, config, signal) {
|
|
12
|
+
throwIfAborted(signal);
|
|
13
|
+
requireReadyTransportBinding(ready, config);
|
|
14
|
+
validateOutboundAdmissionV2(rawFSB2, config, ready.kind);
|
|
15
|
+
const channel = await ready.openAdmissionChannel(signal);
|
|
16
|
+
try {
|
|
17
|
+
assertCredentialValid();
|
|
18
|
+
try {
|
|
19
|
+
await commitArtifactLeaseSpendV2(lease, signal);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
throw new CredentialCommitError(error);
|
|
23
|
+
}
|
|
24
|
+
assertCredentialValid();
|
|
25
|
+
throwIfAborted(signal);
|
|
26
|
+
const rawFSA2 = channel.framing === "message"
|
|
27
|
+
? await exchangeMessage(channel, rawFSB2, signal)
|
|
28
|
+
: await exchangeStream(channel.stream, rawFSB2, signal);
|
|
29
|
+
requireAdmissionSuccessV2(rawFSA2);
|
|
30
|
+
return ready.finalize();
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
const failure = asError(error);
|
|
34
|
+
channel.abort(failure);
|
|
35
|
+
ready.abort();
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function requireReadyTransportBinding(ready, config) {
|
|
40
|
+
if (ready.candidate.carrier !== ready.kind) {
|
|
41
|
+
throw new AdmissionSessionV2Error("carrier_mismatch", "ready carrier does not match its artifact candidate");
|
|
42
|
+
}
|
|
43
|
+
if (ready.path !== config.path) {
|
|
44
|
+
throw new AdmissionSessionV2Error("path_mismatch", "ready carrier path does not match SessionV2 path");
|
|
45
|
+
}
|
|
46
|
+
requireExactCarrierCapacityV2(ready.inboundBidirectionalStreamCapacity, config.maxInboundStreams);
|
|
47
|
+
}
|
|
48
|
+
async function exchangeMessage(channel, rawFSB2, signal) {
|
|
49
|
+
const options = signalOptions(signal);
|
|
50
|
+
await channel.write(rawFSB2, options);
|
|
51
|
+
return await channel.read(options);
|
|
52
|
+
}
|
|
53
|
+
async function exchangeStream(stream, rawFSB2, signal) {
|
|
54
|
+
await writeAll(stream, rawFSB2, signal);
|
|
55
|
+
await raceAbort(stream.closeWrite(), signal);
|
|
56
|
+
const reader = new NativeExactReader(stream);
|
|
57
|
+
const header = await reader.readExactly(8, signal);
|
|
58
|
+
const reasonLength = new DataView(header.buffer, header.byteOffset, header.byteLength).getUint16(6, false);
|
|
59
|
+
if (reasonLength > 64)
|
|
60
|
+
throw new AdmissionSessionV2Error("invalid_fsa2", "FSA2 reason exceeds limit");
|
|
61
|
+
const rawFSA2 = concat(header, await reader.readExactly(reasonLength, signal));
|
|
62
|
+
await reader.expectCleanEOF(signal);
|
|
63
|
+
return rawFSA2;
|
|
64
|
+
}
|
|
65
|
+
export function requireExactCarrierCapacityV2(physical, logical) {
|
|
66
|
+
if (!validLogicalStreamCapacity(logical) || physical !== logical + 2) {
|
|
67
|
+
throw new AdmissionSessionV2Error("stream_capacity_mismatch", "carrier inbound bidirectional stream capacity does not match SessionV2 logical limit");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function validateOutboundAdmissionV2(rawFSB2, config, carrier) {
|
|
71
|
+
let decoded;
|
|
72
|
+
try {
|
|
73
|
+
decoded = decodeFSB2RequestV2(rawFSB2);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
throw new AdmissionSessionV2Error("invalid_fsb2", error instanceof Error ? error.message : "invalid FSB2 request");
|
|
77
|
+
}
|
|
78
|
+
const request = decoded.request;
|
|
79
|
+
if (request.pathKind !== config.path) {
|
|
80
|
+
throw new AdmissionSessionV2Error("path_mismatch", "FSB2 path does not match SessionV2 path");
|
|
81
|
+
}
|
|
82
|
+
validateSessionContract(config);
|
|
83
|
+
const expectedRole = request.pathKind === "tunnel" && request.role === 2 ? "server" : "client";
|
|
84
|
+
if (config.role !== expectedRole) {
|
|
85
|
+
throw new AdmissionSessionV2Error("role_mismatch", "FSB2 role does not match SessionV2 role");
|
|
86
|
+
}
|
|
87
|
+
if (request.channel_id !== config.channelID ||
|
|
88
|
+
request.session_contract_hash_b64u !== base64urlEncode(config.sessionContractHash)) {
|
|
89
|
+
throw new AdmissionSessionV2Error("session_config_mismatch", "FSB2 session identity does not match SessionV2 config");
|
|
90
|
+
}
|
|
91
|
+
if (request.pathKind === "tunnel" && request.endpoint_instance_id !== config.localEndpointInstanceID) {
|
|
92
|
+
throw new AdmissionSessionV2Error("endpoint_mismatch", "FSB2 endpoint identity does not match SessionV2 config");
|
|
93
|
+
}
|
|
94
|
+
if (!bytesEqual(decoded.localAdmissionBinding, config.localAdmissionBinding)) {
|
|
95
|
+
throw new AdmissionSessionV2Error("admission_binding_mismatch", "FSB2 admission binding does not match SessionV2 config");
|
|
96
|
+
}
|
|
97
|
+
if (request.pathKind === "direct" && !bytesEqual(decoded.localAdmissionBinding, config.peerAdmissionBinding)) {
|
|
98
|
+
throw new AdmissionSessionV2Error("peer_admission_binding_mismatch", "direct FSB2 admission binding does not match SessionV2 peer binding");
|
|
99
|
+
}
|
|
100
|
+
const candidate = request.candidates.find((entry) => entry.id === request.chosen_candidate_id);
|
|
101
|
+
if (candidate?.carrier !== carrier) {
|
|
102
|
+
throw new AdmissionSessionV2Error("carrier_mismatch", "FSB2 chosen candidate does not match the carrier");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function validateSessionContract(config) {
|
|
106
|
+
const contract = config.sessionContract;
|
|
107
|
+
if (contract === undefined) {
|
|
108
|
+
throw new AdmissionSessionV2Error("session_config_mismatch", "admitted SessionV2 requires a validated session contract");
|
|
109
|
+
}
|
|
110
|
+
let hash;
|
|
111
|
+
let hashBase64URL;
|
|
112
|
+
let psk;
|
|
113
|
+
try {
|
|
114
|
+
hashBase64URL = computeSessionContractHashV2(contract).hashBase64URL;
|
|
115
|
+
hash = base64urlDecode(hashBase64URL);
|
|
116
|
+
psk = base64urlDecode(contract.e2ee_psk_b64u);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
throw new AdmissionSessionV2Error("session_config_mismatch", error instanceof Error ? error.message : "invalid session contract");
|
|
120
|
+
}
|
|
121
|
+
if (contract.contract_hash_b64u !== hashBase64URL ||
|
|
122
|
+
!bytesEqual(hash, config.sessionContractHash) ||
|
|
123
|
+
contract.channel_id !== config.channelID ||
|
|
124
|
+
contract.max_inbound_streams !== config.maxInboundStreams ||
|
|
125
|
+
contract.default_suite !== config.suite ||
|
|
126
|
+
!contract.allowed_suites.includes(config.suite) ||
|
|
127
|
+
!bytesEqual(psk, config.psk) ||
|
|
128
|
+
contract.selected_features !== 0 ||
|
|
129
|
+
(config.idleTimeoutMs ?? 60_000) !== contract.idle_timeout_seconds * 1_000 ||
|
|
130
|
+
(config.deadlines?.establishTimeoutMs ?? 30_000) !== contract.establish_timeout_seconds * 1_000 ||
|
|
131
|
+
(config.deadlines?.rekeyPrepareTimeoutMs ?? 10_000) !== contract.rekey_prepare_timeout_seconds * 1_000 ||
|
|
132
|
+
(config.deadlines?.rekeyCompletionTimeoutMs ?? 30_000) !== contract.rekey_completion_timeout_seconds * 1_000) {
|
|
133
|
+
throw new AdmissionSessionV2Error("session_config_mismatch", "SessionV2 config does not match the signed session contract");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function requireAdmissionSuccessV2(rawFSA2) {
|
|
137
|
+
const response = decodeFSA2ResponseV2(rawFSA2);
|
|
138
|
+
if (response.status !== AdmissionStatusV2.Success) {
|
|
139
|
+
throw new AdmissionSessionV2Error(response.reason, `Flowersec v2 admission rejected: ${response.reason}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
class NativeExactReader {
|
|
143
|
+
stream;
|
|
144
|
+
chunks = [];
|
|
145
|
+
offset = 0;
|
|
146
|
+
bytes = 0;
|
|
147
|
+
constructor(stream) {
|
|
148
|
+
this.stream = stream;
|
|
149
|
+
}
|
|
150
|
+
async readExactly(length, signal) {
|
|
151
|
+
while (this.bytes < length) {
|
|
152
|
+
throwIfAborted(signal);
|
|
153
|
+
const chunk = await raceAbort(this.stream.read(), signal);
|
|
154
|
+
if (chunk === null)
|
|
155
|
+
throw new AdmissionSessionV2Error("truncated_fsa2", "unexpected admission EOF");
|
|
156
|
+
if (chunk.length === 0)
|
|
157
|
+
continue;
|
|
158
|
+
this.chunks.push(chunk);
|
|
159
|
+
this.bytes += chunk.length;
|
|
160
|
+
}
|
|
161
|
+
const output = new Uint8Array(length);
|
|
162
|
+
let written = 0;
|
|
163
|
+
while (written < length) {
|
|
164
|
+
const chunk = this.chunks[0];
|
|
165
|
+
const take = Math.min(length - written, chunk.length - this.offset);
|
|
166
|
+
output.set(chunk.subarray(this.offset, this.offset + take), written);
|
|
167
|
+
written += take;
|
|
168
|
+
this.offset += take;
|
|
169
|
+
this.bytes -= take;
|
|
170
|
+
if (this.offset === chunk.length) {
|
|
171
|
+
this.chunks.shift();
|
|
172
|
+
this.offset = 0;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return output;
|
|
176
|
+
}
|
|
177
|
+
async expectCleanEOF(signal) {
|
|
178
|
+
if (this.bytes !== 0)
|
|
179
|
+
throw new AdmissionSessionV2Error("invalid_fsa2", "trailing bytes after FSA2");
|
|
180
|
+
while (true) {
|
|
181
|
+
throwIfAborted(signal);
|
|
182
|
+
const chunk = await raceAbort(this.stream.read(), signal);
|
|
183
|
+
if (chunk === null)
|
|
184
|
+
return;
|
|
185
|
+
if (chunk.length !== 0)
|
|
186
|
+
throw new AdmissionSessionV2Error("invalid_fsa2", "trailing bytes after FSA2");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function writeAll(stream, value, signal) {
|
|
191
|
+
let offset = 0;
|
|
192
|
+
while (offset < value.length) {
|
|
193
|
+
throwIfAborted(signal);
|
|
194
|
+
const written = await raceAbort(stream.write(value.subarray(offset)), signal);
|
|
195
|
+
if (written < 1 || written > value.length - offset) {
|
|
196
|
+
throw new AdmissionSessionV2Error("short_write", "short admission write");
|
|
197
|
+
}
|
|
198
|
+
offset += written;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function signalOptions(signal) {
|
|
202
|
+
return signal === undefined ? {} : { signal };
|
|
203
|
+
}
|
|
204
|
+
function throwIfAborted(signal) {
|
|
205
|
+
if (signal?.aborted === true)
|
|
206
|
+
throw new AdmissionSessionV2Error("aborted", "admission aborted");
|
|
207
|
+
}
|
|
208
|
+
async function raceAbort(promise, signal) {
|
|
209
|
+
if (signal === undefined)
|
|
210
|
+
return await promise;
|
|
211
|
+
throwIfAborted(signal);
|
|
212
|
+
return await new Promise((resolve, reject) => {
|
|
213
|
+
const abort = () => reject(signal.reason instanceof Error
|
|
214
|
+
? signal.reason
|
|
215
|
+
: new AdmissionSessionV2Error("aborted", "admission aborted"));
|
|
216
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
217
|
+
void promise.then((value) => { signal.removeEventListener("abort", abort); resolve(value); }, (error) => { signal.removeEventListener("abort", abort); reject(error); });
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
function concat(left, right) {
|
|
221
|
+
const output = new Uint8Array(left.length + right.length);
|
|
222
|
+
output.set(left);
|
|
223
|
+
output.set(right, left.length);
|
|
224
|
+
return output;
|
|
225
|
+
}
|
|
226
|
+
function asError(error) {
|
|
227
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
228
|
+
}
|
|
229
|
+
function validLogicalStreamCapacity(logical) {
|
|
230
|
+
return Number.isInteger(logical) && logical >= 1 && logical <= 128;
|
|
231
|
+
}
|
|
232
|
+
function bytesEqual(left, right) {
|
|
233
|
+
if (left.length !== right.length)
|
|
234
|
+
return false;
|
|
235
|
+
let different = 0;
|
|
236
|
+
for (let index = 0; index < left.length; index++)
|
|
237
|
+
different |= left[index] ^ right[index];
|
|
238
|
+
return different === 0;
|
|
239
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AdmissionStatusV2, type ArtifactV2, type DecodedFSB2RequestV2 } from "../v2/artifact.js";
|
|
2
|
+
import { type NativeCarrierSessionV2 } from "../v2/carrier.js";
|
|
3
|
+
import type { SessionProtocolRuntimeV2, SessionV2 as InternalSessionV2 } from "../v2/session.js";
|
|
4
|
+
export type AdmissionDecisionV2 = Readonly<{
|
|
5
|
+
accepted: true;
|
|
6
|
+
artifact: ArtifactV2;
|
|
7
|
+
}> | Readonly<{
|
|
8
|
+
accepted: false;
|
|
9
|
+
status: AdmissionStatusV2.Reject | AdmissionStatusV2.Retryable;
|
|
10
|
+
reason: string;
|
|
11
|
+
}>;
|
|
12
|
+
export type AdmissionAuthorizerV2 = (request: DecodedFSB2RequestV2, signal?: AbortSignal) => Promise<AdmissionDecisionV2>;
|
|
13
|
+
export declare function acceptNativeSessionV2(carrier: NativeCarrierSessionV2, authorize: AdmissionAuthorizerV2, options: Readonly<{
|
|
14
|
+
runtime: SessionProtocolRuntimeV2;
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
}>): Promise<InternalSessionV2>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { AdmissionStatusV2, decodeFSB2RequestV2, encodeFSA2ResponseV2, } from "../v2/artifact.js";
|
|
2
|
+
import { adaptNativeCarrierSessionV2 } from "../v2/carrier.js";
|
|
3
|
+
import { establishSessionV2 } from "../v2/session.js";
|
|
4
|
+
import { AdmissionSessionV2Error } from "../v2/admissionError.js";
|
|
5
|
+
import { sessionConfigFromArtifactV2 } from "./sessionConfig.js";
|
|
6
|
+
export async function acceptNativeSessionV2(carrier, authorize, options) {
|
|
7
|
+
const admission = carrier.kind === "webtransport"
|
|
8
|
+
? await carrier.openStream(signalOptions(options.signal))
|
|
9
|
+
: await carrier.acceptStream(signalOptions(options.signal));
|
|
10
|
+
try {
|
|
11
|
+
const rawFSB2 = await readFSB2(admission, options.signal);
|
|
12
|
+
const decoded = decodeFSB2RequestV2(rawFSB2);
|
|
13
|
+
const decision = await authorize(decoded, options.signal);
|
|
14
|
+
const response = decision.accepted
|
|
15
|
+
? { status: AdmissionStatusV2.Success, reason: "" }
|
|
16
|
+
: { status: decision.status, reason: decision.reason };
|
|
17
|
+
await writeAll(admission, encodeFSA2ResponseV2(response), options.signal);
|
|
18
|
+
await raceAbort(admission.closeWrite(), options.signal);
|
|
19
|
+
if (!decision.accepted) {
|
|
20
|
+
throw new AdmissionSessionV2Error(decision.reason, `Flowersec v2 admission rejected: ${decision.reason}`);
|
|
21
|
+
}
|
|
22
|
+
return await establishSessionV2(adaptNativeCarrierSessionV2(carrier), sessionConfigFromArtifactV2(decision.artifact, rawFSB2, options.runtime, undefined, "server"), signalOptions(options.signal));
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
admission.abort(asError(error));
|
|
26
|
+
carrier.abort({ code: 6, reason: "admission failed" });
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function readFSB2(stream, signal) {
|
|
31
|
+
const reader = new NativeReader(stream);
|
|
32
|
+
const header = await reader.readExactly(12, signal);
|
|
33
|
+
const length = new DataView(header.buffer, header.byteOffset, header.byteLength).getUint32(8, false);
|
|
34
|
+
if (length < 1 || length > 32_768)
|
|
35
|
+
throw new Error("invalid FSB2 payload length");
|
|
36
|
+
const payload = await reader.readExactly(length, signal);
|
|
37
|
+
await reader.expectEOF(signal);
|
|
38
|
+
const raw = new Uint8Array(header.length + payload.length);
|
|
39
|
+
raw.set(header);
|
|
40
|
+
raw.set(payload, header.length);
|
|
41
|
+
return raw;
|
|
42
|
+
}
|
|
43
|
+
class NativeReader {
|
|
44
|
+
stream;
|
|
45
|
+
chunks = [];
|
|
46
|
+
offset = 0;
|
|
47
|
+
available = 0;
|
|
48
|
+
constructor(stream) {
|
|
49
|
+
this.stream = stream;
|
|
50
|
+
}
|
|
51
|
+
async readExactly(length, signal) {
|
|
52
|
+
while (this.available < length) {
|
|
53
|
+
const chunk = await raceAbort(this.stream.read(), signal);
|
|
54
|
+
if (chunk === null)
|
|
55
|
+
throw new Error("truncated admission stream");
|
|
56
|
+
if (chunk.length === 0)
|
|
57
|
+
continue;
|
|
58
|
+
this.chunks.push(chunk);
|
|
59
|
+
this.available += chunk.length;
|
|
60
|
+
}
|
|
61
|
+
const result = new Uint8Array(length);
|
|
62
|
+
let written = 0;
|
|
63
|
+
while (written < length) {
|
|
64
|
+
const chunk = this.chunks[0];
|
|
65
|
+
const take = Math.min(length - written, chunk.length - this.offset);
|
|
66
|
+
result.set(chunk.subarray(this.offset, this.offset + take), written);
|
|
67
|
+
this.offset += take;
|
|
68
|
+
this.available -= take;
|
|
69
|
+
written += take;
|
|
70
|
+
if (this.offset === chunk.length) {
|
|
71
|
+
this.chunks.shift();
|
|
72
|
+
this.offset = 0;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
async expectEOF(signal) {
|
|
78
|
+
if (this.available !== 0)
|
|
79
|
+
throw new Error("trailing bytes after FSB2");
|
|
80
|
+
while (true) {
|
|
81
|
+
const chunk = await raceAbort(this.stream.read(), signal);
|
|
82
|
+
if (chunk === null)
|
|
83
|
+
return;
|
|
84
|
+
if (chunk.length !== 0)
|
|
85
|
+
throw new Error("trailing bytes after FSB2");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function writeAll(stream, value, signal) {
|
|
90
|
+
let offset = 0;
|
|
91
|
+
while (offset < value.length) {
|
|
92
|
+
const written = await raceAbort(stream.write(value.subarray(offset)), signal);
|
|
93
|
+
if (written < 1 || written > value.length - offset)
|
|
94
|
+
throw new Error("short admission write");
|
|
95
|
+
offset += written;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function signalOptions(signal) {
|
|
99
|
+
return signal === undefined ? {} : { signal };
|
|
100
|
+
}
|
|
101
|
+
async function raceAbort(promise, signal) {
|
|
102
|
+
if (signal === undefined)
|
|
103
|
+
return await promise;
|
|
104
|
+
if (signal.aborted)
|
|
105
|
+
throw signal.reason;
|
|
106
|
+
return await new Promise((resolve, reject) => {
|
|
107
|
+
const abort = () => reject(signal.reason);
|
|
108
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
109
|
+
void promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function asError(error) {
|
|
113
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
114
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type ArtifactV2 } from "../v2/artifact.js";
|
|
2
|
+
import type { SessionConfigV2, SessionDeadlineFactoryV2, SessionProtocolRuntimeV2 } from "../v2/session.js";
|
|
3
|
+
export declare function sessionConfigFromArtifactV2(artifact: ArtifactV2, rawFSB2: Uint8Array, runtime: SessionProtocolRuntimeV2, deadlineFactory?: SessionDeadlineFactoryV2, role?: SessionConfigV2["role"]): SessionConfigV2;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { admissionBindingV2 } from "../v2/artifact.js";
|
|
2
|
+
import { base64urlDecode } from "../utils/base64url.js";
|
|
3
|
+
const SESSION_CLOSE_TIMEOUT_MS = 5_000;
|
|
4
|
+
export function sessionConfigFromArtifactV2(artifact, rawFSB2, runtime, deadlineFactory, role) {
|
|
5
|
+
const localBinding = admissionBindingV2(rawFSB2);
|
|
6
|
+
const tunnel = artifact.path.kind === "tunnel" ? artifact.path : undefined;
|
|
7
|
+
return {
|
|
8
|
+
role: role ?? (tunnel?.role === 2 ? "server" : "client"),
|
|
9
|
+
path: artifact.path.kind,
|
|
10
|
+
channelID: artifact.session.channel_id,
|
|
11
|
+
sessionContractHash: base64urlDecode(artifact.session.contract_hash_b64u),
|
|
12
|
+
suite: artifact.session.default_suite,
|
|
13
|
+
psk: base64urlDecode(artifact.session.e2ee_psk_b64u),
|
|
14
|
+
maxInboundStreams: artifact.session.max_inbound_streams,
|
|
15
|
+
sessionContract: artifact.session,
|
|
16
|
+
idleTimeoutMs: artifact.session.idle_timeout_seconds * 1_000,
|
|
17
|
+
closeTimeoutMs: SESSION_CLOSE_TIMEOUT_MS,
|
|
18
|
+
runtime,
|
|
19
|
+
localAdmissionBinding: localBinding,
|
|
20
|
+
peerAdmissionBinding: tunnel === undefined ? localBinding : new Uint8Array(32),
|
|
21
|
+
localEndpointInstanceID: tunnel?.local_endpoint_instance_id ?? "",
|
|
22
|
+
expectedPeerEndpointInstanceID: tunnel?.expected_peer_endpoint_instance_id ?? "",
|
|
23
|
+
deadlines: {
|
|
24
|
+
establishTimeoutMs: artifact.session.establish_timeout_seconds * 1_000,
|
|
25
|
+
rekeyPrepareTimeoutMs: artifact.session.rekey_prepare_timeout_seconds * 1_000,
|
|
26
|
+
rekeyCompletionTimeoutMs: artifact.session.rekey_completion_timeout_seconds * 1_000,
|
|
27
|
+
...(deadlineFactory === undefined ? {} : { factory: deadlineFactory }),
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|