node-opcua-transport 2.51.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 (69) hide show
  1. package/.mocharc.yml +10 -0
  2. package/LICENSE +20 -0
  3. package/dist/source/AcknowledgeMessage.d.ts +27 -0
  4. package/dist/source/AcknowledgeMessage.js +79 -0
  5. package/dist/source/AcknowledgeMessage.js.map +1 -0
  6. package/dist/source/HelloMessage.d.ts +27 -0
  7. package/dist/source/HelloMessage.js +95 -0
  8. package/dist/source/HelloMessage.js.map +1 -0
  9. package/dist/source/TCPErrorMessage.d.ts +18 -0
  10. package/dist/source/TCPErrorMessage.js +47 -0
  11. package/dist/source/TCPErrorMessage.js.map +1 -0
  12. package/dist/source/client_tcp_transport.d.ts +68 -0
  13. package/dist/source/client_tcp_transport.js +315 -0
  14. package/dist/source/client_tcp_transport.js.map +1 -0
  15. package/dist/source/index.d.ts +11 -0
  16. package/dist/source/index.js +24 -0
  17. package/dist/source/index.js.map +1 -0
  18. package/dist/source/message_builder_base.d.ts +61 -0
  19. package/dist/source/message_builder_base.js +207 -0
  20. package/dist/source/message_builder_base.js.map +1 -0
  21. package/dist/source/server_tcp_transport.d.ts +45 -0
  22. package/dist/source/server_tcp_transport.js +232 -0
  23. package/dist/source/server_tcp_transport.js.map +1 -0
  24. package/dist/source/tcp_transport.d.ts +117 -0
  25. package/dist/source/tcp_transport.js +350 -0
  26. package/dist/source/tcp_transport.js.map +1 -0
  27. package/dist/source/tools.d.ts +13 -0
  28. package/dist/source/tools.js +99 -0
  29. package/dist/source/tools.js.map +1 -0
  30. package/dist/source/utils.d.ts +2 -0
  31. package/dist/source/utils.js +9 -0
  32. package/dist/source/utils.js.map +1 -0
  33. package/dist/test-fixtures/fixture_full_tcp_packets.d.ts +21 -0
  34. package/dist/test-fixtures/fixture_full_tcp_packets.js +413 -0
  35. package/dist/test-fixtures/fixture_full_tcp_packets.js.map +1 -0
  36. package/dist/test-fixtures/index.d.ts +1 -0
  37. package/dist/test-fixtures/index.js +14 -0
  38. package/dist/test-fixtures/index.js.map +1 -0
  39. package/dist/test_helpers/direct_transport.d.ts +14 -0
  40. package/dist/test_helpers/direct_transport.js +63 -0
  41. package/dist/test_helpers/direct_transport.js.map +1 -0
  42. package/dist/test_helpers/fake_server.d.ts +15 -0
  43. package/dist/test_helpers/fake_server.js +56 -0
  44. package/dist/test_helpers/fake_server.js.map +1 -0
  45. package/dist/test_helpers/half_com_channel.d.ts +10 -0
  46. package/dist/test_helpers/half_com_channel.js +35 -0
  47. package/dist/test_helpers/half_com_channel.js.map +1 -0
  48. package/dist/test_helpers/index.d.ts +4 -0
  49. package/dist/test_helpers/index.js +17 -0
  50. package/dist/test_helpers/index.js.map +1 -0
  51. package/dist/test_helpers/socket_transport.d.ts +8 -0
  52. package/dist/test_helpers/socket_transport.js +30 -0
  53. package/dist/test_helpers/socket_transport.js.map +1 -0
  54. package/package.json +50 -0
  55. package/source/AcknowledgeMessage.ts +112 -0
  56. package/source/HelloMessage.ts +133 -0
  57. package/source/TCPErrorMessage.ts +57 -0
  58. package/source/client_tcp_transport.ts +366 -0
  59. package/source/index.ts +11 -0
  60. package/source/message_builder_base.ts +263 -0
  61. package/source/server_tcp_transport.ts +284 -0
  62. package/source/tcp_transport.ts +450 -0
  63. package/source/tools.ts +113 -0
  64. package/source/utils.ts +4 -0
  65. package/test_helpers/direct_transport.ts +78 -0
  66. package/test_helpers/fake_server.ts +71 -0
  67. package/test_helpers/half_com_channel.ts +38 -0
  68. package/test_helpers/index.ts +4 -0
  69. package/test_helpers/socket_transport.ts +34 -0
