UNPKG

node-opcua-transport

Version:

pure nodejs OPCUA SDK - module transport

183 lines 8.31 kB
"use strict"; /** * @module node-opcua-transport * * Transport-agnostic base class for client-side OPC UA transports. * * Owns the UACP HEL/ACK handshake, the negotiated transport settings, and the * post-connect connection-break detector. Concrete subclasses (`ClientTCP_transport`, * `ClientWS_transport`, ...) implement only the socket-creation step in `connect()`. * * Browser-safe: does not import `node:net`, `node:os`, `node:util`, or any other * Node-only built-in beyond what `TCP_transport` already inherits from `node:events`. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ClientTransportBase = void 0; const node_opcua_assert_1 = require("node-opcua-assert"); const node_opcua_binary_stream_1 = require("node-opcua-binary-stream"); const node_opcua_chunkmanager_1 = require("node-opcua-chunkmanager"); const node_opcua_debug_1 = require("node-opcua-debug"); const AcknowledgeMessage_1 = require("./AcknowledgeMessage"); const HelloMessage_1 = require("./HelloMessage"); const TCPErrorMessage_1 = require("./TCPErrorMessage"); const tcp_transport_1 = require("./tcp_transport"); const tools_1 = require("./tools"); const utils_1 = require("./utils"); // Use a string category instead of `__filename` so the module loads in // browsers without a Node-style filename global. const doDebug = (0, node_opcua_debug_1.checkDebugFlag)("ClientTransportBase"); const debugLog = (0, node_opcua_debug_1.make_debugLog)("ClientTransportBase"); const warningLog = (0, node_opcua_debug_1.make_warningLog)("ClientTransportBase"); // biome-ignore lint/suspicious/noUnsafeDeclarationMerging: companion to the interface above class ClientTransportBase extends tcp_transport_1.TCP_transport { static defaultMaxChunk = 0; // 0 - no limits static defaultMaxMessageSize = 0; // 0 - no limits static defaultReceiveBufferSize = 1024 * 64 * 10; static defaultSendBufferSize = 1024 * 64 * 10; // 8192 min, endpointUrl; serverUri; numberOfRetry; parameters; _counter; _helloSettings; constructor(transportSettings) { super(); this.endpointUrl = ""; this.serverUri = ""; this._counter = 0; this.numberOfRetry = 0; // initially before HEL/ACK this.maxChunkCount = 1; this.maxMessageSize = 4 * 1024; this.receiveBufferSize = 4 * 1024; transportSettings = transportSettings || {}; this._helloSettings = { maxChunkCount: transportSettings.maxChunkCount || ClientTransportBase.defaultMaxChunk, maxMessageSize: transportSettings.maxMessageSize || ClientTransportBase.defaultMaxMessageSize, receiveBufferSize: transportSettings.receiveBufferSize || ClientTransportBase.defaultReceiveBufferSize, sendBufferSize: transportSettings.sendBufferSize || ClientTransportBase.defaultSendBufferSize }; } getTransportSettings() { return this._helloSettings; } dispose() { /* c8 ignore next */ doDebug && debugLog(" ClientTransportBase disposed"); super.dispose(); } /** * Install the post-connect "connection break" detector. Subclasses call this * once the underlying socket is open and the HEL/ACK transaction has succeeded. * * Detects ECONNRESET / EPIPE / premature socket termination on the live socket * and re-emits them as `connection_break` so reconnection logic upstream can * react. */ _install_post_connect_error_handler(endpointUrl) { if (!this._socket) return; this._socket.on("error", (err) => { // EPIPE : a write on a pipe/socket/FIFO with no reader. // ECONNRESET : connection forcibly closed by the peer (timeout, reboot, ...). // "premature socket termination" : abrupt close mid-message. if (err.message.match(/ECONNRESET|EPIPE|premature socket termination/)) { /* c8 ignore next */ doDebug && debugLog("connection_break after reconnection", endpointUrl); this.emit("connection_break", err); } }); } _perform_HEL_ACK_transaction(callback) { /* c8 ignore next */ if (!this._socket) { callback(new Error("No socket available to perform HEL/ACK transaction")); return; } (0, node_opcua_assert_1.assert)(this._socket, "expecting a valid socket to send a message"); (0, node_opcua_assert_1.assert)(typeof callback === "function"); this._counter = 0; /* c8 ignore next */ doDebug && debugLog("entering _perform_HEL_ACK_transaction"); this._install_one_time_message_receiver((err, data) => { /* c8 ignore next */ doDebug && debugLog("before _on_ACK_response ", err ? err.message : ""); this._on_ACK_response(callback, err, data); }); this._send_HELLO_request(); } _send_HELLO_request() { /* c8 ignore next */ doDebug && debugLog("entering _send_HELLO_request"); (0, node_opcua_assert_1.assert)(this._socket); (0, node_opcua_assert_1.assert)(Number.isFinite(this.protocolVersion)); (0, node_opcua_assert_1.assert)(this.endpointUrl.length > 0, " expecting a valid endpoint url"); const { maxChunkCount, maxMessageSize, receiveBufferSize, sendBufferSize } = this._helloSettings; const helloMessage = new HelloMessage_1.HelloMessage({ endpointUrl: this.endpointUrl, protocolVersion: this.protocolVersion, maxChunkCount, maxMessageSize, receiveBufferSize, sendBufferSize }); // c8 ignore next utils_1.doTraceHelloAck && warningLog(`sending Hello\n ${helloMessage.toString()} `); const messageChunk = (0, tools_1.packTcpMessage)("HEL", helloMessage); this._write_chunk(messageChunk); } _on_ACK_response(externalCallback, err, data) { /* c8 ignore next */ doDebug && debugLog("entering _on_ACK_response"); (0, node_opcua_assert_1.assert)(typeof externalCallback === "function"); (0, node_opcua_assert_1.assert)(this._counter === 0, "Ack response should only be received once !"); this._counter += 1; if (err || !data) { if (this._socket) { const s = this._socket; this._socket = null; s.destroy(); } externalCallback(err || new Error("no data")); } else { this._handle_ACK_response(data, externalCallback); } } _handle_ACK_response(messageChunk, callback) { const _stream = new node_opcua_binary_stream_1.BinaryStream(messageChunk); const messageHeader = (0, node_opcua_chunkmanager_1.readMessageHeader)(_stream); let err = null; /* c8 ignore next */ if (messageHeader.isFinal !== "F") { err = new Error(" invalid ACK message"); callback(err); return; } let responseClass; let response; if (messageHeader.msgType === "ERR") { responseClass = TCPErrorMessage_1.TCPErrorMessage; _stream.rewind(); response = (0, tools_1.decodeMessage)(_stream, responseClass); err = new Error(`ACK: ERR received ${response.statusCode.toString()} : ${response.reason}`); // biome-ignore lint/suspicious/noExplicitAny: legacy diagnostic field tacked onto Error err.statusCode = response.statusCode; // c8 ignore next utils_1.doTraceHelloAck && warningLog("receiving ERR instead of Ack", response.toString()); callback(err); } else { responseClass = AcknowledgeMessage_1.AcknowledgeMessage; _stream.rewind(); response = (0, tools_1.decodeMessage)(_stream, responseClass); this.parameters = response; this.setLimits(response); // c8 ignore next utils_1.doTraceHelloAck && warningLog("receiving Ack\n", response.toString()); callback(); } } } exports.ClientTransportBase = ClientTransportBase; //# sourceMappingURL=client_transport_base.js.map