UNPKG

node-opcua-transport

Version:

pure nodejs OPCUA SDK - module transport

263 lines 12.5 kB
"use strict"; /** * @module node-opcua-transport */ // tslint:disable:class-name // system var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ServerTCP_transport = void 0; exports.adjustLimitsWithParameters = adjustLimitsWithParameters; const chalk_1 = __importDefault(require("chalk")); 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"); // opcua requires const node_opcua_debug_1 = require("node-opcua-debug"); const node_opcua_status_code_1 = require("node-opcua-status-code"); // this package requires const AcknowledgeMessage_1 = require("./AcknowledgeMessage"); const HelloMessage_1 = require("./HelloMessage"); const tcp_transport_1 = require("./tcp_transport"); const tools_1 = require("./tools"); const utils_1 = require("./utils"); const debugLog = (0, node_opcua_debug_1.make_debugLog)("TRANSPORT"); const errorLog = (0, node_opcua_debug_1.make_errorLog)("TRANSPORT"); const warningLog = (0, node_opcua_debug_1.make_warningLog)("TRANSPORT"); const doDebug = (0, node_opcua_debug_1.checkDebugFlag)("TRANSPORT"); function clamp_value(value, minVal, maxVal) { (0, node_opcua_assert_1.assert)(minVal <= maxVal); if (value === 0) { return maxVal; } if (value < minVal) { return minVal; } /* c8 ignore next*/ if (value >= maxVal) { return maxVal; } return value; } const minimumBufferSize = 8192; const defaultTransportParameters = { minBufferSize: 8192, maxBufferSize: 8 * 64 * 1024, minMaxMessageSize: 128 * 1024, defaultMaxMessageSize: 16 * 1024 * 1024, maxMaxMessageSize: 128 * 1024 * 1024, minMaxChunkCount: 1, defaultMaxChunkCount: Math.ceil((128 * 1024 * 1024) / (8 * 64 * 1024)), maxMaxChunkCount: 9000 }; function adjustLimitsWithParameters(helloMessage, params) { const defaultReceiveBufferSize = 64 * 1024; const defaultSendBufferSize = 64 * 1024; const receiveBufferSize = clamp_value(helloMessage.receiveBufferSize || defaultReceiveBufferSize, params.minBufferSize, params.maxBufferSize); const sendBufferSize = clamp_value(helloMessage.sendBufferSize || defaultSendBufferSize, params.minBufferSize, params.maxBufferSize); const maxMessageSize = clamp_value(helloMessage.maxMessageSize || params.defaultMaxMessageSize, params.minMaxMessageSize, params.maxMaxMessageSize); if (!helloMessage.maxChunkCount && sendBufferSize) { helloMessage.maxChunkCount = Math.ceil(helloMessage.maxMessageSize / Math.min(sendBufferSize, receiveBufferSize)); } const maxChunkCount = clamp_value(helloMessage.maxChunkCount || params.defaultMaxChunkCount, params.minMaxChunkCount, params.maxMaxChunkCount); return { receiveBufferSize, sendBufferSize, maxMessageSize, maxChunkCount }; } const defaultAdjustLimits = (hello) => adjustLimitsWithParameters(hello, defaultTransportParameters); /** * @private */ class ServerTCP_transport extends tcp_transport_1.TCP_transport { static throttleTime = 1000; _aborted; _helloReceived; adjustLimits; constructor(options) { super(); this._aborted = 0; this._helloReceived = false; // before HEL/ACK this.maxChunkCount = 1; this.maxMessageSize = 4 * 1024; this.receiveBufferSize = 4 * 1024; this.adjustLimits = options?.adjustLimits || defaultAdjustLimits; } toString() { let str = super.toString(); str += `helloReceived...... = ${this._helloReceived}\n`; str += `aborted............ = ${this._aborted}\n`; return str; } _write_chunk(messageChunk) { // c8 ignore next if (this.sendBufferSize > 0 && messageChunk.length > this.sendBufferSize) { errorLog("write chunk exceed sendBufferSize messageChunk length = ", messageChunk.length, "sendBufferSize = ", this.sendBufferSize); } super._write_chunk(messageChunk); } /** * Initialize the server transport. * * * The ServerTCP_transport initialization process starts by waiting for the client to send a "HEL" message. * * The ServerTCP_transport replies with a "ACK" message and then start waiting for further messages of any size. * * The callback function received an error: * - if no message from the client is received within the ```self.timeout``` period, * - or, if the connection has dropped within the same interval. * - if the protocol version specified within the HEL message is invalid or is greater * than ```self.protocolVersion``` * * */ init(socket, callback) { // c8 ignore next debugLog?.(chalk_1.default.cyan("init socket")); (0, node_opcua_assert_1.assert)(!this._socket, "init already called!"); this._install_socket(socket); this._install_HEL_message_receiver(callback); } _abortWithError(statusCode, extraErrorDescription, callback) { // When a fatal error occurs, the Server shall send an Error Message to the Client and // closes the TransportConnection gracefully. doDebug && debugLog(this.name, chalk_1.default.cyan("_abortWithError", statusCode.toString(), extraErrorDescription)); /* c8 ignore next */ if (this._aborted) { errorLog("Internal Er!ror: _abortWithError already called! Should not happen here"); // already called callback(new Error(statusCode.name)); return; } this._aborted = 1; this._socket?.setTimeout(0); const err = new Error(`${extraErrorDescription} StatusCode = ${statusCode.name}`); this._theCloseError = err; setTimeout(() => { // send the error message and close the connection this.sendErrorMessage(statusCode, statusCode.description); this.prematureTerminate(err, statusCode); this._emitClose(err); callback(err); }, ServerTCP_transport.throttleTime); } _send_ACK_response(helloMessage) { (0, node_opcua_assert_1.assert)(helloMessage.receiveBufferSize >= minimumBufferSize); (0, node_opcua_assert_1.assert)(helloMessage.sendBufferSize >= minimumBufferSize); const limits = this.adjustLimits(helloMessage); this.setLimits(limits); // c8 ignore next if (utils_1.doTraceHelloAck) { warningLog(`received Hello \n${helloMessage.toString()}`); warningLog("Client accepts only message of size => ", this.maxMessageSize); } // c8 ignore next doDebug && debugLog("Client accepts only message of size => ", this.maxMessageSize); const acknowledgeMessage = new AcknowledgeMessage_1.AcknowledgeMessage({ maxChunkCount: this.maxChunkCount, maxMessageSize: this.maxMessageSize, protocolVersion: this.protocolVersion, receiveBufferSize: this.receiveBufferSize, sendBufferSize: this.sendBufferSize }); // c8 ignore next utils_1.doTraceHelloAck && warningLog(`sending Ack \n${acknowledgeMessage.toString()}`); const messageChunk = (0, tools_1.packTcpMessage)("ACK", acknowledgeMessage); /* c8 ignore next*/ if (doDebug) { (0, node_opcua_chunkmanager_1.verify_message_chunk)(messageChunk); debugLog(`server send: ${chalk_1.default.yellow("ACK")}`); debugLog(`server send: ${(0, node_opcua_debug_1.hexDump)(messageChunk)}`); debugLog("acknowledgeMessage=", acknowledgeMessage); } // send the ACK reply this.write(messageChunk); } _install_HEL_message_receiver(callback) { // c8 ignore next doDebug && debugLog(chalk_1.default.cyan("_install_HEL_message_receiver ")); this._install_one_time_message_receiver((err, data) => { if (err) { callback(err); } else { // c8 ignore next if (!data) { callback(new Error("No data received")); return; } // pass to next stage handle the HEL message this._on_HEL_message(data, callback); } }); } _on_HEL_message(data, callback) { // c8 ignore next doDebug && debugLog(chalk_1.default.cyan("_on_HEL_message")); (0, node_opcua_assert_1.assert)(!this._helloReceived); const stream = new node_opcua_binary_stream_1.BinaryStream(data); const msgType = data.subarray(0, 3).toString("utf-8"); /* c8 ignore next*/ if (doDebug) { debugLog(`SERVER received ${chalk_1.default.yellow(msgType)}`); debugLog(`SERVER received ${(0, node_opcua_debug_1.hexDump)(data)}`); } if (msgType === "HEL") { try { (0, node_opcua_assert_1.assert)(data.length >= 24); const decoded = (0, tools_1.decodeMessage)(stream, HelloMessage_1.HelloMessage); if (!(decoded instanceof HelloMessage_1.HelloMessage)) { throw new Error("expecting a HelloMessage"); } const helloMessage = decoded; // OPCUA Spec 1.03 part 6 - page 41 // The Server shall always accept versions greater than what it supports. if (helloMessage.protocolVersion !== this.protocolVersion) { // c8 ignore next doDebug && debugLog(`warning ! client sent helloMessage.protocolVersion = ` + ` 0x${helloMessage.protocolVersion.toString(16)} ` + `whereas server protocolVersion is 0x${this.protocolVersion.toString(16)}`); } if (helloMessage.protocolVersion === 0xdeadbeef || helloMessage.protocolVersion < this.protocolVersion) { // Note: 0xDEADBEEF is our special version number to simulate BadProtocolVersionUnsupported in tests // invalid protocol version requested by client this._abortWithError(node_opcua_status_code_1.StatusCodes.BadProtocolVersionUnsupported, `Protocol Version Error${this.protocolVersion}`, callback); return; } // OPCUA Spec 1.04 part 6 - page 45 // UASC is designed to operate with different TransportProtocols that may have limited buffer // sizes. For this reason, OPC UA Secure Conversation will break OPC UA Messages into several // pieces (called ‘MessageChunks’) that are smaller than the buffer size allowed by the // TransportProtocol. UASC requires a TransportProtocol buffer size that is at least 8 192 bytes if (helloMessage.receiveBufferSize < minimumBufferSize || helloMessage.sendBufferSize < minimumBufferSize) { this._abortWithError(node_opcua_status_code_1.StatusCodes.BadConnectionRejected, `Buffer size too small (should be at least ${minimumBufferSize}`, callback); return; } // the helloMessage shall only be received once. this._helloReceived = true; this._send_ACK_response(helloMessage); callback(); // no Error } catch (err) { // connection rejected because of malformed message this._abortWithError(node_opcua_status_code_1.StatusCodes.BadConnectionRejected, err instanceof Error ? err.message : "", callback); return; } } else { // invalid packet , expecting HEL /* c8 ignore next*/ doDebug && debugLog(`${chalk_1.default.red("BadCommunicationError ")}Expecting 'HEL' message to initiate communication`); this._abortWithError(node_opcua_status_code_1.StatusCodes.BadCommunicationError, "Expecting 'HEL' message to initiate communication", callback); } } } exports.ServerTCP_transport = ServerTCP_transport; //# sourceMappingURL=server_tcp_transport.js.map