node-opcua-transport 2.67.1 → 2.69.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.
Files changed (41) hide show
  1. package/dist/source/client_tcp_transport.d.ts +8 -4
  2. package/dist/source/client_tcp_transport.js +15 -6
  3. package/dist/source/client_tcp_transport.js.map +1 -1
  4. package/dist/source/index.d.ts +5 -3
  5. package/dist/source/index.js +10 -4
  6. package/dist/source/index.js.map +1 -1
  7. package/dist/source/message_builder_base.d.ts +27 -7
  8. package/dist/source/message_builder_base.js +36 -13
  9. package/dist/source/message_builder_base.js.map +1 -1
  10. package/dist/source/server_tcp_transport.d.ts +1 -5
  11. package/dist/source/server_tcp_transport.js +42 -40
  12. package/dist/source/server_tcp_transport.js.map +1 -1
  13. package/dist/source/status_codes.d.ts +100 -0
  14. package/dist/source/status_codes.js +111 -0
  15. package/dist/source/status_codes.js.map +1 -0
  16. package/dist/source/tcp_transport.d.ts +21 -28
  17. package/dist/source/tcp_transport.js +86 -57
  18. package/dist/source/tcp_transport.js.map +1 -1
  19. package/dist/source/tools.d.ts +1 -0
  20. package/dist/source/utils.d.ts +3 -2
  21. package/dist/source/utils.js +4 -3
  22. package/dist/source/utils.js.map +1 -1
  23. package/dist/test-fixtures/index.js +5 -1
  24. package/dist/test-fixtures/index.js.map +1 -1
  25. package/dist/test_helpers/direct_transport.d.ts +4 -0
  26. package/dist/test_helpers/direct_transport.js.map +1 -1
  27. package/dist/test_helpers/fake_server.d.ts +2 -0
  28. package/dist/test_helpers/half_com_channel.d.ts +7 -0
  29. package/dist/test_helpers/half_com_channel.js.map +1 -1
  30. package/dist/test_helpers/index.js +5 -1
  31. package/dist/test_helpers/index.js.map +1 -1
  32. package/package.json +16 -16
  33. package/source/client_tcp_transport.ts +21 -9
  34. package/source/index.ts +5 -3
  35. package/source/message_builder_base.ts +68 -16
  36. package/source/server_tcp_transport.ts +51 -48
  37. package/source/status_codes.ts +109 -0
  38. package/source/tcp_transport.ts +118 -73
  39. package/source/utils.ts +3 -2
  40. package/test_helpers/direct_transport.ts +4 -1
  41. package/test_helpers/half_com_channel.ts +7 -0