@@ -0,0 +1,366 @@
1
+ /**
2
+ * @module node-opcua-transport
3
+ */
4
+ // tslint:disable:class-name
5
+ // system
6
+ import * as os from "os";
7
+ import * as chalk from "chalk";
8
+
9
+ import { createConnection, Socket } from "net";
10
+ import { assert } from "node-opcua-assert";
11
+ import { BinaryStream } from "node-opcua-binary-stream";
12
+ import { readMessageHeader } from "node-opcua-chunkmanager";
13
+ import { ErrorCallback } from "node-opcua-status-code";
14
+
15
+ import { getFakeTransport, TCP_transport } from "./tcp_transport";
16
+ import { decodeMessage, packTcpMessage, parseEndpointUrl } from "./tools";
17
+
18
+ import * as debug from "node-opcua-debug";
19
+ import { AcknowledgeMessage } from "./AcknowledgeMessage";
20
+ import { HelloMessage } from "./HelloMessage";
21
+ import { TCPErrorMessage } from "./TCPErrorMessage";
22
+ import { doTraceHelloAck } from "./utils";
23
+
24
+ const doDebug = debug.checkDebugFlag(__filename);
25
+ const debugLog = debug.make_debugLog(__filename);
26
+ const errorLog = debug.make_errorLog(__filename);
27
+ const gHostname = os.hostname();
28
+
29
+ function createClientSocket(endpointUrl: string): Socket {
30
+ // create a socket based on Url
31
+ const ep = parseEndpointUrl(endpointUrl);
32
+ const port = parseInt(ep.port!, 10);
33
+ const hostname = ep.hostname!;
34
+ let socket: Socket;
35
+ switch (ep.protocol) {
36
+ case "opc.tcp:":
37
+ socket = createConnection({ host: hostname, port });
38
+
39
+ // // Setting true for noDelay will immediately fire off data each time socket.write() is called.
40
+ socket.setNoDelay(true);
41
+
42
+ return socket;
43
+ case "fake:":
44
+ socket = getFakeTransport();
45
+ assert(ep.protocol === "fake:", " Unsupported transport protocol");
46
+ process.nextTick(() => socket.emit("connect"));
47
+ return socket;
48
+
49
+ case "websocket:":
50
+ case "http:":
51
+ case "https:FF":
52
+ default: {
53
+ const msg = "[NODE-OPCUA-E05] this transport protocol is not supported :" + ep.protocol;
54
+ errorLog(msg);
55
+ throw new Error(msg);
56
+ }
57
+ }
58
+ }
59
+ export interface ClientTCP_transport {
60
+ on(eventName: "message", eventHandler: (message: Buffer) => void): this;
61
+ once(eventName: "message", eventHandler: (message: Buffer) => void): this;
62
+ on(eventName: "socket_closed", eventHandler: (err: Error | null) => void): this;
63
+ once(eventName: "socket_closed", eventHandler: (err: Error | null) => void): this;
64
+ on(eventName: "close", eventHandler: (err: Error | null) => void): this;
65
+ once(eventName: "close", eventHandler: (err: Error | null) => void): this;
66
+ //
67
+ on(eventName: "connection_break", eventHandler: () => void): this;
68
+ once(eventName: "connection_break", eventHandler: () => void): this;
69
+ }
70
+
71
+ /**
72
+ * a ClientTCP_transport connects to a remote server socket and
73
+ * initiates a communication with a HEL/ACK transaction.
74
+ * It negotiates the communication parameters with the other end.
75
+ *
76
+ * @class ClientTCP_transport
77
+ * @extends TCP_transport
78
+ * @constructor
79
+ * @example
80
+ *
81
+ * ```javascript
82
+ * const transport = ClientTCP_transport(url);
83
+ *
84
+ * transport.timeout = 10000;
85
+ *
86
+ * transport.connect(function(err)) {
87
+ * if (err) {
88
+ * // cannot connect
89
+ * } else {
90
+ * // connected
91
+ *
92
+ * }
93
+ * });
94
+ * ....
95
+ *
96
+ * transport.write(message_chunk,'F');
97
+ *
98
+ * ....
99
+ *
100
+ * transport.on("message",function(message_chunk) {
101
+ * // do something with message from server...
102
+ * });
103
+ *
104
+ *
105
+ * ```
106
+ *
107
+ *
108
+ */
109
+ export class ClientTCP_transport extends TCP_transport {
110
+ public endpointUrl: string;
111
+ public serverUri: string;
112
+ public numberOfRetry: number;
113
+ public parameters?: AcknowledgeMessage;
114
+
115
+ private connected: boolean;
116
+ private _counter: number;
117
+
118
+ constructor() {
119
+ super();
120
+ this.connected = false;
121
+ this.endpointUrl = "";
122
+ this.serverUri = "";
123
+ this._counter = 0;
124
+ this.numberOfRetry = 0;
125
+ }
126
+
127
+ public dispose() {
128
+ /* istanbul ignore next */
129
+ if (doDebug) {
130
+ debugLog(" ClientTCP_transport disposed");
131
+ }
132
+ super.dispose();
133
+ }
134
+
135
+ public connect(endpointUrl: string, callback: ErrorCallback) {
136
+ assert(arguments.length === 2);
137
+ assert(typeof callback === "function");
138
+
139
+ const ep = parseEndpointUrl(endpointUrl);
140
+
141
+ this.endpointUrl = endpointUrl;
142
+
143
+ this.serverUri = "urn:" + gHostname + ":Sample";
144
+ /* istanbul ignore next */
145
+ if (doDebug) {
146
+ debugLog(chalk.cyan("ClientTCP_transport#connect(endpointUrl = " + endpointUrl + ")"));
147
+ }
148
+ try {
149
+ this._socket = createClientSocket(endpointUrl);
150
+ } catch (err) {
151
+ /* istanbul ignore next */
152
+ if (doDebug) {
153
+ debugLog("CreateClientSocket has failed");
154
+ }
155
+ return callback(err as Error);
156
+ }
157
+
158
+ const _on_socket_error_after_connection = (err: Error) => {
159
+ /* istanbul ignore next */
160
+ if (doDebug) {
161
+ debugLog(" _on_socket_error_after_connection ClientTCP_transport Socket Error", err.message);
162
+ }
163
+ // EPIPE : EPIPE (Broken pipe): A write on a pipe, socket, or FIFO for which there is no process to read the
164
+ // data. Commonly encountered at the net and http layers, indicative that the remote side of the stream
165
+ // being written to has been closed.
166
+
167
+ // ECONNRESET (Connection reset by peer): A connection was forcibly closed by a peer. This normally results
168
+ // from a loss of the connection on the remote socket due to a timeout or reboot. Commonly encountered
169
+ // via the http and net module
170
+ if (err.message.match(/ECONNRESET|EPIPE/)) {
171
+ /**
172
+ * @event connection_break
173
+ *
174
+ */
175
+ this.emit("connection_break");
176
+ }
177
+ };
178
+
179
+ const _on_socket_connect = () => {
180
+ /* istanbul ignore next */
181
+ if (doDebug) {
182
+ debugLog("entering _on_socket_connect");
183
+ }
184
+ _remove_connect_listeners();
185
+ this._perform_HEL_ACK_transaction((err?: Error) => {
186
+ if (!err) {
187
+ /* istanbul ignore next */
188
+ if (!this._socket) {
189
+ throw new Error("internal error");
190
+ }
191
+ // install error handler to detect connection break
192
+ this._socket.on("error", _on_socket_error_after_connection);
193
+
194
+ this.connected = true;
195
+ /**
196
+ * notify the observers that the transport is connected (the socket is connected and the the HEL/ACK
197
+ * transaction has been done)
198
+ * @event connect
199
+ *
200
+ */
201
+ this.emit("connect");
202
+ } else {
203
+ debugLog("_perform_HEL_ACK_transaction has failed with err=", err.message);
204
+ }
205
+ callback(err);
206
+ });
207
+ };
208
+
209
+ const _on_socket_error_for_connect = (err: Error) => {
210
+ // this handler will catch attempt to connect to an inaccessible address.
211
+ /* istanbul ignore next */
212
+ if (doDebug) {
213
+ debugLog(chalk.cyan("ClientTCP_transport#connect - _on_socket_error_for_connect"), err.message);
214
+ }
215
+ assert(err instanceof Error);
216
+ _remove_connect_listeners();
217
+ callback(err);
218
+ };
219
+
220
+ const _on_socket_end_for_connect = (err: Error | null) => {
221
+ /* istanbul ignore next */
222
+ if (doDebug) {
223
+ debugLog(
224
+ chalk.cyan("ClientTCP_transport#connect -> _on_socket_end_for_connect Socket has been closed by server"),
225
+ err
226
+ );
227
+ }
228
+ };
229
+
230
+ const _remove_connect_listeners = () => {
231
+ /* istanbul ignore next */
232
+ if (!this._socket) {
233
+ return;
234
+ }
235
+ this._socket.removeListener("error", _on_socket_error_for_connect);
236
+ this._socket.removeListener("end", _on_socket_end_for_connect);
237
+ };
238
+
239
+ this._socket.once("error", _on_socket_error_for_connect);
240
+ this._socket.once("end", _on_socket_end_for_connect);
241
+ this._socket.once("connect", _on_socket_connect);
242
+ this._install_socket(this._socket);
243
+ }
244
+
245
+ protected on_socket_ended(err: Error | null) {
246
+ debugLog("on_socket_ended", this.name, err ? err.message : "");
247
+ if (this.connected) {
248
+ super.on_socket_ended(err);
249
+ }
250
+ // if (this._socket) {
251
+ // this._socket.removeAllListeners();
252
+ // }
253
+ }
254
+
255
+ private _handle_ACK_response(messageChunk: Buffer, callback: ErrorCallback) {
256
+ const _stream = new BinaryStream(messageChunk);
257
+ const messageHeader = readMessageHeader(_stream);
258
+ let err;
259
+ /* istanbul ignore next */
260
+ if (messageHeader.isFinal !== "F") {
261
+ err = new Error(" invalid ACK message");
262
+ return callback(err);
263
+ }
264
+
265
+ let responseClass;
266
+ let response;
267
+
268
+ if (messageHeader.msgType === "ERR") {
269
+ responseClass = TCPErrorMessage;
270
+ _stream.rewind();
271
+ response = decodeMessage(_stream, responseClass) as TCPErrorMessage;
272
+
273
+ err = new Error("ACK: ERR received " + response.statusCode.toString() + " : " + response.reason);
274
+ (err as any).statusCode = response.statusCode;
275
+ // istanbul ignore next
276
+ if (doTraceHelloAck) {
277
+ console.log("receiving ERR instead of Ack", response.toString());
278
+ }
279
+ callback(err);
280
+ } else {
281
+ responseClass = AcknowledgeMessage;
282
+ _stream.rewind();
283
+ response = decodeMessage(_stream, responseClass);
284
+ this.parameters = response as AcknowledgeMessage;
285
+
286
+ // istanbul ignore next
287
+ if (doTraceHelloAck) {
288
+ console.log("receiving Ack\n", response.toString());
289
+ }
290
+
291
+ callback();
292
+ }
293
+ }
294
+
295
+ private _send_HELLO_request() {
296
+ /* istanbul ignore next */
297
+ if (doDebug) {
298
+ debugLog("entering _send_HELLO_request");
299
+ }
300
+ assert(this._socket);
301
+ assert(isFinite(this.protocolVersion));
302
+ assert(this.endpointUrl.length > 0, " expecting a valid endpoint url");
303
+
304
+ // Write a message to the socket as soon as the client is connected,
305
+ // the server will receive it as message from the client
306
+ const helloMessage = new HelloMessage({
307
+ endpointUrl: this.endpointUrl,
308
+ maxChunkCount: 0, // 0 - no limits
309
+ maxMessageSize: 0, // 0 - no limits
310
+
311
+ protocolVersion: this.protocolVersion,
312
+ receiveBufferSize: 1024 * 64 * 10,
313
+ sendBufferSize: 1024 * 64 * 10 // 8192 min,
314
+ });
315
+ // istanbul ignore next
316
+ if (doTraceHelloAck) {
317
+ console.log(`sending Hello\n ${helloMessage.toString()}`);
318
+ }
319
+
320
+ const messageChunk = packTcpMessage("HEL", helloMessage);
321
+ this._write_chunk(messageChunk);
322
+ }
323
+
324
+ private _on_ACK_response(externalCallback: ErrorCallback, err: Error | null, data?: Buffer) {
325
+ /* istanbul ignore next */
326
+ if (doDebug) {
327
+ debugLog("entering _on_ACK_response");
328
+ }
329
+
330
+ assert(typeof externalCallback === "function");
331
+ assert(this._counter === 0, "Ack response should only be received once !");
332
+ this._counter += 1;
333
+
334
+ if (err || !data) {
335
+ externalCallback(err || new Error("no data"));
336
+ if (this._socket) {
337
+ this._socket.end();
338
+ // Xx this._socket.removeAllListeners();
339
+ }
340
+ } else {
341
+ this._handle_ACK_response(data, externalCallback);
342
+ }
343
+ }
344
+
345
+ private _perform_HEL_ACK_transaction(callback: ErrorCallback) {
346
+ /* istanbul ignore next */
347
+ if (!this._socket) {
348
+ return callback(new Error("No socket available to perform HEL/ACK transaction"));
349
+ }
350
+ assert(this._socket, "expecting a valid socket to send a message");
351
+ assert(typeof callback === "function");
352
+ this._counter = 0;
353
+ /* istanbul ignore next */
354
+ if (doDebug) {
355
+ debugLog("entering _perform_HEL_ACK_transaction");
356
+ }
357
+ this._install_one_time_message_receiver((err: Error | null, data?: Buffer) => {
358
+ /* istanbul ignore next */
359
+ if (doDebug) {
360
+ debugLog("before _on_ACK_response ", err ? err.message : "");
361
+ }
362
+ this._on_ACK_response(callback, err, data);
363
+ });
364
+ this._send_HELLO_request();
365
+ }
366
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @module node-opcua-transport
3
+ */
4
+ export * from "./HelloMessage";
5
+ export * from "./AcknowledgeMessage";
6
+ export * from "./TCPErrorMessage";
7
+ export * from "./client_tcp_transport";
8
+ export * from "./server_tcp_transport";
9
+ export * from "./tcp_transport";
10
+ export * from "./tools";
11
+ export * from "./message_builder_base";
@@ -0,0 +1,263 @@
1
+ /**
2
+ * @module node-opcua-transport
3
+ */
4
+ import { EventEmitter } from "events";
5
+ import { assert } from "node-opcua-assert";
6
+
7
+ import { BinaryStream } from "node-opcua-binary-stream";
8
+ import { createFastUninitializedBuffer } from "node-opcua-buffer-utils";
9
+ import { readMessageHeader, SequenceHeader } from "node-opcua-chunkmanager";
10
+ import { make_errorLog, make_debugLog } from "node-opcua-debug";
11
+ import { MessageHeader, PacketAssembler, PacketInfo } from "node-opcua-packet-assembler";
12
+ import { get_clock_tick } from "node-opcua-utils";
13
+
14
+ const doPerfMonitoring = process.env.NODEOPCUADEBUG && process.env.NODEOPCUADEBUG.indexOf("PERF") >= 0;
15
+
16
+ const errorLog = make_errorLog("MessageBuilder");
17
+ const debugLog = make_debugLog("MessageBuilder");
18
+
19
+ export function readRawMessageHeader(data: Buffer): PacketInfo {
20
+ const messageHeader = readMessageHeader(new BinaryStream(data));
21
+ return {
22
+ extra: "",
23
+ length: messageHeader.length,
24
+ messageHeader
25
+ };
26
+ }
27
+
28
+ /**
29
+ * @class MessageBuilderBase
30
+ * @extends EventEmitter
31
+ * @uses PacketAssembler
32
+ * @constructor
33
+ * @param options {Object}
34
+ * @param [options.signatureLength=0] {number}
35
+ *
36
+ */
37
+ export class MessageBuilderBase extends EventEmitter {
38
+ public readonly signatureLength: number;
39
+ public readonly options: { signatureLength?: number };
40
+ public readonly _packetAssembler: PacketAssembler;
41
+ public channelId: number;
42
+ public totalMessageSize: number;
43
+ public sequenceHeader: SequenceHeader | null;
44
+
45
+ public _tick0: number;
46
+ public _tick1: number;
47
+
48
+ protected id: string;
49
+
50
+ protected totalBodySize: number;
51
+ protected messageChunks: Buffer[];
52
+ protected messageHeader?: MessageHeader;
53
+
54
+ private _securityDefeated: boolean;
55
+ private _hasReceivedError: boolean;
56
+ private blocks: Buffer[];
57
+ private readonly _expectedChannelId: number;
58
+ private offsetBodyStart: number;
59
+
60
+ constructor(options?: { signatureLength?: number }) {
61
+ super();
62
+
63
+ this.id = "";
64
+
65
+ this._tick0 = 0;
66
+ this._tick1 = 0;
67
+ this._hasReceivedError = false;
68
+ this.blocks = [];
69
+ this.messageChunks = [];
70
+ this._expectedChannelId = 0;
71
+
72
+ options = options || {};
73
+
74
+ this.signatureLength = options.signatureLength || 0;
75
+
76
+ this.options = options;
77
+
78
+ this._packetAssembler = new PacketAssembler({
79
+ minimumSizeInBytes: 0,
80
+ readMessageFunc: readRawMessageHeader
81
+ });
82
+
83
+ this._packetAssembler.on("message", (messageChunk) => this._feed_messageChunk(messageChunk));
84
+
85
+ this._packetAssembler.on("newMessage", (info, data) => {
86
+ if (doPerfMonitoring) {
87
+ // record tick 0: when the first data is received
88
+ this._tick0 = get_clock_tick();
89
+ }
90
+ /**
91
+ *
92
+ * notify the observers that a new message is being built
93
+ * @event start_chunk
94
+ * @param info
95
+ * @param data
96
+ */
97
+ this.emit("start_chunk", info, data);
98
+ });
99
+
100
+ this._securityDefeated = false;
101
+ this.totalBodySize = 0;
102
+ this.totalMessageSize = 0;
103
+ this.channelId = 0;
104
+ this.offsetBodyStart = 0;
105
+ this.sequenceHeader = null;
106
+ this._init_new();
107
+ }
108
+
109
+ public dispose() {
110
+ this.removeAllListeners();
111
+ }
112
+
113
+ /**
114
+ * Feed message builder with some data
115
+ * @method feed
116
+ * @param data
117
+ */
118
+ public feed(data: Buffer) {
119
+ if (!this._securityDefeated && !this._hasReceivedError) {
120
+ this._packetAssembler.feed(data);
121
+ }
122
+ }
123
+
124
+ protected _decodeMessageBody(fullMessageBody: Buffer): boolean {
125
+ return true;
126
+ }
127
+
128
+ protected _read_headers(binaryStream: BinaryStream): boolean {
129
+ try {
130
+ this.messageHeader = readMessageHeader(binaryStream);
131
+ assert(binaryStream.length === 8, "expecting message header to be 8 bytes");
132
+
133
+ this.channelId = binaryStream.readUInt32();
134
+ assert(binaryStream.length === 12);
135
+
136
+ // verifying secure ChannelId
137
+ if (this._expectedChannelId && this.channelId !== this._expectedChannelId) {
138
+ return this._report_error("Invalid secure channel Id");
139
+ }
140
+ return true;
141
+ } catch (err) {
142
+ return false;
143
+ }
144
+ }
145
+
146
+ protected _report_error(errorMessage: string): false {
147
+ this._hasReceivedError = true;
148
+ /**
149
+ * notify the observers that an error has occurred
150
+ * @event error
151
+ * @param error the error to raise
152
+ */
153
+ debugLog("Error ", this.id, errorMessage);
154
+ // xx errorLog(new Error());
155
+ this.emit("error", new Error(errorMessage), this.sequenceHeader ? this.sequenceHeader.requestId : null);
156
+ return false;
157
+ }
158
+
159
+ private _init_new() {
160
+ this._securityDefeated = false;
161
+ this._hasReceivedError = false;
162
+ this.totalBodySize = 0;
163
+ this.totalMessageSize = 0;
164
+ this.blocks = [];
165
+ this.messageChunks = [];
166
+ }
167
+
168
+ /**
169
+ * append a message chunk
170
+ * @method _append
171
+ * @param chunk
172
+ * @private
173
+ */
174
+ private _append(chunk: Buffer): boolean {
175
+ if (this._hasReceivedError) {
176
+ // the message builder is in error mode and further message chunks should be discarded.
177
+ return false;
178
+ }
179
+
180
+ this.messageChunks.push(chunk);
181
+ this.totalMessageSize += chunk.length;
182
+
183
+ const binaryStream = new BinaryStream(chunk);
184
+
185
+ if (!this._read_headers(binaryStream)) {
186
+ return this._report_error(`Invalid message header detected`);
187
+ }
188
+
189
+ assert(binaryStream.length >= 12);
190
+
191
+ // verify message chunk length
192
+ if (this.messageHeader!.length !== chunk.length) {
193
+ // tslint:disable:max-line-length
194
+ return this._report_error(
195
+ `Invalid messageChunk size: the provided chunk is ${chunk.length} bytes long but header specifies ${
196
+ this.messageHeader!.length
197
+ }`
198
+ );
199
+ }
200
+
201
+ // the start of the message body block
202
+ const offsetBodyStart = binaryStream.length;
203
+
204
+ // the end of the message body block
205
+ const offsetBodyEnd = binaryStream.buffer.length;
206
+
207
+ this.totalBodySize += offsetBodyEnd - offsetBodyStart;
208
+ this.offsetBodyStart = offsetBodyStart;
209
+
210
+ // add message body to a queue
211
+ // note : Buffer.slice create a shared memory !
212
+ // use Buffer.clone
213
+ const sharedBuffer = chunk.slice(this.offsetBodyStart, offsetBodyEnd);
214
+ const clonedBuffer = createFastUninitializedBuffer(sharedBuffer.length);
215
+
216
+ sharedBuffer.copy(clonedBuffer, 0, 0);
217
+ this.blocks.push(clonedBuffer);
218
+
219
+ return true;
220
+ }
221
+
222
+ private _feed_messageChunk(chunk: Buffer) {
223
+ assert(chunk);
224
+ const messageHeader = readMessageHeader(new BinaryStream(chunk));
225
+ /**
226
+ * notify the observers that new message chunk has been received
227
+ * @event chunk
228
+ * @param messageChunk the raw message chunk
229
+ */
230
+ this.emit("chunk", chunk);
231
+
232
+ if (messageHeader.isFinal === "F") {
233
+ // last message
234
+ this._append(chunk);
235
+ if (this._hasReceivedError) {
236
+ return false;
237
+ }
238
+
239
+ const fullMessageBody: Buffer = this.blocks.length === 1 ? this.blocks[0] : Buffer.concat(this.blocks);
240
+
241
+ if (doPerfMonitoring) {
242
+ // record tick 1: when a complete message has been received ( all chunks assembled)
243
+ this._tick1 = get_clock_tick();
244
+ }
245
+ /**
246
+ * notify the observers that a full message has been received
247
+ * @event full_message_body
248
+ * @param full_message_body the full message body made of all concatenated chunks.
249
+ */
250
+ this.emit("full_message_body", fullMessageBody);
251
+
252
+ this._decodeMessageBody(fullMessageBody);
253
+ // be ready for next block
254
+ this._init_new();
255
+ return true;
256
+ } else if (messageHeader.isFinal === "A") {
257
+ return this._report_error("received and Abort Message");
258
+ } else if (messageHeader.isFinal === "C") {
259
+ return this._append(chunk);
260
+ }
261
+ return false;
262
+ }
263
+ }