opcjs-base 0.1.29-alpha → 0.1.32-alpha
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/dist/index.cjs +213 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -32
- package/dist/index.d.ts +65 -32
- package/dist/index.js +213 -74
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2932,12 +2932,22 @@ var SecureChannelTypeEncoder = class extends TransformStream {
|
|
|
2932
2932
|
}
|
|
2933
2933
|
};
|
|
2934
2934
|
|
|
2935
|
+
// src/secureChannel/messages/msgType.ts
|
|
2936
|
+
var MsgTypeOpenFinal = "O".charCodeAt(0) | "P".charCodeAt(0) << 8 | "N".charCodeAt(0) << 16 | "F".charCodeAt(0) << 24;
|
|
2937
|
+
var MsgTypeOpenChunk = "O".charCodeAt(0) | "P".charCodeAt(0) << 8 | "N".charCodeAt(0) << 16 | "C".charCodeAt(0) << 24;
|
|
2938
|
+
var MsgTypeAbort = "M".charCodeAt(0) | "S".charCodeAt(0) << 8 | "G".charCodeAt(0) << 16 | "A".charCodeAt(0) << 24;
|
|
2939
|
+
var MsgTypeChunk = "M".charCodeAt(0) | "S".charCodeAt(0) << 8 | "G".charCodeAt(0) << 16 | "C".charCodeAt(0) << 24;
|
|
2940
|
+
var MsgTypeFinal = "M".charCodeAt(0) | "S".charCodeAt(0) << 8 | "G".charCodeAt(0) << 16 | "F".charCodeAt(0) << 24;
|
|
2941
|
+
var MsgTypeCloseFinal = "C".charCodeAt(0) | "L".charCodeAt(0) << 8 | "O".charCodeAt(0) << 16 | "F".charCodeAt(0) << 24;
|
|
2942
|
+
|
|
2935
2943
|
// src/secureChannel/secureChannelTypeDecoder.ts
|
|
2936
2944
|
var SecureChannelTypeDecoder = class extends TransformStream {
|
|
2937
2945
|
constructor(decoder) {
|
|
2938
2946
|
super({
|
|
2939
2947
|
transform(msg, controller) {
|
|
2940
|
-
|
|
2948
|
+
if (msg.header.msgType !== MsgTypeAbort) {
|
|
2949
|
+
msg.body = decoder.decode(msg.body, "binary");
|
|
2950
|
+
}
|
|
2941
2951
|
controller.enqueue(msg);
|
|
2942
2952
|
}
|
|
2943
2953
|
});
|
|
@@ -17399,13 +17409,6 @@ var MsgSymmetric = class _MsgSymmetric extends MsgBase2 {
|
|
|
17399
17409
|
}
|
|
17400
17410
|
};
|
|
17401
17411
|
|
|
17402
|
-
// src/secureChannel/messages/msgType.ts
|
|
17403
|
-
var MsgTypeOpenFinal = "O".charCodeAt(0) | "P".charCodeAt(0) << 8 | "N".charCodeAt(0) << 16 | "F".charCodeAt(0) << 24;
|
|
17404
|
-
var MsgTypeAbort = "M".charCodeAt(0) | "S".charCodeAt(0) << 8 | "G".charCodeAt(0) << 16 | "A".charCodeAt(0) << 24;
|
|
17405
|
-
var MsgTypeChunk = "M".charCodeAt(0) | "S".charCodeAt(0) << 8 | "G".charCodeAt(0) << 16 | "C".charCodeAt(0) << 24;
|
|
17406
|
-
var MsgTypeFinal = "M".charCodeAt(0) | "S".charCodeAt(0) << 8 | "G".charCodeAt(0) << 16 | "F".charCodeAt(0) << 24;
|
|
17407
|
-
var MsgTypeCloseFinal = "C".charCodeAt(0) | "L".charCodeAt(0) << 8 | "O".charCodeAt(0) << 16 | "F".charCodeAt(0) << 24;
|
|
17408
|
-
|
|
17409
17412
|
// src/secureChannel/pendingRequests.ts
|
|
17410
17413
|
var PendingRequests = class {
|
|
17411
17414
|
map = /* @__PURE__ */ new Map();
|
|
@@ -17457,6 +17460,7 @@ var Certificate = class {
|
|
|
17457
17460
|
};
|
|
17458
17461
|
|
|
17459
17462
|
// src/secureChannel/secureChannelFacade.ts
|
|
17463
|
+
var TOKEN_RENEW_FRACTION = 0.75;
|
|
17460
17464
|
var SecureChannelFacade = class {
|
|
17461
17465
|
// ─── Constructor ────────────────────────────────────────────────────────────────
|
|
17462
17466
|
constructor(context, readerTransform, writerTransform) {
|
|
@@ -17471,11 +17475,17 @@ var SecureChannelFacade = class {
|
|
|
17471
17475
|
logger = getLogger("secureChannel.SecureChannelFacade");
|
|
17472
17476
|
writer;
|
|
17473
17477
|
reader;
|
|
17478
|
+
/** Timer handle for the scheduled token renewal; undefined when no renewal is pending. */
|
|
17479
|
+
renewalTimer;
|
|
17474
17480
|
/**
|
|
17475
|
-
*
|
|
17476
|
-
*
|
|
17481
|
+
* Builds and sends an OpenSecureChannel request.
|
|
17482
|
+
* Called for both the initial Issue and subsequent Renew requests.
|
|
17483
|
+
*
|
|
17484
|
+
* On success the context `channelId` and `tokenId` are updated and a new
|
|
17485
|
+
* renewal is scheduled at 75 % of the server-revised token lifetime, per
|
|
17486
|
+
* OPC UA Part 4 Section 5.4.1 / Part 6 Section 6.7.3.
|
|
17477
17487
|
*/
|
|
17478
|
-
async
|
|
17488
|
+
async sendOpenSecureChannel(requestType) {
|
|
17479
17489
|
const requestHeader = new RequestHeader();
|
|
17480
17490
|
requestHeader.authenticationToken = NodeId.newTwoByte(0);
|
|
17481
17491
|
requestHeader.timestamp = /* @__PURE__ */ new Date();
|
|
@@ -17487,31 +17497,72 @@ var SecureChannelFacade = class {
|
|
|
17487
17497
|
const request = new OpenSecureChannelRequest();
|
|
17488
17498
|
request.requestHeader = requestHeader;
|
|
17489
17499
|
request.clientProtocolVersion = 0;
|
|
17490
|
-
request.requestType =
|
|
17500
|
+
request.requestType = requestType;
|
|
17491
17501
|
request.securityMode = this.context.securityPolicy.getSecurityMode();
|
|
17492
17502
|
request.clientNonce = null;
|
|
17493
17503
|
request.requestedLifetime = 36e5;
|
|
17494
17504
|
const { sequenceNumber, requestId } = this.context.nextIds();
|
|
17495
|
-
this.context.securityAlgorithm = this.context.securityPolicy.getAlgorithmAsymmetric(
|
|
17505
|
+
this.context.securityAlgorithm = this.context.securityPolicy.getAlgorithmAsymmetric(
|
|
17506
|
+
new Uint8Array(),
|
|
17507
|
+
new Uint8Array()
|
|
17508
|
+
);
|
|
17496
17509
|
const msg = new MsgAsymmetric(
|
|
17497
|
-
new MsgHeader2(MsgTypeOpenFinal, 0,
|
|
17510
|
+
new MsgHeader2(MsgTypeOpenFinal, 0, this.context.channelId),
|
|
17498
17511
|
new MsgSecurityHeaderAsymmetric(
|
|
17499
17512
|
"http://opcfoundation.org/UA/SecurityPolicy#None"
|
|
17500
17513
|
),
|
|
17501
17514
|
new MsgSequenceHeader(sequenceNumber, requestId),
|
|
17502
17515
|
request
|
|
17503
17516
|
);
|
|
17504
|
-
this.
|
|
17505
|
-
|
|
17506
|
-
|
|
17507
|
-
|
|
17508
|
-
|
|
17509
|
-
|
|
17510
|
-
|
|
17511
|
-
|
|
17512
|
-
|
|
17513
|
-
|
|
17514
|
-
|
|
17517
|
+
const response = await this.pushMessage(msg);
|
|
17518
|
+
this.context.channelId = response.securityToken?.channelId;
|
|
17519
|
+
this.context.tokenId = response.securityToken?.tokenId;
|
|
17520
|
+
this.context.securityAlgorithm = this.context.securityPolicy.getAlgorithmSymmetric(
|
|
17521
|
+
new Certificate(),
|
|
17522
|
+
new Certificate()
|
|
17523
|
+
);
|
|
17524
|
+
const revisedLifetimeMs = response.securityToken?.revisedLifetime;
|
|
17525
|
+
this.scheduleRenewal(revisedLifetimeMs);
|
|
17526
|
+
}
|
|
17527
|
+
/**
|
|
17528
|
+
* Schedules a proactive token renewal at {@link TOKEN_RENEW_FRACTION} of
|
|
17529
|
+
* `lifetimeMs`. Any previously scheduled renewal is cancelled first.
|
|
17530
|
+
*/
|
|
17531
|
+
scheduleRenewal(lifetimeMs) {
|
|
17532
|
+
if (this.renewalTimer !== void 0) {
|
|
17533
|
+
clearTimeout(this.renewalTimer);
|
|
17534
|
+
}
|
|
17535
|
+
const delayMs = Math.floor(lifetimeMs * TOKEN_RENEW_FRACTION);
|
|
17536
|
+
this.logger.debug(
|
|
17537
|
+
`Scheduling SecurityToken renewal in ${delayMs} ms (75 % of ${lifetimeMs} ms lifetime).`
|
|
17538
|
+
);
|
|
17539
|
+
this.renewalTimer = setTimeout(() => {
|
|
17540
|
+
this.renewalTimer = void 0;
|
|
17541
|
+
this.logger.info("Renewing SecurityToken...");
|
|
17542
|
+
this.sendOpenSecureChannel(1 /* Renew */).catch((err) => {
|
|
17543
|
+
this.logger.error("SecurityToken renewal failed:", err);
|
|
17544
|
+
});
|
|
17545
|
+
}, delayMs);
|
|
17546
|
+
}
|
|
17547
|
+
/**
|
|
17548
|
+
* Sends the initial OpenSecureChannel request and resolves once the server
|
|
17549
|
+
* replies. Updates `context.channelId` and `context.tokenId` on success
|
|
17550
|
+
* and schedules automatic renewal at 75 % of the token lifetime.
|
|
17551
|
+
*/
|
|
17552
|
+
openSecureChannel() {
|
|
17553
|
+
this.logger.debug("Sending OpenSecureChannelRequest (Issue)...");
|
|
17554
|
+
return this.sendOpenSecureChannel(0 /* Issue */);
|
|
17555
|
+
}
|
|
17556
|
+
/**
|
|
17557
|
+
* Cancels any pending token renewal timer and releases the stream writer.
|
|
17558
|
+
* Call this when the secure channel is no longer needed.
|
|
17559
|
+
*/
|
|
17560
|
+
close() {
|
|
17561
|
+
if (this.renewalTimer !== void 0) {
|
|
17562
|
+
clearTimeout(this.renewalTimer);
|
|
17563
|
+
this.renewalTimer = void 0;
|
|
17564
|
+
}
|
|
17565
|
+
this.writer.releaseLock();
|
|
17515
17566
|
}
|
|
17516
17567
|
// ─── ISecureChannel implementation ─────────────────────────────────────────────
|
|
17517
17568
|
getSecurityPolicy() {
|
|
@@ -17548,14 +17599,20 @@ var SecureChannelFacade = class {
|
|
|
17548
17599
|
if (!value) {
|
|
17549
17600
|
this.logger.error("Received empty frame");
|
|
17550
17601
|
}
|
|
17551
|
-
const
|
|
17552
|
-
if (
|
|
17553
|
-
|
|
17554
|
-
|
|
17555
|
-
|
|
17556
|
-
);
|
|
17602
|
+
const requestId = value.sequenceHeader.requestId;
|
|
17603
|
+
if (value.header.msgType === MsgTypeAbort) {
|
|
17604
|
+
const reader = new BinaryReader(value.body);
|
|
17605
|
+
const statusCode = reader.readUInt32();
|
|
17606
|
+
const reason = reader.readString();
|
|
17607
|
+
this.context.chunkBuffers.delete(requestId);
|
|
17608
|
+
this.pending.fail(requestId, new Error(`Abort 0x${statusCode.toString(16).toUpperCase()}: ${reason}`));
|
|
17557
17609
|
} else {
|
|
17558
|
-
|
|
17610
|
+
const response = value.body;
|
|
17611
|
+
if (response instanceof ServiceFault) {
|
|
17612
|
+
this.pending.fail(requestId, new Error(`ServiceFault: ${JSON.stringify(response)}`));
|
|
17613
|
+
} else {
|
|
17614
|
+
this.pending.settle(requestId, response);
|
|
17615
|
+
}
|
|
17559
17616
|
}
|
|
17560
17617
|
}
|
|
17561
17618
|
} catch (e) {
|
|
@@ -17686,33 +17743,67 @@ var SecurityPolicyNone = class {
|
|
|
17686
17743
|
};
|
|
17687
17744
|
|
|
17688
17745
|
// src/secureChannel/secureChannelContext.ts
|
|
17689
|
-
var
|
|
17746
|
+
var SEQ_WRAP_THRESHOLD = 4294967295 - 1024;
|
|
17747
|
+
var SEQ_WRAP_START = 1;
|
|
17690
17748
|
var SecureChannelContext = class {
|
|
17691
17749
|
constructor(endpointUrl) {
|
|
17692
17750
|
this.endpointUrl = endpointUrl;
|
|
17693
17751
|
}
|
|
17694
|
-
|
|
17695
|
-
|
|
17752
|
+
/**
|
|
17753
|
+
* Next outgoing sequence number. Starts at 1 for non-ECC legacy profiles
|
|
17754
|
+
* per OPC UA Part 6, Section 6.7.2.4. The value 0 is used only as the
|
|
17755
|
+
* LastSequenceNumber sentinel in the OPN request ("no prior sequence").
|
|
17756
|
+
*/
|
|
17757
|
+
sequenceNumber = 1;
|
|
17758
|
+
/** Next outgoing request ID. The value 0 is reserved and must be skipped. */
|
|
17759
|
+
requestId = 1;
|
|
17696
17760
|
channelId = 0;
|
|
17697
17761
|
tokenId = 0;
|
|
17698
|
-
maxSendBufferSize =
|
|
17699
|
-
maxRecvBufferSize =
|
|
17700
|
-
|
|
17762
|
+
maxSendBufferSize = 2147483647;
|
|
17763
|
+
maxRecvBufferSize = 2147483647;
|
|
17764
|
+
/** Pending chunk bodies keyed by requestId, for reassembling multi-chunk messages. */
|
|
17765
|
+
chunkBuffers = /* @__PURE__ */ new Map();
|
|
17701
17766
|
securityAlgorithm;
|
|
17767
|
+
/** Last remote sequence number seen; undefined before the first received message. */
|
|
17768
|
+
lastRemoteSequenceNumber = void 0;
|
|
17702
17769
|
securityPolicy = new SecurityPolicyNone();
|
|
17703
17770
|
/**
|
|
17704
|
-
*
|
|
17705
|
-
*
|
|
17771
|
+
* Returns the current outgoing sequence number then advances it with
|
|
17772
|
+
* UInt32 wrap-around per OPC UA Part 6, Section 6.7.2.4. Only advances
|
|
17773
|
+
* the sequence counter — use when creating additional chunks for the same
|
|
17774
|
+
* message (each chunk needs its own sequence number but the same requestId).
|
|
17775
|
+
*/
|
|
17776
|
+
nextSequenceNumber() {
|
|
17777
|
+
const seq = this.sequenceNumber;
|
|
17778
|
+
if (this.sequenceNumber >= SEQ_WRAP_THRESHOLD) {
|
|
17779
|
+
this.sequenceNumber = SEQ_WRAP_START;
|
|
17780
|
+
} else {
|
|
17781
|
+
this.sequenceNumber++;
|
|
17782
|
+
}
|
|
17783
|
+
return seq;
|
|
17784
|
+
}
|
|
17785
|
+
/**
|
|
17786
|
+
* Returns the next outgoing sequence number and request ID, then advances
|
|
17787
|
+
* both counters. Call once per outgoing message; use {@link nextSequenceNumber}
|
|
17788
|
+
* for additional chunks of the same message.
|
|
17789
|
+
*
|
|
17790
|
+
* Handles UInt32 wrap-around per OPC UA Part 6, Section 6.7.2.4:
|
|
17791
|
+
* sequence numbers reset to 1 after reaching 0xFFFFFFFF-1024; request IDs
|
|
17792
|
+
* skip the reserved value 0 on wrap.
|
|
17706
17793
|
*/
|
|
17707
17794
|
nextIds() {
|
|
17708
|
-
|
|
17709
|
-
|
|
17710
|
-
|
|
17711
|
-
|
|
17795
|
+
const result = { sequenceNumber: this.nextSequenceNumber(), requestId: this.requestId };
|
|
17796
|
+
this.requestId++;
|
|
17797
|
+
if (this.requestId === 0) {
|
|
17798
|
+
this.requestId = 1;
|
|
17799
|
+
}
|
|
17800
|
+
return result;
|
|
17712
17801
|
}
|
|
17713
17802
|
};
|
|
17714
17803
|
|
|
17715
17804
|
// src/secureChannel/secureChannelMessageDecoder.ts
|
|
17805
|
+
var SEQ_WRAP_THRESHOLD2 = 4294967295 - 1024;
|
|
17806
|
+
var SEQ_WRAP_MAX = 1024;
|
|
17716
17807
|
var SecureChannelMessageDecoder = class extends TransformStream {
|
|
17717
17808
|
constructor(context) {
|
|
17718
17809
|
super({
|
|
@@ -17721,12 +17812,34 @@ var SecureChannelMessageDecoder = class extends TransformStream {
|
|
|
17721
17812
|
this.context = context;
|
|
17722
17813
|
}
|
|
17723
17814
|
logger = getLogger("secureChannel.SecureChannelMessageDecoder");
|
|
17815
|
+
/**
|
|
17816
|
+
* Validates that `sequenceNumber` is strictly increasing from the last
|
|
17817
|
+
* seen remote sequence. Allows exactly one UInt32 wrap-around per token.
|
|
17818
|
+
* Returns false and logs an error if the number is a duplicate or out of order.
|
|
17819
|
+
*/
|
|
17820
|
+
validateSequenceNumber(sequenceNumber, controller) {
|
|
17821
|
+
const last = this.context.lastRemoteSequenceNumber;
|
|
17822
|
+
if (last === void 0) {
|
|
17823
|
+
this.context.lastRemoteSequenceNumber = sequenceNumber;
|
|
17824
|
+
return true;
|
|
17825
|
+
}
|
|
17826
|
+
const isIncrement = sequenceNumber === last + 1;
|
|
17827
|
+
const isWrap = last >= SEQ_WRAP_THRESHOLD2 && sequenceNumber < SEQ_WRAP_MAX;
|
|
17828
|
+
if (!isIncrement && !isWrap) {
|
|
17829
|
+
this.logger.error(`Invalid remote sequence number: expected ${last + 1}, got ${sequenceNumber}`);
|
|
17830
|
+
controller.error(new Error(`Invalid remote sequence number: expected ${last + 1}, got ${sequenceNumber}`));
|
|
17831
|
+
return false;
|
|
17832
|
+
}
|
|
17833
|
+
this.context.lastRemoteSequenceNumber = sequenceNumber;
|
|
17834
|
+
return true;
|
|
17835
|
+
}
|
|
17724
17836
|
transform(data, controller) {
|
|
17725
17837
|
const buffer = new BinaryReader(data);
|
|
17726
17838
|
const header = MsgHeader2.decode(buffer);
|
|
17727
17839
|
switch (header.msgType) {
|
|
17728
|
-
case MsgTypeOpenFinal:
|
|
17729
|
-
|
|
17840
|
+
case MsgTypeOpenFinal:
|
|
17841
|
+
case MsgTypeOpenChunk: {
|
|
17842
|
+
this.logger.debug("SecureChannel received OpenFinal/OpenChunk message");
|
|
17730
17843
|
const secHeader = MsgSecurityHeaderAsymmetric.decode(buffer);
|
|
17731
17844
|
const msgAsym = MsgAsymmetric.decode(
|
|
17732
17845
|
buffer,
|
|
@@ -17734,16 +17847,22 @@ var SecureChannelMessageDecoder = class extends TransformStream {
|
|
|
17734
17847
|
secHeader,
|
|
17735
17848
|
this.context.securityAlgorithm
|
|
17736
17849
|
);
|
|
17850
|
+
if (!this.validateSequenceNumber(msgAsym.sequenceHeader.sequenceNumber, controller)) return;
|
|
17737
17851
|
controller.enqueue(msgAsym);
|
|
17738
17852
|
break;
|
|
17739
17853
|
}
|
|
17740
|
-
case MsgTypeAbort:
|
|
17741
|
-
this.logger.warn("
|
|
17854
|
+
case MsgTypeAbort: {
|
|
17855
|
+
this.logger.warn("SecureChannel received Abort message");
|
|
17856
|
+
const secHeader = MsgSecurityHeaderSymmetric.decode(buffer);
|
|
17857
|
+
const msgSym = MsgSymmetric.decode(buffer, header, secHeader, this.context.securityAlgorithm);
|
|
17858
|
+
controller.enqueue(msgSym);
|
|
17742
17859
|
break;
|
|
17860
|
+
}
|
|
17743
17861
|
case MsgTypeChunk: {
|
|
17744
17862
|
this.logger.debug("SecureChannel received Chunk message.");
|
|
17745
17863
|
const secHeader = MsgSecurityHeaderSymmetric.decode(buffer);
|
|
17746
17864
|
const msgSym = MsgSymmetric.decode(buffer, header, secHeader, this.context.securityAlgorithm);
|
|
17865
|
+
if (!this.validateSequenceNumber(msgSym.sequenceHeader.sequenceNumber, controller)) return;
|
|
17747
17866
|
controller.enqueue(msgSym);
|
|
17748
17867
|
break;
|
|
17749
17868
|
}
|
|
@@ -17751,11 +17870,12 @@ var SecureChannelMessageDecoder = class extends TransformStream {
|
|
|
17751
17870
|
this.logger.debug("SecureChannel received Final message");
|
|
17752
17871
|
const secHeader = MsgSecurityHeaderSymmetric.decode(buffer);
|
|
17753
17872
|
const msgSym = MsgSymmetric.decode(buffer, header, secHeader, this.context.securityAlgorithm);
|
|
17873
|
+
if (!this.validateSequenceNumber(msgSym.sequenceHeader.sequenceNumber, controller)) return;
|
|
17754
17874
|
controller.enqueue(msgSym);
|
|
17755
17875
|
break;
|
|
17756
17876
|
}
|
|
17757
17877
|
case MsgTypeCloseFinal:
|
|
17758
|
-
this.logger.warn("Unimplemented
|
|
17878
|
+
this.logger.warn("Unimplemented CloseFinal message.");
|
|
17759
17879
|
break;
|
|
17760
17880
|
default:
|
|
17761
17881
|
this.logger.warn("SecureChannel received unknown message type:", header.msgType);
|
|
@@ -17765,7 +17885,7 @@ var SecureChannelMessageDecoder = class extends TransformStream {
|
|
|
17765
17885
|
};
|
|
17766
17886
|
|
|
17767
17887
|
// src/secureChannel/secureChannelMessageEncoder.ts
|
|
17768
|
-
var
|
|
17888
|
+
var SecureChannelMessageEncoder = class extends TransformStream {
|
|
17769
17889
|
constructor(context) {
|
|
17770
17890
|
super({
|
|
17771
17891
|
transform(msg, controller) {
|
|
@@ -17780,24 +17900,33 @@ var SecureChannelMesssageEncoder = class extends TransformStream {
|
|
|
17780
17900
|
// src/secureChannel/secureChannelChunkReader.ts
|
|
17781
17901
|
var SecureChannelChunkReader = class extends TransformStream {
|
|
17782
17902
|
logger = getLogger("secureChannel.SecureChannelChunkReader");
|
|
17783
|
-
prependChunk(chunk, body) {
|
|
17784
|
-
const result = new Uint8Array(chunk.byteLength + body.byteLength);
|
|
17785
|
-
result.set(chunk, 0);
|
|
17786
|
-
result.set(body, chunk.byteLength);
|
|
17787
|
-
return result;
|
|
17788
|
-
}
|
|
17789
17903
|
transform(msg, context, controller) {
|
|
17904
|
+
const requestId = msg.sequenceHeader.requestId;
|
|
17790
17905
|
switch (msg.header.msgType) {
|
|
17791
|
-
case MsgTypeChunk:
|
|
17906
|
+
case MsgTypeChunk:
|
|
17907
|
+
case MsgTypeOpenChunk: {
|
|
17792
17908
|
this.logger.debug("Received Chunk message");
|
|
17793
|
-
context.chunkBuffers.
|
|
17909
|
+
const chunks = context.chunkBuffers.get(requestId) ?? [];
|
|
17910
|
+
chunks.push(msg.body);
|
|
17911
|
+
context.chunkBuffers.set(requestId, chunks);
|
|
17794
17912
|
break;
|
|
17795
17913
|
}
|
|
17796
|
-
case MsgTypeFinal:
|
|
17914
|
+
case MsgTypeFinal:
|
|
17915
|
+
case MsgTypeOpenFinal: {
|
|
17797
17916
|
this.logger.debug("Received Final message");
|
|
17798
|
-
|
|
17799
|
-
|
|
17800
|
-
|
|
17917
|
+
const chunks = context.chunkBuffers.get(requestId);
|
|
17918
|
+
if (chunks && chunks.length > 0) {
|
|
17919
|
+
const finalBody = msg.body;
|
|
17920
|
+
const totalLength = chunks.reduce((sum, c) => sum + c.byteLength, 0) + finalBody.byteLength;
|
|
17921
|
+
const assembled = new Uint8Array(totalLength);
|
|
17922
|
+
let offset = 0;
|
|
17923
|
+
for (const chunk of chunks) {
|
|
17924
|
+
assembled.set(chunk, offset);
|
|
17925
|
+
offset += chunk.byteLength;
|
|
17926
|
+
}
|
|
17927
|
+
assembled.set(finalBody, offset);
|
|
17928
|
+
msg.body = assembled;
|
|
17929
|
+
context.chunkBuffers.delete(requestId);
|
|
17801
17930
|
}
|
|
17802
17931
|
controller.enqueue(msg);
|
|
17803
17932
|
break;
|
|
@@ -17831,29 +17960,39 @@ var SecureChannelChunkWriter = class extends TransformStream {
|
|
|
17831
17960
|
this.logger.debug("Received Final message");
|
|
17832
17961
|
const securityAlgorithm = this.context.securityAlgorithm;
|
|
17833
17962
|
const maxCipherTextSize = this.context.maxSendBufferSize - MsgHeader2.Size - MsgSecurityHeaderSymmetric.Size;
|
|
17834
|
-
const
|
|
17835
|
-
const maxPayloadSize = maxPlainTextSize - securityAlgorithm.GetSignatureLength() - 1 - MsgSecurityHeaderSymmetric.Size + (securityAlgorithm.IsAuthenticated() ? 1 : 0);
|
|
17963
|
+
const maxPayloadSize = securityAlgorithm.GetMaxPayload(maxCipherTextSize) - MsgSequenceHeader.Size;
|
|
17836
17964
|
const data = msgSymmetric.body;
|
|
17837
|
-
const
|
|
17838
|
-
if (
|
|
17839
|
-
this.logger.debug(`Message body exceeds max chunk size, splitting into ${
|
|
17840
|
-
for (let i = 0; i <
|
|
17965
|
+
const numChunks = Math.ceil(data.byteLength / maxPayloadSize);
|
|
17966
|
+
if (numChunks > 1) {
|
|
17967
|
+
this.logger.debug(`Message body exceeds max chunk size, splitting into ${numChunks} chunks.`);
|
|
17968
|
+
for (let i = 0; i < numChunks - 1; i++) {
|
|
17841
17969
|
const chunkMsg = new MsgSymmetric(
|
|
17842
|
-
|
|
17970
|
+
// messageSize=0 is a placeholder: MsgBase.Encrypt() overwrites it at byte offset 4
|
|
17971
|
+
// with the actual encrypted size before writing to the wire.
|
|
17972
|
+
new MsgHeader2(MsgTypeChunk, 0, msgSymmetric.header.secureChannelId),
|
|
17843
17973
|
msgSymmetric.securityHeader,
|
|
17844
|
-
|
|
17974
|
+
new MsgSequenceHeader(
|
|
17975
|
+
this.context.nextSequenceNumber(),
|
|
17976
|
+
msgSymmetric.sequenceHeader.requestId
|
|
17977
|
+
),
|
|
17845
17978
|
data.subarray(i * maxPayloadSize, (i + 1) * maxPayloadSize)
|
|
17846
17979
|
);
|
|
17847
|
-
this.logger.trace(
|
|
17980
|
+
this.logger.trace(
|
|
17981
|
+
`Enqueuing chunk ${i + 1}/${numChunks} with size ${chunkMsg.body.byteLength} bytes.`
|
|
17982
|
+
);
|
|
17848
17983
|
controller.enqueue(chunkMsg);
|
|
17849
17984
|
}
|
|
17850
|
-
msg.
|
|
17985
|
+
msg.sequenceHeader = new MsgSequenceHeader(
|
|
17986
|
+
this.context.nextSequenceNumber(),
|
|
17987
|
+
msgSymmetric.sequenceHeader.requestId
|
|
17988
|
+
);
|
|
17989
|
+
msg.body = data.subarray((numChunks - 1) * maxPayloadSize);
|
|
17851
17990
|
}
|
|
17852
17991
|
break;
|
|
17853
17992
|
}
|
|
17854
17993
|
}
|
|
17855
17994
|
}
|
|
17856
|
-
this.logger.trace(`Enqueuing
|
|
17995
|
+
this.logger.trace(`Enqueuing final message with body size ${msg.body.byteLength} bytes.`);
|
|
17857
17996
|
controller.enqueue(msg);
|
|
17858
17997
|
}
|
|
17859
17998
|
};
|
|
@@ -18196,7 +18335,7 @@ exports.SecureChannelChunkWriter = SecureChannelChunkWriter;
|
|
|
18196
18335
|
exports.SecureChannelContext = SecureChannelContext;
|
|
18197
18336
|
exports.SecureChannelFacade = SecureChannelFacade;
|
|
18198
18337
|
exports.SecureChannelMessageDecoder = SecureChannelMessageDecoder;
|
|
18199
|
-
exports.
|
|
18338
|
+
exports.SecureChannelMessageEncoder = SecureChannelMessageEncoder;
|
|
18200
18339
|
exports.SecureChannelTypeDecoder = SecureChannelTypeDecoder;
|
|
18201
18340
|
exports.SecureChannelTypeEncoder = SecureChannelTypeEncoder;
|
|
18202
18341
|
exports.SecurityGroupDataType = SecurityGroupDataType;
|