@@ -0,0 +1,109 @@
1
+ import { StatusCodes } from "node-opcua-status-code";
2
+
3
+ export const StatusCodes2 = {
4
+ /**
5
+ * The Server cannot process the request because it is too busy.
6
+ *
7
+ * It is up to the Server to determine when it needs to return this Message.
8
+ *
9
+ * A Server can control the how frequently a Client reconnects by waiting to return this error.
10
+ */
11
+ BadTcpServerTooBusy: StatusCodes.BadTcpServerTooBusy,
12
+ /**
13
+ * The type of the Message specified in the header invalid.
14
+ * Each Message starts with a 4-byte sequence of ASCII values that identifies the Message type.
15
+ * The Server returns this error if the Message type is not accepted.
16
+ * Some of the Message types are defined by the SecureChannel layer.
17
+ */
18
+ BadTcpMessageTypeInvalid: StatusCodes.BadTcpMessageTypeInvalid,
19
+ /**
20
+ * The SecureChannelId and/or TokenId are not currently in use.
21
+ * This error is reported by the SecureChannel layer.
22
+ */
23
+ BadTcpSecureChannelUnknown: StatusCodes.BadTcpSecureChannelUnknown,
24
+ /**
25
+ * The size of the Message specified in the header is too large.
26
+ * The Server returns this error if the Message size exceeds its maximum buffer size
27
+ * or the receive buffer size negotiated during the Hello/Acknowledge exchange.
28
+ */
29
+ BadTcpMessageTooLarge: StatusCodes.BadTcpMessageTooLarge,
30
+
31
+ /**
32
+ * A timeout occurred while accessing a resource.
33
+ * It is up to the Server to determine when a timeout occurs.
34
+ */
35
+ BadTimeout: StatusCodes.BadTimeout,
36
+ /**
37
+ * There are not enough resources to process the request.
38
+ * The Server returns this error when it runs out of memory or encounters similar resource problems.
39
+ * A Server can control the how frequently a Client reconnects by waiting to return this error.
40
+ */
41
+ BadTcpNotEnoughResources: StatusCodes.BadTcpNotEnoughResources,
42
+ /**
43
+ * An internal error occurred.
44
+ * This should only be returned if an unexpected configuration or programming error occurs.
45
+ */
46
+ BadTcpInternalError: StatusCodes.BadTcpInternalError,
47
+ /**
48
+ * The Server does not recognize the EndpointUrl specified.
49
+ */
50
+ BadTcpEndpointUrlInvalid: StatusCodes.BadTcpEndpointUrlInvalid,
51
+ /**
52
+ * The Message was rejected because it could not be verified.
53
+ */
54
+ BadSecurityChecksFailed: StatusCodes.BadSecurityChecksFailed,
55
+ /**
56
+ * The request could not be sent because of a network interruption.
57
+ */
58
+ BadRequestInterrupted: StatusCodes.BadRequestInterrupted,
59
+ /**
60
+ * Timeout occurred while processing the request.
61
+ */
62
+ BadRequestTimeout: StatusCodes.BadRequestTimeout,
63
+ /**
64
+ * The secure channel has been closed.
65
+ */
66
+ BadSecureChannelClosed: StatusCodes.BadSecureChannelClosed,
67
+ /**
68
+ * The SecurityToken has expired or is not recognized. BadSecureChannelTokenUnknown
69
+ */
70
+ BadSecureChannelTokenUnknown: StatusCodes.BadSecureChannelTokenUnknown,
71
+ /**
72
+ * The sender Certificate is not trusted by the receiver.
73
+ */
74
+ BadCertificateUntrusted: StatusCodes.BadCertificateUntrusted,
75
+ /**
76
+ * The sender Certificate has expired or is not yet valid.
77
+ */
78
+ BadCertificateTimeInvalid: StatusCodes.BadCertificateTimeInvalid,
79
+ /**
80
+ * The issuer for the sender Certificate has expired or is not yet valid.
81
+ */
82
+ BadCertificateIssuerTimeInvalid: StatusCodes.BadCertificateIssuerTimeInvalid,
83
+ /**
84
+ * The sender’s Certificate may not be used for establishing a secure channel.
85
+ */
86
+ BadCertificateUseNotAllowed: StatusCodes.BadCertificateUseNotAllowed,
87
+ /**
88
+ * The issuer Certificate may not be used as a Certificate Authority.
89
+ */
90
+ BadCertificateIssuerUseNotAllowed: StatusCodes.BadCertificateIssuerUseNotAllowed,
91
+ /**
92
+ * Could not verify the revocation status of the sender’s Certificate.
93
+ */
94
+ BadCertificateRevocationUnknown: StatusCodes.BadCertificateRevocationUnknown,
95
+ /**
96
+ * Could not verify the revocation status of the issuer Certificate.
97
+ */
98
+ BadCertificateIssuerRevocationUnknown: StatusCodes.BadCertificateIssuerRevocationUnknown,
99
+ /**
100
+ * The sender Certificate has been revoked by the issuer.
101
+ */
102
+ BadCertificateRevoked: StatusCodes.BadCertificateRevoked
103
+ /**The issuer Certificate has been revoked by its issuer.
104
+ */
105
+ // todo BadIssuerCertificateRevoked: StatusCodes.BadIssuerCertificateRevoked,
106
+ /** The receiver Certificate thumbprint is not recognized by the receiver.
107
+ */
108
+ // todo .. BadCertificateUnknown: StatusCodes.BadCertificateUnknown
109
+ };
@@ -3,22 +3,24 @@
3
3
  */
4
4
  import { EventEmitter } from "events";
5
5
  import { Socket } from "net";
6
-
7
6
  import * as chalk from "chalk";
8
7
 
9
8
  import { assert } from "node-opcua-assert";
