UNPKG

node-opcua-transport

Version:

pure nodejs OPCUA SDK - module transport

464 lines (462 loc) 22.8 kB
"use strict"; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var _TCP_transport_instances, _a, _TCP_transport__closedEmitted, _TCP_transport__timerId, _TCP_transport__theCallback, _TCP_transport__on_error_during_one_time_message_receiver, _TCP_transport_packetAssembler, _TCP_transport__timeout, _TCP_transport__isDisconnecting, _TCP_transport__install_packetAssembler; Object.defineProperty(exports, "__esModule", { value: true }); exports.TCP_transport = void 0; exports.setFakeTransport = setFakeTransport; exports.getFakeTransport = getFakeTransport; /* eslint-disable @typescript-eslint/ban-types */ /** * @module node-opcua-transport */ const events_1 = require("events"); const chalk_1 = __importDefault(require("chalk")); const node_opcua_assert_1 = require("node-opcua-assert"); const node_opcua_debug_1 = require("node-opcua-debug"); const node_opcua_object_registry_1 = require("node-opcua-object-registry"); const node_opcua_packet_assembler_1 = require("node-opcua-packet-assembler"); const status_codes_1 = require("./status_codes"); const message_builder_base_1 = require("./message_builder_base"); const utils_1 = require("./utils"); const TCPErrorMessage_1 = require("./TCPErrorMessage"); const tools_1 = require("./tools"); const debugLog = (0, node_opcua_debug_1.make_debugLog)("TRANSPORT"); const doDebug = (0, node_opcua_debug_1.checkDebugFlag)("TRANSPORT"); const errorLog = (0, node_opcua_debug_1.make_errorLog)("TRANSPORT"); const warningLog = (0, node_opcua_debug_1.make_warningLog)("TRANSPORT"); const doDebugFlow = false; const defaultFakeSocket = { invalid: true, destroyed: false, destroy(err) { this.destroyed = true; errorLog("MockSocket.destroy"); }, end() { errorLog("MockSocket.end"); }, write(data, callback) { /** */ if (callback) { callback(); } }, setKeepAlive(enable, initialDelay) { return this; }, setNoDelay(noDelay) { return this; }, setTimeout(timeout, callback) { return this; } }; let fakeSocket = defaultFakeSocket; function setFakeTransport(mockSocket) { fakeSocket = mockSocket; } function getFakeTransport() { if (fakeSocket.invalid) { throw new Error("getFakeTransport: setFakeTransport must be called first - BadProtocolVersionUnsupported"); } process.nextTick(() => fakeSocket.emit("connect")); return fakeSocket; } let counter = 0; // tslint:disable:class-name class TCP_transport extends events_1.EventEmitter { constructor() { super(); _TCP_transport_instances.add(this); _TCP_transport__closedEmitted.set(this, undefined); _TCP_transport__timerId.set(this, void 0); _TCP_transport__theCallback.set(this, void 0); _TCP_transport__on_error_during_one_time_message_receiver.set(this, void 0); _TCP_transport_packetAssembler.set(this, void 0); _TCP_transport__timeout.set(this, void 0); _TCP_transport__isDisconnecting.set(this, false); this._theCloseError = null; this.name = this.constructor.name + counter; counter += 1; this._socket = null; __classPrivateFieldSet(this, _TCP_transport__timerId, null, "f"); __classPrivateFieldSet(this, _TCP_transport__timeout, 5000, "f"); // 5 seconds timeout __classPrivateFieldSet(this, _TCP_transport__theCallback, undefined, "f"); this.maxMessageSize = 0; this.maxChunkCount = 0; this.receiveBufferSize = 0; this.sendBufferSize = 0; this.protocolVersion = 0; this.bytesWritten = 0; this.bytesRead = 0; this.chunkWrittenCount = 0; this.chunkReadCount = 0; _a.registry.register(this); } toString() { let str = "\n"; str += " name.............. = " + this.name + "\n"; str += " protocolVersion... = " + this.protocolVersion + "\n"; str += " maxMessageSize.... = " + this.maxMessageSize + "\n"; str += " maxChunkCount..... = " + this.maxChunkCount + "\n"; str += " receiveBufferSize. = " + this.receiveBufferSize + "\n"; str += " sendBufferSize.... = " + this.sendBufferSize + "\n"; str += " bytesRead......... = " + this.bytesRead + "\n"; str += " bytesWritten...... = " + this.bytesWritten + "\n"; str += " chunkWrittenCount. = " + this.chunkWrittenCount + "\n"; str += " chunkReadCount.... = " + this.chunkReadCount + "\n"; str += " closeEmitted ? ....= " + __classPrivateFieldGet(this, _TCP_transport__closedEmitted, "f") + "\n"; return str; } setLimits({ receiveBufferSize, sendBufferSize, maxMessageSize, maxChunkCount }) { this.receiveBufferSize = receiveBufferSize; this.sendBufferSize = sendBufferSize; this.maxMessageSize = maxMessageSize; this.maxChunkCount = maxChunkCount; if (maxChunkCount !== 0) { if (maxMessageSize / sendBufferSize > maxChunkCount) { const expectedMaxChunkCount = Math.ceil(maxMessageSize / sendBufferSize); warningLog(`Warning: maxChunkCount is not big enough : maxMessageSize / sendBufferSize ${expectedMaxChunkCount} > maxChunkCount ${maxChunkCount}`); } if (maxMessageSize / receiveBufferSize > maxChunkCount) { const expectedMaxChunkCount = Math.ceil(maxMessageSize / receiveBufferSize); warningLog(`Warning: maxChunkCount is not big enough :maxMessageSize / sendBufferSize ${expectedMaxChunkCount} > maxChunkCount ${maxChunkCount}`); } } // reinstall packetAssembler with correct limits __classPrivateFieldGet(this, _TCP_transport_instances, "m", _TCP_transport__install_packetAssembler).call(this); } get timeout() { return __classPrivateFieldGet(this, _TCP_transport__timeout, "f"); } set timeout(value) { (0, node_opcua_assert_1.assert)(!__classPrivateFieldGet(this, _TCP_transport__timerId, "f")); debugLog("Setting socket " + this.name + " timeout = ", value); __classPrivateFieldSet(this, _TCP_transport__timeout, value, "f"); } dispose() { this._cleanup_timers(); (0, node_opcua_assert_1.assert)(!__classPrivateFieldGet(this, _TCP_transport__timerId, "f")); if (this._socket) { const gracefully = true; if (gracefully) { // close the connection gracefully this._socket.end(); } else { // close the connection forcefully this._socket.destroy(); } // this._socket.removeAllListeners(); this._socket = null; } _a.registry.unregister(this); } /** * write the message_chunk on the socket. * @param messageChunk */ write(messageChunk, callback) { const header = (0, message_builder_base_1.readRawMessageHeader)(messageChunk); (0, node_opcua_assert_1.assert)(header.length === messageChunk.length); const c = header.messageHeader.isFinal; (0, node_opcua_assert_1.assert)(c === "F" || c === "C" || c === "A"); this._write_chunk(messageChunk, (err) => { callback && callback(err); }); } isDisconnecting() { return !this._socket || __classPrivateFieldGet(this, _TCP_transport__isDisconnecting, "f"); } /** * disconnect the TCP layer and close the underlying socket. * The ```"close"``` event will be emitted to the observers with err=null. * */ disconnect(callback) { (0, node_opcua_assert_1.assert)(typeof callback === "function", "expecting a callback function, but got " + callback); if (!this._socket || __classPrivateFieldGet(this, _TCP_transport__isDisconnecting, "f")) { if (!__classPrivateFieldGet(this, _TCP_transport__isDisconnecting, "f")) { this.dispose(); } callback(); return; } __classPrivateFieldSet(this, _TCP_transport__isDisconnecting, true, "f"); this._cleanup_timers(); this._socket.prependOnceListener("close", () => { this._emitClose(null); setImmediate(() => { callback(); }); }); this._socket.end(); this._socket && this._socket.destroy(); this._socket = null; } isValid() { return this._socket !== null && !this._socket.destroyed; } _write_chunk(messageChunk, callback) { if (this._socket !== null) { this.bytesWritten += messageChunk.length; this.chunkWrittenCount++; this._socket.write(messageChunk, callback); } else { if (callback) { callback(); } } } _install_socket(socket) { // note: it is possible that a transport may be recycled and re-used again after a connection break (0, node_opcua_assert_1.assert)(socket); (0, node_opcua_assert_1.assert)(!this._socket, "already have a socket"); this._socket = socket; __classPrivateFieldSet(this, _TCP_transport__closedEmitted, undefined, "f"); this._theCloseError = null; (0, node_opcua_assert_1.assert)(__classPrivateFieldGet(this, _TCP_transport__closedEmitted, "f") === undefined, "TCP Transport has already been closed !"); this._socket.setKeepAlive(true); // Setting true for noDelay will immediately fire off data each time socket.write() is called. this._socket.setNoDelay(true); // set socket timeout debugLog(" TCP_transport#install => setting " + this.name + " _socket.setTimeout to ", this.timeout); // let use a large timeout here to make sure that we not conflict with our internal timeout this._socket.setTimeout(this.timeout); // istanbul ignore next doDebug && debugLog(" TCP_transport#_install_socket ", this.name); __classPrivateFieldGet(this, _TCP_transport_instances, "m", _TCP_transport__install_packetAssembler).call(this); this._socket .on("data", (data) => this._on_socket_data(data)) .on("close", (hadError) => this._on_socket_close(hadError)) .on("end", () => this._on_socket_end()) .on("error", (err) => this._on_socket_error(err)) .on("timeout", () => this._on_socket_timeout()); } sendErrorMessage(statusCode, extraErrorDescription) { // When the Client receives an Error Message it reports the error to the application and closes the TransportConnection gracefully. // If a Client encounters a fatal error, it shall report the error to the application and send a CloseSecureChannel Message. /* istanbul ignore next*/ if (doDebug) { debugLog(chalk_1.default.red(" sendErrorMessage ") + chalk_1.default.cyan(statusCode.toString())); debugLog(chalk_1.default.red(" extraErrorDescription ") + chalk_1.default.cyan(extraErrorDescription)); } const reason = `${statusCode.toString()}:${extraErrorDescription || ""}`; const errorResponse = new TCPErrorMessage_1.TCPErrorMessage({ statusCode, reason }); const messageChunk = (0, tools_1.packTcpMessage)("ERR", errorResponse); this.write(messageChunk); } prematureTerminate(err, statusCode) { // https://reference.opcfoundation.org/v104/Core/docs/Part6/6.7.3/ debugLog("prematureTerminate", err ? err.message : "", statusCode.toString(), "has socket = ", !!this._socket); doDebugFlow && errorLog("prematureTerminate from", "has socket = ", !!this._socket, new Error().stack); if (this._socket) { err.message = "premature socket termination " + err.message; // we consider this as an error const _s = this._socket; this._socket = null; _s.destroy(err); this.dispose(); } } /** * * install a one time message receiver callback * * Rules: * * TCP_transport will not emit the ```message``` event, while the "one time message receiver" is in operation. * * the TCP_transport will wait for the next complete message chunk and call the provided callback func * ```callback(null,messageChunk);``` * * if a messageChunk is not received within ```TCP_transport.timeout``` or if the underlying socket reports * an error, the callback function will be called with an Error. * */ _install_one_time_message_receiver(callback) { (0, node_opcua_assert_1.assert)(!__classPrivateFieldGet(this, _TCP_transport__theCallback, "f"), "callback already set"); (0, node_opcua_assert_1.assert)(typeof callback === "function"); this._start_one_time_message_receiver(callback); } _fulfill_pending_promises(err, data) { if (!__classPrivateFieldGet(this, _TCP_transport__theCallback, "f")) return false; doDebugFlow && errorLog("_fulfill_pending_promises from", new Error().stack); const callback = __classPrivateFieldGet(this, _TCP_transport__theCallback, "f"); __classPrivateFieldSet(this, _TCP_transport__theCallback, undefined, "f"); callback(err, data); return true; } _on_message_chunk_received(messageChunk) { if (utils_1.doTraceIncomingChunk) { warningLog("Transport", this.name); warningLog((0, node_opcua_debug_1.hexDump)(messageChunk)); } const hadCallback = this._fulfill_pending_promises(null, messageChunk); this.chunkReadCount++; if (!hadCallback) { this.emit("chunk", messageChunk); } } _cleanup_timers() { if (__classPrivateFieldGet(this, _TCP_transport__timerId, "f")) { clearTimeout(__classPrivateFieldGet(this, _TCP_transport__timerId, "f")); __classPrivateFieldSet(this, _TCP_transport__timerId, null, "f"); } } _start_one_time_message_receiver(callback) { (0, node_opcua_assert_1.assert)(!__classPrivateFieldGet(this, _TCP_transport__timerId, "f") && !__classPrivateFieldGet(this, _TCP_transport__on_error_during_one_time_message_receiver, "f"), "timer already started"); const _cleanUp = () => { this._cleanup_timers(); if (__classPrivateFieldGet(this, _TCP_transport__on_error_during_one_time_message_receiver, "f")) { this._socket?.removeListener("close", __classPrivateFieldGet(this, _TCP_transport__on_error_during_one_time_message_receiver, "f")); __classPrivateFieldSet(this, _TCP_transport__on_error_during_one_time_message_receiver, undefined, "f"); } }; const onTimeout = () => { _cleanUp(); this._fulfill_pending_promises(new Error(`Timeout(A) in waiting for data on socket ( timeout was = ${this.timeout} ms)`)); this.dispose(); }; // Setup timeout detection timer .... __classPrivateFieldSet(this, _TCP_transport__timerId, setTimeout(() => { __classPrivateFieldSet(this, _TCP_transport__timerId, null, "f"); onTimeout(); }, this.timeout), "f"); // also monitored if (this._socket) { // to do = intercept socket error as well __classPrivateFieldSet(this, _TCP_transport__on_error_during_one_time_message_receiver, (hadError) => { const err = new Error(`ERROR in waiting for data on socket ( timeout was = ${this.timeout} ms) hadError` + hadError); this._emitClose(err); this._fulfill_pending_promises(err); }, "f"); this._socket.prependOnceListener("close", __classPrivateFieldGet(this, _TCP_transport__on_error_during_one_time_message_receiver, "f")); } const _callback = callback; __classPrivateFieldSet(this, _TCP_transport__theCallback, (err, data) => { _cleanUp(); __classPrivateFieldSet(this, _TCP_transport__theCallback, undefined, "f"); _callback(err, data); }, "f"); } _on_socket_data(data) { // istanbul ignore next if (!__classPrivateFieldGet(this, _TCP_transport_packetAssembler, "f")) { throw new Error("internal Error"); } this.bytesRead += data.length; if (data.length > 0) { __classPrivateFieldGet(this, _TCP_transport_packetAssembler, "f").feed(data); } } _on_socket_close(hadError) { // istanbul ignore next if (doDebug) { debugLog(chalk_1.default.red(` SOCKET CLOSE ${this.name}: `), chalk_1.default.yellow("had_error ="), chalk_1.default.cyan(hadError.toString())); } this.dispose(); if (__classPrivateFieldGet(this, _TCP_transport__theCallback, "f")) return; // if (hadError) { // if (this._socket) { // this._socket.destroy(); // } // } this._emitClose(); } _emitClose(err) { err = err || this._theCloseError; doDebugFlow && warningLog("_emitClose ", err?.message || "", "from", new Error().stack); if (!__classPrivateFieldGet(this, _TCP_transport__closedEmitted, "f")) { __classPrivateFieldSet(this, _TCP_transport__closedEmitted, err || "noError", "f"); this.emit("close", err || null); // if (this._theCallback) { // const callback = this._theCallback; // this._theCallback = undefined; // callback(err || null); // } } else { debugLog("Already emitted close event", __classPrivateFieldGet(this, _TCP_transport__closedEmitted, "f").message); debugLog("err = ", err?.message); debugLog(""); debugLog("Already emitted close event", __classPrivateFieldGet(this, _TCP_transport__closedEmitted, "f")); debugLog("err = ", err?.message, err); } } _on_socket_end() { // istanbul ignore next doDebug && debugLog(chalk_1.default.red(` SOCKET END : ${this.name}`), "is disconnecting ", this.isDisconnecting()); if (this.isDisconnecting()) { return; } // debugLog(chalk_1.default.red(" Transport Connection ended") + " " + this.name); const err = new Error(this.name + ": socket has been disconnected by third party"); debugLog(" bytesRead = ", this.bytesRead); debugLog(" bytesWritten = ", this.bytesWritten); this._theCloseError = err; this._fulfill_pending_promises(new Error("Connection aborted - ended by server : " + (err ? err.message : ""))); } _on_socket_error(err) { // istanbul ignore next debugLog(chalk_1.default.red(` _on_socket_error: ${this.name}`), chalk_1.default.yellow(err.message)); // node The "close" event will be called directly following this event. // this._emitClose(err); } _on_socket_timeout() { const msg = `socket timeout : timeout=${this.timeout} ${this.name}`; doDebug && debugLog(msg); this.prematureTerminate(new Error(msg), status_codes_1.StatusCodes2.BadTimeout); } } exports.TCP_transport = TCP_transport; _a = TCP_transport, _TCP_transport__closedEmitted = new WeakMap(), _TCP_transport__timerId = new WeakMap(), _TCP_transport__theCallback = new WeakMap(), _TCP_transport__on_error_during_one_time_message_receiver = new WeakMap(), _TCP_transport_packetAssembler = new WeakMap(), _TCP_transport__timeout = new WeakMap(), _TCP_transport__isDisconnecting = new WeakMap(), _TCP_transport_instances = new WeakSet(), _TCP_transport__install_packetAssembler = function _TCP_transport__install_packetAssembler() { if (__classPrivateFieldGet(this, _TCP_transport_packetAssembler, "f")) { __classPrivateFieldGet(this, _TCP_transport_packetAssembler, "f").removeAllListeners(); __classPrivateFieldSet(this, _TCP_transport_packetAssembler, undefined, "f"); } // install packet assembler ... __classPrivateFieldSet(this, _TCP_transport_packetAssembler, new node_opcua_packet_assembler_1.PacketAssembler({ readChunkFunc: message_builder_base_1.readRawMessageHeader, minimumSizeInBytes: _a.headerSize, maxChunkSize: this.receiveBufferSize //Math.max(this.receiveBufferSize, this.sendBufferSize) }), "f"); __classPrivateFieldGet(this, _TCP_transport_packetAssembler, "f").on("chunk", (chunk) => this._on_message_chunk_received(chunk)); __classPrivateFieldGet(this, _TCP_transport_packetAssembler, "f").on("error", (err, code) => { let statusCode = status_codes_1.StatusCodes2.BadTcpMessageTooLarge; switch (code) { case node_opcua_packet_assembler_1.PacketAssemblerErrorCode.ChunkSizeExceeded: statusCode = status_codes_1.StatusCodes2.BadTcpMessageTooLarge; break; default: statusCode = status_codes_1.StatusCodes2.BadTcpInternalError; } this.sendErrorMessage(statusCode, err.message); this.prematureTerminate(new Error("Packet Assembler : " + err.message), statusCode); }); }; TCP_transport.registry = new node_opcua_object_registry_1.ObjectRegistry(); /** * the size of the header in bytes * @default 8 */ TCP_transport.headerSize = 8; //# sourceMappingURL=tcp_transport.js.map