UNPKG

node-opcua-transport

Version:

pure nodejs OPCUA SDK - module transport

256 lines (254 loc) 11.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MessageBuilderBase = void 0; exports.readRawMessageHeader = readRawMessageHeader; /** * @module node-opcua-transport */ const node_events_1 = require("node:events"); const node_opcua_assert_1 = require("node-opcua-assert"); const node_opcua_basic_types_1 = require("node-opcua-basic-types"); 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 node_opcua_packet_assembler_1 = require("node-opcua-packet-assembler"); const node_opcua_utils_1 = require("node-opcua-utils"); const status_codes_1 = require("./status_codes"); const doPerfMonitoring = process.env.NODEOPCUADEBUG && process.env.NODEOPCUADEBUG.indexOf("PERF") >= 0; const errorLog = (0, node_opcua_debug_1.make_errorLog)("MessageBuilder"); const _debugLog = (0, node_opcua_debug_1.make_debugLog)("MessageBuilder"); const warningLog = (0, node_opcua_debug_1.make_warningLog)("MessageBuilder"); function readRawMessageHeader(data) { const messageHeader = (0, node_opcua_chunkmanager_1.readMessageHeader)(new node_opcua_binary_stream_1.BinaryStream(data)); return { extra: "", length: messageHeader.length, messageHeader }; } /** * */ class MessageBuilderBase extends node_events_1.EventEmitter { static defaultMaxChunkCount = 1000; static defaultMaxMessageSize = 1024 * 64 * 1024; // 64Mo static defaultMaxChunkSize = 1024 * 8; signatureLength; maxMessageSize; maxChunkCount; maxChunkSize; options; channelId; totalMessageSize; sequenceHeader; _tick0; _tick1; id; totalBodySize; messageChunks; messageHeader; #_packetAssembler; #_securityDefeated; #_hasReceivedError; #blocks; #_expectedChannelId; #offsetBodyStart; constructor(options) { super(); this.id = ""; this._tick0 = 0; this._tick1 = 0; this.#_hasReceivedError = false; this.#blocks = []; this.messageChunks = []; this.#_expectedChannelId = 0; options = options || { maxMessageSize: 0, maxChunkCount: 0, maxChunkSize: 0 }; this.signatureLength = options.signatureLength || 0; this.maxMessageSize = options.maxMessageSize || MessageBuilderBase.defaultMaxMessageSize; this.maxChunkCount = options.maxChunkCount || MessageBuilderBase.defaultMaxChunkCount; this.maxChunkSize = options.maxChunkSize || MessageBuilderBase.defaultMaxChunkSize; this.options = options; this.#_packetAssembler = new node_opcua_packet_assembler_1.PacketAssembler({ minimumSizeInBytes: 8, maxChunkSize: this.maxChunkSize, readChunkFunc: readRawMessageHeader }); this.#_packetAssembler.on("chunk", (messageChunk) => this.#_feed_messageChunk(messageChunk)); this.#_packetAssembler.on("startChunk", (info, data) => { if (doPerfMonitoring) { // record tick 0: when the first data is received this._tick0 = (0, node_opcua_utils_1.get_clock_tick)(); } this.emit("startChunk", info, data); }); this.#_packetAssembler.on("error", (err) => { warningLog("packet assembler ", err.message); this._report_error(status_codes_1.StatusCodes2.BadTcpMessageTooLarge, `packet assembler: ${err.message}`); }); this.#_securityDefeated = false; this.totalBodySize = 0; this.totalMessageSize = 0; this.channelId = 0; this.#offsetBodyStart = 0; this.sequenceHeader = null; this.#_init_new(); } dispose() { this.removeAllListeners(); } /** * Feed message builder with some data * @param data */ feed(data) { if (!this.#_securityDefeated && !this.#_hasReceivedError) { this.#_packetAssembler.feed(data); } } _decodeMessageBody(_fullMessageBody) { return true; } _read_headers(binaryStream) { try { this.messageHeader = (0, node_opcua_chunkmanager_1.readMessageHeader)(binaryStream); // assert(binaryStream.length === 8, "expecting message header to be 8 bytes"); this.channelId = binaryStream.readUInt32(); // assert(binaryStream.length === 12); // verifying secure ChannelId if (this.#_expectedChannelId && this.channelId !== this.#_expectedChannelId) { return this._report_error(status_codes_1.StatusCodes2.BadTcpSecureChannelUnknown, "Invalid secure channel Id"); } return true; } catch (err) { return this._report_error(status_codes_1.StatusCodes2.BadTcpInternalError, `_read_headers error ${err instanceof Error ? err.message : String(err)}`); } } _report_abandon(_channelId, _tokenId, sequenceHeader) { // the server has not been able to send a complete message and has abandoned the request // the connection can probably continue this.#_hasReceivedError = false; /// this.emit("abandon", sequenceHeader.requestId); return false; } _report_error(statusCode, errorMessage) { this.#_hasReceivedError = true; errorLog("Error ", this.id, errorMessage); // xx errorLog(new Error()); this.emit("error", new Error(errorMessage), statusCode, this.sequenceHeader?.requestId || null); return false; } #_init_new() { this.#_securityDefeated = false; this.#_hasReceivedError = false; this.totalBodySize = 0; this.totalMessageSize = 0; this.#blocks = []; this.messageChunks = []; } /** * append a message chunk * @param chunk * @private */ #_append(chunk) { if (this.#_hasReceivedError) { // the message builder is in error mode and further message chunks should be discarded. return false; } if (this.messageChunks.length + 1 > this.maxChunkCount) { return this._report_error(status_codes_1.StatusCodes2.BadTcpMessageTooLarge, `max chunk count exceeded: ${this.maxChunkCount}`); } this.messageChunks.push(chunk); this.totalMessageSize += chunk.length; if (this.totalMessageSize > this.maxMessageSize) { return this._report_error(status_codes_1.StatusCodes2.BadTcpMessageTooLarge, `max message size exceeded: ${this.maxMessageSize} : total message size ${this.totalMessageSize}`); } const binaryStream = new node_opcua_binary_stream_1.BinaryStream(chunk); if (!this._read_headers(binaryStream)) { return false; // error already reported } (0, node_opcua_assert_1.assert)(binaryStream.length >= 12); // verify message chunk length if (this.messageHeader?.length !== chunk.length) { // tslint:disable:max-line-length return this._report_error(status_codes_1.StatusCodes2.BadTcpInternalError, `Invalid messageChunk size: the provided chunk is ${chunk.length} bytes long but header specifies ${this.messageHeader?.length}`); } // the start of the message body block const offsetBodyStart = binaryStream.length; // the end of the message body block const offsetBodyEnd = binaryStream.buffer.length; this.totalBodySize += offsetBodyEnd - offsetBodyStart; this.#offsetBodyStart = offsetBodyStart; // add message body to a queue // We use subarray here to avoid copy. // This assumes PacketAssembler manages the buffer lifecycle appropriately. const sharedBuffer = chunk.subarray(this.#offsetBodyStart, offsetBodyEnd); this.#blocks.push(sharedBuffer); return true; } #_feed_messageChunk(chunk) { (0, node_opcua_assert_1.assert)(chunk); const messageHeader = (0, node_opcua_chunkmanager_1.readMessageHeader)(new node_opcua_binary_stream_1.BinaryStream(chunk)); this.emit("chunk", chunk); if (messageHeader.isFinal === "F") { if (messageHeader.msgType === "ERR") { const binaryStream = new node_opcua_binary_stream_1.BinaryStream(chunk); binaryStream.length = 8; const errorCode = (0, node_opcua_basic_types_1.decodeStatusCode)(binaryStream); const message = (0, node_opcua_basic_types_1.decodeString)(binaryStream); this._report_error(errorCode, message || "Error message not specified"); return true; } else { this.#_append(chunk); // last message if (this.#_hasReceivedError) { return false; } const fullMessageBody = this.#blocks.length === 1 ? this.#blocks[0] : Buffer.concat(this.#blocks); if (doPerfMonitoring) { // record tick 1: when a complete message has been received ( all chunks assembled) this._tick1 = (0, node_opcua_utils_1.get_clock_tick)(); } this.emit("full_message_body", fullMessageBody); const messageOk = this._decodeMessageBody(fullMessageBody); // be ready for next block this.#_init_new(); return messageOk; } } else if (messageHeader.isFinal === "A") { try { // only valid for MSG, according to spec const stream = new node_opcua_binary_stream_1.BinaryStream(chunk); (0, node_opcua_chunkmanager_1.readMessageHeader)(stream); (0, node_opcua_assert_1.assert)(stream.length === 8); // instead of // const securityHeader = new SymmetricAlgorithmSecurityHeader(); // securityHeader.decode(stream); const channelId = stream.readUInt32(); const tokenId = (0, node_opcua_basic_types_1.decodeUInt32)(stream); const sequenceHeader = new node_opcua_chunkmanager_1.SequenceHeader(); sequenceHeader.decode(stream); return this._report_abandon(channelId, tokenId, sequenceHeader); } catch (err) { const errMessage = err instanceof Error ? err.message : String(err); warningLog((0, node_opcua_debug_1.hexDump)(chunk)); warningLog("Cannot interpret message chunk: ", errMessage); return this._report_error(status_codes_1.StatusCodes2.BadTcpInternalError, `Error decoding message header ${errMessage}`); } } else if (messageHeader.isFinal === "C") { return this.#_append(chunk); } return false; } } exports.MessageBuilderBase = MessageBuilderBase; //# sourceMappingURL=message_builder_base.js.map