10
- import { createFastUninitializedBuffer } from "node-opcua-buffer-utils";
11
- import * as debug from "node-opcua-debug";
9
+ import { BinaryStream } from "node-opcua-binary-stream";
10
+ import { make_debugLog, checkDebugFlag, make_errorLog, hexDump } from "node-opcua-debug";
12
11
  import { ObjectRegistry } from "node-opcua-object-registry";
13
- import { PacketAssembler } from "node-opcua-packet-assembler";
14
- import { ErrorCallback, CallbackWithData } from "node-opcua-status-code";
12
+ import { PacketAssembler, PacketAssemblerErrorCode } from "node-opcua-packet-assembler";
13
+ import { ErrorCallback, CallbackWithData, StatusCode } from "node-opcua-status-code";
15
14
 
15
+ import { StatusCodes2 } from "./status_codes";
16
16
  import { readRawMessageHeader } from "./message_builder_base";
17
- import { writeTCPMessageHeader } from "./tools";
17
+ import { doTraceIncomingChunk } from "./utils";
18
+ import { TCPErrorMessage } from "./TCPErrorMessage";
19
+ import { packTcpMessage } from "./tools";
18
20
 
19
- const debugLog = debug.make_debugLog(__filename);
20
- const doDebug = debug.checkDebugFlag(__filename);
21
- const errorLog = debug.make_errorLog(__filename);
21
+ const debugLog = make_debugLog(__filename);
22
+ const doDebug = checkDebugFlag(__filename);
23
+ const errorLog = make_errorLog(__filename);
22
24
 
