@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
package/dist/v2/protocol.js
CHANGED
|
@@ -616,68 +616,59 @@ export function canonicalStreamMetadataJSONV2Internal(value) {
|
|
|
616
616
|
if (!isJSONObject(value))
|
|
617
617
|
throw new ProtocolV2Error("OPEN metadata root must be an object");
|
|
618
618
|
const state = { nodes: -1 };
|
|
619
|
-
|
|
620
|
-
const canonical = canonicalJSONString(value);
|
|
619
|
+
const canonical = canonicalValidatedMetadataJSONString(value, 1, state);
|
|
621
620
|
if (encoder.encode(canonical).length > MAX_OPEN_METADATA_BYTES) {
|
|
622
621
|
throw new ProtocolV2Error("OPEN metadata is too large");
|
|
623
622
|
}
|
|
624
623
|
return canonical;
|
|
625
624
|
}
|
|
626
|
-
function
|
|
625
|
+
function canonicalValidatedMetadataJSONString(value, depth, state) {
|
|
627
626
|
if (depth > MAX_OPEN_METADATA_DEPTH)
|
|
628
627
|
throw new ProtocolV2Error("OPEN metadata exceeds depth limit");
|
|
629
628
|
state.nodes += 1;
|
|
630
629
|
if (state.nodes > MAX_OPEN_METADATA_NODES)
|
|
631
630
|
throw new ProtocolV2Error("OPEN metadata exceeds node limit");
|
|
632
|
-
if (value === null
|
|
633
|
-
return;
|
|
631
|
+
if (value === null)
|
|
632
|
+
return "null";
|
|
633
|
+
if (typeof value === "boolean")
|
|
634
|
+
return value ? "true" : "false";
|
|
634
635
|
if (typeof value === "number") {
|
|
635
|
-
if (!Number.isSafeInteger(value))
|
|
636
|
+
if (!Number.isSafeInteger(value) || Object.is(value, -0))
|
|
636
637
|
throw new ProtocolV2Error("OPEN metadata number is not an I-JSON safe integer");
|
|
637
|
-
return;
|
|
638
|
+
return value.toString(10);
|
|
638
639
|
}
|
|
639
640
|
if (typeof value === "string") {
|
|
640
641
|
if (!validOpenUnicodeString(value, MAX_OPEN_METADATA_STRING_BYTES, true)) {
|
|
641
642
|
throw new ProtocolV2Error("invalid OPEN metadata string");
|
|
642
643
|
}
|
|
643
|
-
return;
|
|
644
|
+
return quoteCanonicalJSONString(value);
|
|
644
645
|
}
|
|
645
646
|
if (Array.isArray(value)) {
|
|
646
|
-
|
|
647
|
+
const length = value.length;
|
|
648
|
+
if (length > MAX_OPEN_METADATA_ARRAY)
|
|
647
649
|
throw new ProtocolV2Error("OPEN metadata array is too large");
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
650
|
+
const items = new Array(length);
|
|
651
|
+
for (let index = 0; index < length; index++) {
|
|
652
|
+
const item = value[index];
|
|
653
|
+
items[index] = canonicalValidatedMetadataJSONString(item, depth + 1, state);
|
|
654
|
+
}
|
|
655
|
+
return `[${items.join(",")}]`;
|
|
651
656
|
}
|
|
652
657
|
if (!isJSONObject(value))
|
|
653
658
|
throw new ProtocolV2Error("unsupported OPEN metadata value");
|
|
654
|
-
const keys = Object.keys(value);
|
|
659
|
+
const keys = Object.keys(value).sort();
|
|
655
660
|
if (keys.length > MAX_OPEN_METADATA_KEYS)
|
|
656
661
|
throw new ProtocolV2Error("OPEN metadata object is too large");
|
|
662
|
+
const members = new Array(keys.length);
|
|
663
|
+
let index = 0;
|
|
657
664
|
for (const key of keys) {
|
|
658
665
|
if (!validOpenUnicodeString(key, MAX_OPEN_METADATA_KEY_BYTES, false)) {
|
|
659
666
|
throw new ProtocolV2Error("invalid OPEN metadata key");
|
|
660
667
|
}
|
|
661
|
-
|
|
668
|
+
const item = value[key];
|
|
669
|
+
members[index++] = `${quoteCanonicalJSONString(key)}:${canonicalValidatedMetadataJSONString(item, depth + 1, state)}`;
|
|
662
670
|
}
|
|
663
|
-
}
|
|
664
|
-
function canonicalJSONString(value) {
|
|
665
|
-
if (value === null)
|
|
666
|
-
return "null";
|
|
667
|
-
if (typeof value === "boolean")
|
|
668
|
-
return value ? "true" : "false";
|
|
669
|
-
if (typeof value === "number")
|
|
670
|
-
return value.toString(10);
|
|
671
|
-
if (typeof value === "string")
|
|
672
|
-
return quoteCanonicalJSONString(value);
|
|
673
|
-
if (Array.isArray(value))
|
|
674
|
-
return `[${value.map(canonicalJSONString).join(",")}]`;
|
|
675
|
-
if (!isJSONObject(value))
|
|
676
|
-
throw new ProtocolV2Error("unsupported OPEN metadata value");
|
|
677
|
-
return `{${Object.keys(value)
|
|
678
|
-
.sort()
|
|
679
|
-
.map((key) => `${quoteCanonicalJSONString(key)}:${canonicalJSONString(value[key])}`)
|
|
680
|
-
.join(",")}}`;
|
|
671
|
+
return `{${members.join(",")}}`;
|
|
681
672
|
}
|
|
682
673
|
function quoteCanonicalJSONString(value) {
|
|
683
674
|
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
package/dist/v2/publicSession.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SessionError } from "./contract.js";
|
|
2
2
|
import { createStreamMetadataV2, streamMetadataValuesV2 } from "./streamMetadata.js";
|
|
3
|
+
import { assertRpcTypeId } from "../rpc/validate.js";
|
|
3
4
|
/** @internal */
|
|
4
5
|
export function projectSessionV2(session) {
|
|
5
6
|
const notificationOwner = {
|
|
@@ -132,6 +133,7 @@ function projectRpcPeerV2(peer, notificationOwner) {
|
|
|
132
133
|
return Object.freeze({
|
|
133
134
|
async call(typeId, payload, decodeResponse, options) {
|
|
134
135
|
try {
|
|
136
|
+
assertRpcTypeId(typeId);
|
|
135
137
|
assertJsonValue(payload);
|
|
136
138
|
const result = await peer.call(typeId, payload, options?.signal);
|
|
137
139
|
if (result.error !== undefined) {
|
|
@@ -146,6 +148,7 @@ function projectRpcPeerV2(peer, notificationOwner) {
|
|
|
146
148
|
},
|
|
147
149
|
async notify(typeId, payload, options) {
|
|
148
150
|
try {
|
|
151
|
+
assertRpcTypeId(typeId);
|
|
149
152
|
assertJsonValue(payload);
|
|
150
153
|
if (options?.signal?.aborted)
|
|
151
154
|
throw options.signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
@@ -156,6 +159,7 @@ function projectRpcPeerV2(peer, notificationOwner) {
|
|
|
156
159
|
}
|
|
157
160
|
},
|
|
158
161
|
onNotify(typeId, decodePayload, handler) {
|
|
162
|
+
assertRpcTypeId(typeId);
|
|
159
163
|
if (notificationOwner.closed)
|
|
160
164
|
return () => undefined;
|
|
161
165
|
const unsubscribe = peer.onNotify(typeId, (payload) => {
|
package/dist/v2/session.d.ts
CHANGED
|
@@ -91,6 +91,7 @@ export declare class SessionV2 implements SessionV2Contract {
|
|
|
91
91
|
private controlReceiveEpoch;
|
|
92
92
|
private controlReceiveSequence;
|
|
93
93
|
private controlWriteTail;
|
|
94
|
+
private controlTerminalSealed;
|
|
94
95
|
private nextLogicalID;
|
|
95
96
|
private receivedGoAway;
|
|
96
97
|
private receivedGoAwayLastAccepted;
|
|
@@ -164,6 +165,10 @@ export declare class SessionV2 implements SessionV2Contract {
|
|
|
164
165
|
private acceptCarrierStreamAfterFSS2;
|
|
165
166
|
private resetInboundBeforeDelivery;
|
|
166
167
|
private sendControl;
|
|
168
|
+
private sendControlResponse;
|
|
169
|
+
private sendControlCleanup;
|
|
170
|
+
private sendTerminalControl;
|
|
171
|
+
private writeControl;
|
|
167
172
|
private readControl;
|
|
168
173
|
private controlLoop;
|
|
169
174
|
private closeOnce;
|
package/dist/v2/session.js
CHANGED
|
@@ -96,6 +96,7 @@ export class SessionV2 {
|
|
|
96
96
|
controlReceiveEpoch = 0;
|
|
97
97
|
controlReceiveSequence = 0n;
|
|
98
98
|
controlWriteTail = Promise.resolve();
|
|
99
|
+
controlTerminalSealed = false;
|
|
99
100
|
nextLogicalID;
|
|
100
101
|
receivedGoAway = false;
|
|
101
102
|
receivedGoAwayLastAccepted = 0n;
|
|
@@ -312,10 +313,11 @@ export class SessionV2 {
|
|
|
312
313
|
this.releaseStream(stream);
|
|
313
314
|
let controlConfirmed = false;
|
|
314
315
|
try {
|
|
315
|
-
await this.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
316
|
+
if (await this.sendControlCleanup(InnerTypeV2.StreamReset, idReason(stream.id, 6))) {
|
|
317
|
+
this.commitLocalReset(stream.id);
|
|
318
|
+
if (stream.hasRemoteFIN())
|
|
319
|
+
controlConfirmed = await this.confirmControlDelivery();
|
|
320
|
+
}
|
|
319
321
|
}
|
|
320
322
|
catch (cause) {
|
|
321
323
|
this.fail(asError(cause));
|
|
@@ -343,6 +345,8 @@ export class SessionV2 {
|
|
|
343
345
|
this.cleanupEpochRoots();
|
|
344
346
|
}
|
|
345
347
|
async confirmControlDelivery() {
|
|
348
|
+
if (this.lifecycle !== "open" || this.controlTerminalSealed)
|
|
349
|
+
return false;
|
|
346
350
|
const nonce = this.nextPing++;
|
|
347
351
|
const pending = deferred();
|
|
348
352
|
this.pings.set(nonce, pending);
|
|
@@ -567,33 +571,87 @@ export class SessionV2 {
|
|
|
567
571
|
async resetInboundBeforeDelivery(id, carrierStream) {
|
|
568
572
|
await carrierStream.reset().catch(() => undefined);
|
|
569
573
|
try {
|
|
570
|
-
await this.
|
|
571
|
-
|
|
574
|
+
if (await this.sendControlCleanup(InnerTypeV2.StreamReset, idReason(id, 3))) {
|
|
575
|
+
this.peerLedger.localResetCommitted(id);
|
|
576
|
+
}
|
|
572
577
|
}
|
|
573
578
|
catch (error) {
|
|
574
579
|
this.fail(asError(error));
|
|
575
580
|
}
|
|
576
581
|
}
|
|
577
582
|
async sendControl(type, payload, signal) {
|
|
583
|
+
if (this.controlTerminalSealed) {
|
|
584
|
+
throw this.terminalError ?? new SessionV2Error("closed", "Flowersec v2 control stream is sealed");
|
|
585
|
+
}
|
|
578
586
|
const task = this.controlWriteTail.then(async () => {
|
|
579
587
|
if (this.lifecycle === "closed")
|
|
580
588
|
throw this.terminalError ?? new SessionV2Error("closed", "Flowersec v2 session closed");
|
|
581
|
-
|
|
582
|
-
const roots = this.rootForSend(this.controlSendEpoch);
|
|
583
|
-
const material = deriveControlMaterial(roots.controlRoot, this.h3, this.sendDirection, this.controlSendEpoch);
|
|
584
|
-
const header = {
|
|
585
|
-
epoch: this.controlSendEpoch,
|
|
586
|
-
sequence: this.controlSendSequence,
|
|
587
|
-
ciphertextLength: inner.length + 16,
|
|
588
|
-
};
|
|
589
|
-
const ciphertext = sealRecord(this.config.suite, material, this.h3, 0n, this.sendDirection, header, inner);
|
|
590
|
-
this.controlSendSequence += 1n;
|
|
591
|
-
await writeAll(this.control, concat(encodeRecordHeader(header), ciphertext), signal);
|
|
592
|
-
this.markAuthenticatedActivity();
|
|
589
|
+
await this.writeControl(type, payload, signal);
|
|
593
590
|
});
|
|
594
591
|
this.controlWriteTail = task.catch(() => undefined);
|
|
595
592
|
await task;
|
|
596
593
|
}
|
|
594
|
+
async sendControlResponse(type, payload) {
|
|
595
|
+
if (this.lifecycle !== "open" || this.controlTerminalSealed)
|
|
596
|
+
return false;
|
|
597
|
+
const task = this.controlWriteTail.then(async () => {
|
|
598
|
+
if (this.lifecycle !== "open" || this.controlTerminalSealed)
|
|
599
|
+
return false;
|
|
600
|
+
await this.writeControl(type, payload);
|
|
601
|
+
return true;
|
|
602
|
+
});
|
|
603
|
+
this.controlWriteTail = task.then(() => undefined, () => undefined);
|
|
604
|
+
return await task;
|
|
605
|
+
}
|
|
606
|
+
async sendControlCleanup(type, payload) {
|
|
607
|
+
if (this.controlTerminalSealed || this.lifecycle === "closed")
|
|
608
|
+
return false;
|
|
609
|
+
const task = this.controlWriteTail.then(async () => {
|
|
610
|
+
if (this.lifecycle === "closed")
|
|
611
|
+
return false;
|
|
612
|
+
await this.writeControl(type, payload);
|
|
613
|
+
return true;
|
|
614
|
+
});
|
|
615
|
+
this.controlWriteTail = task.then(() => undefined, () => undefined);
|
|
616
|
+
return await task;
|
|
617
|
+
}
|
|
618
|
+
sendTerminalControl(reason) {
|
|
619
|
+
if (this.controlTerminalSealed)
|
|
620
|
+
return this.controlWriteTail;
|
|
621
|
+
this.controlTerminalSealed = true;
|
|
622
|
+
const task = this.controlWriteTail.then(async () => {
|
|
623
|
+
if (this.lifecycle === "closed")
|
|
624
|
+
throw this.terminalError ?? new SessionV2Error("closed", "Flowersec v2 session closed");
|
|
625
|
+
if (!this.sentGoAway) {
|
|
626
|
+
this.sentGoAway = true;
|
|
627
|
+
this.sentGoAwayLastAccepted = this.peerLedger.frontier;
|
|
628
|
+
this.sentGoAwayReason = reason;
|
|
629
|
+
}
|
|
630
|
+
await this.writeControl(InnerTypeV2.GoAway, idReason(this.sentGoAwayLastAccepted, this.sentGoAwayReason));
|
|
631
|
+
if (!this.sentSessionClose) {
|
|
632
|
+
this.sentSessionClose = true;
|
|
633
|
+
await this.writeControl(InnerTypeV2.SessionClose, Uint8Array.of(0, reason));
|
|
634
|
+
this.sessionCloseCommitted = true;
|
|
635
|
+
}
|
|
636
|
+
await this.control.closeWrite();
|
|
637
|
+
});
|
|
638
|
+
this.controlWriteTail = task.catch(() => undefined);
|
|
639
|
+
return task;
|
|
640
|
+
}
|
|
641
|
+
async writeControl(type, payload, signal) {
|
|
642
|
+
const inner = encodeInnerRecordV2(type, payload);
|
|
643
|
+
const roots = this.rootForSend(this.controlSendEpoch);
|
|
644
|
+
const material = deriveControlMaterial(roots.controlRoot, this.h3, this.sendDirection, this.controlSendEpoch);
|
|
645
|
+
const header = {
|
|
646
|
+
epoch: this.controlSendEpoch,
|
|
647
|
+
sequence: this.controlSendSequence,
|
|
648
|
+
ciphertextLength: inner.length + 16,
|
|
649
|
+
};
|
|
650
|
+
const ciphertext = sealRecord(this.config.suite, material, this.h3, 0n, this.sendDirection, header, inner);
|
|
651
|
+
this.controlSendSequence += 1n;
|
|
652
|
+
await writeAll(this.control, concat(encodeRecordHeader(header), ciphertext), signal);
|
|
653
|
+
this.markAuthenticatedActivity();
|
|
654
|
+
}
|
|
597
655
|
async readControl() {
|
|
598
656
|
const header = decodeRecordHeader(await this.controlReader.readExactly(24));
|
|
599
657
|
const cutover = header.epoch === this.controlReceiveEpoch + 1 &&
|
|
@@ -622,7 +680,7 @@ export class SessionV2 {
|
|
|
622
680
|
const record = await this.readControl();
|
|
623
681
|
switch (record.type) {
|
|
624
682
|
case InnerTypeV2.Ping:
|
|
625
|
-
await this.
|
|
683
|
+
await this.sendControlResponse(InnerTypeV2.Pong, record.payload);
|
|
626
684
|
break;
|
|
627
685
|
case InnerTypeV2.Pong:
|
|
628
686
|
this.pings.get(readU64(record.payload))?.resolve();
|
|
@@ -685,20 +743,10 @@ export class SessionV2 {
|
|
|
685
743
|
if (this.lifecycle === "closed")
|
|
686
744
|
return;
|
|
687
745
|
const closed = new SessionV2Error("closed", "Flowersec v2 session closed");
|
|
746
|
+
const terminal = this.sendTerminalControl(1);
|
|
688
747
|
const work = (async () => {
|
|
689
748
|
try {
|
|
690
|
-
await
|
|
691
|
-
}
|
|
692
|
-
catch {
|
|
693
|
-
// A prior GOAWAY boundary must not suppress the close response.
|
|
694
|
-
}
|
|
695
|
-
try {
|
|
696
|
-
if (!this.sentSessionClose) {
|
|
697
|
-
this.sentSessionClose = true;
|
|
698
|
-
await this.sendControl(InnerTypeV2.SessionClose, Uint8Array.of(0, 1));
|
|
699
|
-
this.sessionCloseCommitted = true;
|
|
700
|
-
await this.control.closeWrite();
|
|
701
|
-
}
|
|
749
|
+
await terminal;
|
|
702
750
|
}
|
|
703
751
|
catch {
|
|
704
752
|
// Closing is best-effort once the bounded shutdown has started.
|
|
@@ -853,7 +901,12 @@ export class SessionV2 {
|
|
|
853
901
|
}
|
|
854
902
|
const streams = [...this.streams.values()];
|
|
855
903
|
await Promise.all(streams.map(async (stream) => await stream.waitReceiveRekey(transition, nextEpoch, signal)));
|
|
856
|
-
await raceAbort(this.
|
|
904
|
+
const acknowledged = await raceAbort(this.sendControlResponse(InnerTypeV2.SessionKeyUpdateACK, payload), signal);
|
|
905
|
+
if (!acknowledged) {
|
|
906
|
+
this.pendingReceiveEpoch = undefined;
|
|
907
|
+
this.cleanupEpochRoots();
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
857
910
|
throwIfAborted(signal);
|
|
858
911
|
this.receiveEpoch = nextEpoch;
|
|
859
912
|
this.receiveTransition = transition;
|
|
@@ -897,9 +950,10 @@ export class SessionV2 {
|
|
|
897
950
|
}
|
|
898
951
|
}
|
|
899
952
|
async commitOutboundReset(id) {
|
|
900
|
-
await this.
|
|
901
|
-
|
|
902
|
-
|
|
953
|
+
if (await this.sendControlCleanup(InnerTypeV2.StreamReset, idReason(id, 6))) {
|
|
954
|
+
this.outboundLedger.localResetCommitted(id);
|
|
955
|
+
this.notifyOutboundFrontierChanged();
|
|
956
|
+
}
|
|
903
957
|
}
|
|
904
958
|
commitLocalReset(id) {
|
|
905
959
|
if (this.isLocalLogicalID(id)) {
|
|
@@ -1026,6 +1080,7 @@ export class SessionV2 {
|
|
|
1026
1080
|
fail(error, abortCarrier = true) {
|
|
1027
1081
|
if (this.lifecycle === "closed")
|
|
1028
1082
|
return;
|
|
1083
|
+
this.controlTerminalSealed = true;
|
|
1029
1084
|
this.beginClosing();
|
|
1030
1085
|
const normalPeerCarrierClose = this.sessionCloseCommitted && error instanceof CarrierError && error.code === "closed";
|
|
1031
1086
|
if (normalPeerCarrierClose)
|
|
@@ -1224,6 +1279,10 @@ class EncryptedStreamV2 {
|
|
|
1224
1279
|
if (this.terminalError !== undefined)
|
|
1225
1280
|
return false;
|
|
1226
1281
|
this.terminalError = error;
|
|
1282
|
+
const pendingSendRekey = this.pendingSendRekey;
|
|
1283
|
+
this.pendingSendRekey = undefined;
|
|
1284
|
+
pendingSendRekey?.armed.reject(error);
|
|
1285
|
+
pendingSendRekey?.done.reject(error);
|
|
1227
1286
|
this.opened.reject(error);
|
|
1228
1287
|
this.data.fail(error);
|
|
1229
1288
|
return true;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { FLOWERSEC_V3_PROFILE } from "./transportConstants.js";
|
|
2
|
+
export type ArtifactCarrierV3 = "websocket" | "raw_quic" | "webtransport";
|
|
3
|
+
export type ArtifactPathKindV3 = "direct" | "tunnel";
|
|
4
|
+
export type CertificatePinV3 = Readonly<{
|
|
5
|
+
algorithm: "sha-256";
|
|
6
|
+
not_after_unix_s: number;
|
|
7
|
+
value_b64u: string;
|
|
8
|
+
}>;
|
|
9
|
+
export type TransportSecurityPolicyV3 = Readonly<{
|
|
10
|
+
mode: "ca";
|
|
11
|
+
}> | Readonly<{
|
|
12
|
+
mode: "pin";
|
|
13
|
+
pins: readonly CertificatePinV3[];
|
|
14
|
+
}>;
|
|
15
|
+
export type ArtifactCandidateV3 = Readonly<{
|
|
16
|
+
id: string;
|
|
17
|
+
carrier: ArtifactCarrierV3;
|
|
18
|
+
url: string;
|
|
19
|
+
wire_profile: string;
|
|
20
|
+
tls: TransportSecurityPolicyV3;
|
|
21
|
+
normalized_url?: string;
|
|
22
|
+
}>;
|
|
23
|
+
export type CanonicalArtifactCandidateV3 = Readonly<{
|
|
24
|
+
carrier: ArtifactCarrierV3;
|
|
25
|
+
id: string;
|
|
26
|
+
normalized_url: string;
|
|
27
|
+
tls: TransportSecurityPolicyV3;
|
|
28
|
+
wire_profile: string;
|
|
29
|
+
}>;
|
|
30
|
+
export type SessionContractV3 = Readonly<{
|
|
31
|
+
channel_id: string;
|
|
32
|
+
init_expire_at_unix_s: number;
|
|
33
|
+
idle_timeout_seconds: number;
|
|
34
|
+
establish_timeout_seconds: number;
|
|
35
|
+
rekey_prepare_timeout_seconds: number;
|
|
36
|
+
rekey_completion_timeout_seconds: number;
|
|
37
|
+
max_inbound_streams: number;
|
|
38
|
+
e2ee_psk_b64u: string;
|
|
39
|
+
allowed_suites: readonly number[];
|
|
40
|
+
default_suite: number;
|
|
41
|
+
selected_features: number;
|
|
42
|
+
contract_hash_b64u: string;
|
|
43
|
+
}>;
|
|
44
|
+
export type DirectArtifactPathV3 = Readonly<{
|
|
45
|
+
kind: "direct";
|
|
46
|
+
rendezvous_group_id: string;
|
|
47
|
+
listener_audience: string;
|
|
48
|
+
routing_token: string;
|
|
49
|
+
candidates: readonly ArtifactCandidateV3[];
|
|
50
|
+
}>;
|
|
51
|
+
export type TunnelArtifactPathV3 = Readonly<{
|
|
52
|
+
kind: "tunnel";
|
|
53
|
+
rendezvous_group_id: string;
|
|
54
|
+
listener_audience: string;
|
|
55
|
+
role: 1 | 2;
|
|
56
|
+
local_endpoint_instance_id: string;
|
|
57
|
+
expected_peer_endpoint_instance_id: string;
|
|
58
|
+
token: string;
|
|
59
|
+
candidates: readonly ArtifactCandidateV3[];
|
|
60
|
+
}>;
|
|
61
|
+
export type ScopeMetadataV3 = Readonly<{
|
|
62
|
+
scope: string;
|
|
63
|
+
scope_version: number;
|
|
64
|
+
critical: boolean;
|
|
65
|
+
payload: Readonly<Record<string, unknown>>;
|
|
66
|
+
}>;
|
|
67
|
+
export type CorrelationTagV3 = Readonly<{
|
|
68
|
+
key: string;
|
|
69
|
+
value: string;
|
|
70
|
+
}>;
|
|
71
|
+
export type CorrelationContextV3 = Readonly<{
|
|
72
|
+
v: 3;
|
|
73
|
+
tags: readonly CorrelationTagV3[];
|
|
74
|
+
}>;
|
|
75
|
+
export type ArtifactV3 = Readonly<{
|
|
76
|
+
v: 3;
|
|
77
|
+
profile: typeof FLOWERSEC_V3_PROFILE;
|
|
78
|
+
session: SessionContractV3;
|
|
79
|
+
path: DirectArtifactPathV3 | TunnelArtifactPathV3;
|
|
80
|
+
scoped: readonly ScopeMetadataV3[];
|
|
81
|
+
correlation: CorrelationContextV3;
|
|
82
|
+
}>;
|
|
83
|
+
type CommonFSB3RequestV3 = Readonly<{
|
|
84
|
+
profile: typeof FLOWERSEC_V3_PROFILE;
|
|
85
|
+
channel_id: string;
|
|
86
|
+
session_contract_hash_b64u: string;
|
|
87
|
+
rendezvous_group_id: string;
|
|
88
|
+
candidates: readonly CanonicalArtifactCandidateV3[];
|
|
89
|
+
candidate_set_hash_b64u: string;
|
|
90
|
+
chosen_candidate_id: string;
|
|
91
|
+
listener_audience: string;
|
|
92
|
+
}>;
|
|
93
|
+
export type DirectFSB3RequestV3 = CommonFSB3RequestV3 & Readonly<{
|
|
94
|
+
pathKind: "direct";
|
|
95
|
+
routing_token: string;
|
|
96
|
+
}>;
|
|
97
|
+
export type TunnelFSB3RequestV3 = CommonFSB3RequestV3 & Readonly<{
|
|
98
|
+
pathKind: "tunnel";
|
|
99
|
+
role: 1 | 2;
|
|
100
|
+
endpoint_instance_id: string;
|
|
101
|
+
attach_token: string;
|
|
102
|
+
}>;
|
|
103
|
+
export type FSB3RequestV3 = DirectFSB3RequestV3 | TunnelFSB3RequestV3;
|
|
104
|
+
export type DecodedFSB3RequestV3 = Readonly<{
|
|
105
|
+
request: FSB3RequestV3;
|
|
106
|
+
raw: Uint8Array;
|
|
107
|
+
localAdmissionBinding: Uint8Array;
|
|
108
|
+
}>;
|
|
109
|
+
export declare enum AdmissionStatusV3 {
|
|
110
|
+
Success = 0,
|
|
111
|
+
Reject = 1,
|
|
112
|
+
Retryable = 2
|
|
113
|
+
}
|
|
114
|
+
export type AdmissionResponseV3 = Readonly<{
|
|
115
|
+
status: AdmissionStatusV3;
|
|
116
|
+
reason: string;
|
|
117
|
+
}>;
|
|
118
|
+
export type ArtifactV3ErrorCode = "artifact_too_large" | "fsb3_payload_too_large" | "invalid_artifact" | "invalid_candidate" | "invalid_fsa3" | "invalid_fsb3" | "noncanonical_fsb3";
|
|
119
|
+
export declare class ArtifactV3Error extends Error {
|
|
120
|
+
readonly code: ArtifactV3ErrorCode;
|
|
121
|
+
constructor(code: ArtifactV3ErrorCode, message: string);
|
|
122
|
+
}
|
|
123
|
+
export type LabeledHashV3 = Readonly<{
|
|
124
|
+
canonicalJSON: string;
|
|
125
|
+
hash: Uint8Array;
|
|
126
|
+
hashBase64URL: string;
|
|
127
|
+
}>;
|
|
128
|
+
export type CanonicalCandidateSetV3 = LabeledHashV3 & Readonly<{
|
|
129
|
+
candidates: readonly CanonicalArtifactCandidateV3[];
|
|
130
|
+
}>;
|
|
131
|
+
export declare function computeSessionContractHashV3(session: SessionContractV3): LabeledHashV3;
|
|
132
|
+
export declare function canonicalizeCandidatesV3(kind: ArtifactPathKindV3, candidates: readonly ArtifactCandidateV3[]): CanonicalCandidateSetV3;
|
|
133
|
+
export declare function decodeArtifactV3JSON(raw: string | Uint8Array): ArtifactV3;
|
|
134
|
+
export declare function encodeArtifactV3JSON(artifact: ArtifactV3): Uint8Array;
|
|
135
|
+
export declare function validateArtifactV3(artifact: ArtifactV3): CanonicalCandidateSetV3;
|
|
136
|
+
export declare function buildFSB3RequestV3(artifact: ArtifactV3, chosenCandidateID: string): FSB3RequestV3;
|
|
137
|
+
export declare function encodeFSB3RequestV3(request: FSB3RequestV3): Uint8Array;
|
|
138
|
+
export declare function decodeFSB3RequestV3(raw: Uint8Array): DecodedFSB3RequestV3;
|
|
139
|
+
export declare function admissionBindingV3(rawFSB3: Uint8Array): Uint8Array;
|
|
140
|
+
export declare function acceptorAdmissionsHashV3(framesByCandidateID: ReadonlyMap<string, Uint8Array>): Uint8Array;
|
|
141
|
+
export declare function encodeFSA3ResponseV3(response: AdmissionResponseV3, admissionReasons?: ReadonlySet<string>): Uint8Array;
|
|
142
|
+
export declare function decodeFSA3ResponseV3(raw: Uint8Array): AdmissionResponseV3;
|
|
143
|
+
export declare function tlsPolicyDigestV3(policy: TransportSecurityPolicyV3): LabeledHashV3;
|
|
144
|
+
export declare function activePinsV3(policy: TransportSecurityPolicyV3, attemptNowUnixSeconds: number): readonly CertificatePinV3[];
|
|
145
|
+
export {};
|