23
25
  export interface MockSocket {
24
26
  invalid?: boolean;
@@ -28,7 +30,7 @@ export interface MockSocket {
28
30
  }
29
31
  let fakeSocket: MockSocket = {
30
32
  invalid: true,
31
-
33
+
32
34
  destroy() {
33
35
  errorLog("MockSocket.destroy");
34
36
  },
@@ -52,11 +54,11 @@ export function getFakeTransport(): any {
52
54
  let counter = 0;
53
55
 
54
56
  export interface TCP_transport {
55
- on(eventName: "message", eventHandler: (message: Buffer) => void): this;
57
+ on(eventName: "chunk", eventHandler: (messageChunk: Buffer) => void): this;
56
58
  on(eventName: "socket_closed", eventHandler: (err: Error | null) => void): this;
57
59
  on(eventName: "close", eventHandler: (err: Error | null) => void): this;
58
60
 
59
- once(eventName: "message", eventHandler: (message: Buffer) => void): this;
61
+ once(eventName: "chunk", eventHandler: (messageChunk: Buffer) => void): this;
60
62
  once(eventName: "socket_closed", eventHandler: (err: Error | null) => void): this;
61
63
  once(eventName: "close", eventHandler: (err: Error | null) => void): this;
62
64
  }
@@ -69,6 +71,10 @@ export class TCP_transport extends EventEmitter {
69
71
  * @default 0
70
72
  */
71
73
  public protocolVersion: number;
74
+ public maxMessageSize: number;
75
+ public maxChunkCount: number;
76
+ public sendBufferSize: number;
77
+ public receiveBufferSize: number;
72
78
 
73
79
  public bytesWritten: number;
74
80
  public bytesRead: number;
@@ -89,7 +95,6 @@ export class TCP_transport extends EventEmitter {
89
95
  private _onSocketEndedHasBeenCalled: boolean;
90
96
  private _theCallback?: CallbackWithData;
91
97
  private _on_error_during_one_time_message_receiver: any;
92
- private _pendingBuffer?: any;
93
98
  private packetAssembler?: PacketAssembler;
94
99
  private _timeout: number;
95
100
 
@@ -103,10 +108,14 @@ export class TCP_transport extends EventEmitter {
103
108
  this._timeout = 30000; // 30 seconds timeout
104
109
  this._socket = null;
105
110
  this.headerSize = 8;
111
+
112
+ this.maxMessageSize = 0;
113
+ this.maxChunkCount = 0;
114
+ this.receiveBufferSize = 0;
115
+ this.sendBufferSize = 0;
106
116
  this.protocolVersion = 0;
107
117
 
108
118
  this._disconnecting = false;
109
- this._pendingBuffer = undefined;
110
119
 
111
120
  this.bytesWritten = 0;
112
121
  this.bytesRead = 0;
@@ -120,6 +129,26 @@ export class TCP_transport extends EventEmitter {
120
129
  TCP_transport.registry.register(this);
121
130
  }
122
131
 
132
+ public setLimits({
133
+ receiveBufferSize,
134
+ sendBufferSize,
135
+ maxMessageSize,
136
+ maxChunkCount
137
+ }: {
138
+ receiveBufferSize: number;
139
+ sendBufferSize: number;
140
+ maxMessageSize: number;
141
+ maxChunkCount: number;
142
+ }) {
143
+ this.receiveBufferSize = receiveBufferSize;
144
+ this.sendBufferSize = sendBufferSize;
145
+ this.maxMessageSize = maxMessageSize;
146
+ this.maxChunkCount = maxChunkCount;
147
+
148
+ // reinstall packetAssembler with correct limits
149
+ this._install_packetAssembler();
150
+ }
151
+
123
152
  public get timeout(): number {
124
153
  return this._timeout;
125
154
  }
@@ -138,52 +167,19 @@ export class TCP_transport extends EventEmitter {
138
167
  TCP_transport.registry.unregister(this);
139
168
  }
140
169
 
141
- /**
142
- * ```createChunk``` is used to construct a pre-allocated chunk to store up to ```length``` bytes of data.
143
- * The created chunk includes a prepended header for ```chunk_type``` of size ```self.headerSize```.
144
- *
145
- * @method createChunk
146
- * @param msgType
147
- * @param chunkType {String} chunk type. should be 'F' 'C' or 'A'
148
- * @param length
149
- * @return a buffer object with the required length representing the chunk.
150
- *
151
- * Note:
152
- * - only one chunk can be created at a time.
153
- * - a created chunk should be committed using the ```write``` method before an other one is created.
154
- */
155
- public createChunk(msgType: string, chunkType: string, length: number): Buffer {
156
- assert(msgType === "MSG");
157
- assert(this._pendingBuffer === undefined, "createChunk has already been called ( use write first)");
158
-
159
- const totalLength = length + this.headerSize;
160
- const buffer = createFastUninitializedBuffer(totalLength);
161
- writeTCPMessageHeader("MSG", chunkType, totalLength, buffer);
162
- this._pendingBuffer = buffer;
163
-
164
- return buffer;
165
- }
166
-
167
170
  /**
168
171
  * write the message_chunk on the socket.
169
172
  * @method write
170
173
  * @param messageChunk
171
- *
172
- * Notes:
173
- * - the message chunk must have been created by ```createChunk```.
174
- * - once a message chunk has been written, it is possible to call ```createChunk``` again.
175
- *
176
174
  */
177
- public write(messageChunk: Buffer): void {
178
- assert(
179
- this._pendingBuffer === undefined || this._pendingBuffer === messageChunk,
180
- " write should be used with buffer created by createChunk"
181
- );
175
+ public write(messageChunk: Buffer, callback?: (err?: Error) => void | undefined): void {
182
176
  const header = readRawMessageHeader(messageChunk);
183
177
  assert(header.length === messageChunk.length);
184
- assert(["F", "C", "A"].indexOf(header.messageHeader.isFinal) !== -1);
185
- this._write_chunk(messageChunk);
186
- this._pendingBuffer = undefined;
178
+ const c = header.messageHeader.isFinal;
179
+ assert(c === "F" || c === "C" || c === "A");
180
+ this._write_chunk(messageChunk, (err) => {
181
+ callback && callback(err);
182
+ });
187
183
  }
188
184
 
189
185
  public get isDisconnecting(): boolean {
@@ -228,11 +224,15 @@ export class TCP_transport extends EventEmitter {
228
224
  return this._socket !== null && !this._socket.destroyed && !this._disconnecting;
229
225
  }
230
226
 
231
- protected _write_chunk(messageChunk: Buffer): void {
227
+ protected _write_chunk(messageChunk: Buffer, callback?: (err?: Error) => void | undefined): void {
232
228
  if (this._socket !== null) {
233
229
  this.bytesWritten += messageChunk.length;
234
230
  this.chunkWrittenCount++;
235
- this._socket.write(messageChunk);
231
+ this._socket.write(messageChunk, callback);
232
+ } else {
233
+ if (callback) {
234
+ callback();
235
+ }
236
236
  }
237
237
  }
238
238
 
@@ -247,6 +247,35 @@ export class TCP_transport extends EventEmitter {
247
247
  this.emit("close", err || null);
248
248
  }
249
249
 
250
+ protected _install_packetAssembler() {
251
+ if (this.packetAssembler) {
252
+ this.packetAssembler.removeAllListeners();
253
+ this.packetAssembler = undefined;
254
+ }
255
+
256
+ // install packet assembler ...
257
+ this.packetAssembler = new PacketAssembler({
258
+ readChunkFunc: readRawMessageHeader,
259
+ minimumSizeInBytes: this.headerSize,
260
+ maxChunkSize: this.receiveBufferSize //Math.max(this.receiveBufferSize, this.sendBufferSize)
261
+ });
262
+
263
+ this.packetAssembler.on("chunk", (chunk: Buffer) => this._on_message_chunk_received(chunk));
264
+
265
+ this.packetAssembler.on("error", (err, code) => {
266
+ let statusCode = StatusCodes2.BadTcpMessageTooLarge;
267
+ switch (code) {
268
+ case PacketAssemblerErrorCode.ChunkSizeExceeded:
269
+ statusCode = StatusCodes2.BadTcpMessageTooLarge;
270
+ break;
271
+ default:
272
+ statusCode = StatusCodes2.BadTcpInternalError;
273
+ }
274
+
275
+ this.sendErrorMessage(statusCode, err.message);
276
+ this.prematureTerminate(new Error("Packet Assembler : " + err.message), statusCode);
277
+ });
278
+ }
250
279
  /**
251
280
  * @method _install_socket
252
281
  * @param socket {Socket}
@@ -259,18 +288,7 @@ export class TCP_transport extends EventEmitter {
259
288
  debugLog(" TCP_transport#_install_socket ", this.name);
260
289
  }
261
290
 
262
- // install packet assembler ...
263
- this.packetAssembler = new PacketAssembler({
264
- readMessageFunc: readRawMessageHeader,
265
-
266
- minimumSizeInBytes: this.headerSize
267
- });
268
-
269
- /* istanbul ignore next */
270
- if (!this.packetAssembler) {
271
- throw new Error("Internal Error");
272
- }
273
- this.packetAssembler.on("message", (messageChunk: Buffer) => this._on_message_received(messageChunk));
291
+ this._install_packetAssembler();
274
292
 
275
293
  this._socket
276
294
  .on("data", (data: Buffer) => this._on_socket_data(data))
@@ -284,14 +302,36 @@ export class TCP_transport extends EventEmitter {
284
302
  // let use a large timeout here to make sure that we not conflict with our internal timeout
285
303
  this._socket!.setTimeout(this.timeout + 2000, () => {
286
304
  debugLog(` _socket ${this.name} has timed out (timeout = ${this.timeout})`);
287
- this.prematureTerminate(new Error("socket timeout : timeout=" + this.timeout));
305
+ this.prematureTerminate(new Error("socket timeout : timeout=" + this.timeout), StatusCodes2.BadTimeout);
288
306
  });
289
307
  }
290
308
 
291
- public prematureTerminate(err: Error): void {
292
- debugLog("prematureTerminate", err ? err.message : "");
309
+ public sendErrorMessage(statusCode: StatusCode, extraErrorDescription: string | null): void {
310
+ // When the Client receives an Error Message it reports the error to the application and closes the TransportConnection gracefully.
311
+ // If a Client encounters a fatal error, it shall report the error to the application and send a CloseSecureChannel Message.
312
+
313
+ /* istanbul ignore next*/
314
+ if (doDebug) {
315
+ debugLog(chalk.red(" sendErrorMessage ") + chalk.cyan(statusCode.toString()));
316
+ debugLog(chalk.red(" extraErrorDescription ") + chalk.cyan(extraErrorDescription));
317
+ }
318
+
319
+ const reason = `${statusCode.toString()}:${extraErrorDescription || ""}`;
320
+ const errorResponse = new TCPErrorMessage({
321
+ statusCode,
322
+ reason
323
+ });
324
+ const messageChunk = packTcpMessage("ERR", errorResponse);
325
+ this.write(messageChunk);
326
+ }
327
+
328
+ public prematureTerminate(err: Error, statusCode: StatusCode): void {
329
+ // https://reference.opcfoundation.org/v104/Core/docs/Part6/6.7.3/
330
+
331
+ debugLog("prematureTerminate", err ? err.message : "", statusCode.toString());
332
+
293
333
  if (this._socket) {
294
- err.message = "socket has timeout: EPIPE: " + err.message;
334
+ err.message = "premature socket termination " + err.message;
295
335
  // we consider this as an error
296
336
  const _s = this._socket;
297
337
  _s.end();
@@ -301,6 +341,7 @@ export class TCP_transport extends EventEmitter {
301
341
  this.dispose();
302
342
  _s.removeAllListeners();
303
343
  }
344
+ // this.gracefullShutdown(err);
304
345
  }
305
346
  /**
306
347
  * @method _install_one_time_message_receiver
@@ -341,7 +382,10 @@ export class TCP_transport extends EventEmitter {
341
382
  return false;
342
383
  }
343
384
 
344
- private _on_message_received(messageChunk: Buffer) {
385
+ private _on_message_chunk_received(messageChunk: Buffer) {
386
+ if (doTraceIncomingChunk) {
387
+ console.log(hexDump(messageChunk));
388
+ }
345
389
  const hadCallback = this._fulfill_pending_promises(null, messageChunk);
346
390
  this.chunkReadCount++;
347
391
  if (!hadCallback) {
@@ -350,7 +394,7 @@ export class TCP_transport extends EventEmitter {
350
394
  * @event message
351
395
  * @param message_chunk the message chunk
352
396
  */
353
- this.emit("message", messageChunk);
397
+ this.emit("chunk", messageChunk);
354
398
  }
355
399
  }
356
400
 
@@ -397,6 +441,7 @@ export class TCP_transport extends EventEmitter {
397
441
  }
398
442
 
399
443
  private _on_socket_data(data: Buffer): void {
444
+ // istanbul ignore next
400
445
  if (!this.packetAssembler) {
401
446
  throw new Error("internal Error");
402
447
  }
package/source/utils.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  //
2
2
  const transportFlag = (process.env?.NODEOPCUADEBUG?.match(/TRANSPORT{([^}]*)}/) || [])[1] || "";
3
- export const doTraceHelloAck = transportFlag.match(/HELACK/);
4
- export const doTraceChunk = transportFlag.match(/CHUNK/);
3
+ export const doTraceHelloAck = !!transportFlag.match(/HELACK/);
4
+ export const doTraceChunk = !!transportFlag.match(/CHUNK/);
5
+ export const doTraceIncomingChunk = !!transportFlag.match(/FLOW/);
@@ -3,6 +3,9 @@ import { assert } from "node-opcua-assert";
3
3
  import { setFakeTransport } from "../source";
4
4
  import { HalfComChannel } from "./half_com_channel";
5
5
 
6
+ export interface DirectTransport {
7
+ on(eventName: "end", eventHandler:()=>void): this;
8
+ }
6
9
  export class DirectTransport extends EventEmitter {
7
10
  public client: HalfComChannel;
8
11
  public server: HalfComChannel;
@@ -32,7 +35,7 @@ export class DirectTransport extends EventEmitter {
32
35
  this.server._hasEnded = true;
33
36
  });
34
37
 
35
- this.server.on("end", (err: Error) => {
38
+ this.server.on("end", (err?: Error) => {
36
39
  this.emit("end", err);
37
40
  });
38
41
 
@@ -2,6 +2,13 @@
2
2
  import { EventEmitter } from "events";
3
3
  import { assert } from "node-opcua-assert";
4
4
 
5
+ export interface HalfComChannel {
6
+ on(eventName: "data", eventHandler:(data: Buffer)=>void): this;
7
+ on(eventName: "send_data", eventHandler:(data: Buffer)=>void): this;
8
+ on(eventName: "ending", eventHandler:()=>void): this;
9
+ on(eventName: "end", eventHandler:(err?: Error)=>void): this;
10
+
11
+ }
5
12
  export class HalfComChannel extends EventEmitter {
6
13
  public _hasEnded: boolean;
7
14