APIs

Show:
"use strict";
/**
 * @module opcua.miscellaneous
 */

const util = require("util");
const assert = require("node-opcua-assert").assert;
const _ = require("underscore");
const EventEmitter = require("events").EventEmitter;

const ChunkManager = require("node-opcua-chunkmanager").ChunkManager;
const BinaryStream = require("node-opcua-binary-stream").BinaryStream;

const AsymmetricAlgorithmSecurityHeader = require("node-opcua-service-secure-channel")
    .AsymmetricAlgorithmSecurityHeader;
const SymmetricAlgorithmSecurityHeader = require("node-opcua-service-secure-channel").SymmetricAlgorithmSecurityHeader;
const SequenceHeader = require("node-opcua-service-secure-channel").SequenceHeader;

function chooseSecurityHeader(msgType) {
    const securityHeader =
        msgType === "OPN" ? new AsymmetricAlgorithmSecurityHeader() : new SymmetricAlgorithmSecurityHeader();
    return securityHeader;
}

exports.chooseSecurityHeader = chooseSecurityHeader;

/**
 * @class SecureMessageChunkManager
 *
 * @param msgType
 * @param options
 * @param options.chunkSize {Integer} [=8192]
 * @param options.secureChannelId
 * @param options.requestId
 * @param options.signatureLength  {number}  [undefined]
 * @param options.signingFunc {Function} [undefined]
 *
 * @param securityHeader
 * @param sequenceNumberGenerator
 * @constructor
 */
const SecureMessageChunkManager = function(msgType, options, securityHeader, sequenceNumberGenerator) {
    const self = this;
    self.aborted = false;

    msgType = msgType || "OPN";

    securityHeader = securityHeader || chooseSecurityHeader(msgType);
    assert(_.isObject(securityHeader));

    // the maximum size of a message chunk:
    // Note: OPCUA requires that chunkSize is at least 8192
    self.chunkSize = options.chunkSize || 1024 * 128;

    self.msgType = msgType;

    options.secureChannelId = options.secureChannelId || 0;
    assert(_.isFinite(options.secureChannelId));
    self.secureChannelId = options.secureChannelId;

    const requestId = options.requestId;

    self.sequenceNumberGenerator = sequenceNumberGenerator;

    self.securityHeader = securityHeader;

    assert(requestId > 0, "expecting a valid request ID");

    self.sequenceHeader = new SequenceHeader({ requestId: requestId, sequenceNumber: -1 });

    const securityHeaderSize = self.securityHeader.binaryStoreSize();
    const sequenceHeaderSize = self.sequenceHeader.binaryStoreSize();
    assert(sequenceHeaderSize === 8);

    self.headerSize = 12 + securityHeaderSize;

    const params = {
        chunkSize: self.chunkSize,

        headerSize: self.headerSize,
        writeHeaderFunc: function(block, isLast, totalLength) {
            let finalC = isLast ? "F" : "C";
            finalC = this.aborted ? "A" : finalC;
            self.write_header(finalC, block, totalLength);
        },

        sequenceHeaderSize: options.sequenceHeaderSize,
        writeSequenceHeaderFunc: function(block) {
            assert(block.length === this.sequenceHeaderSize);
            self.writeSequenceHeader(block);
        },

        // ---------------------------------------- Signing stuff
        signatureLength: options.signatureLength,
        compute_signature: options.signingFunc,

        // ---------------------------------------- Encrypting stuff
        plainBlockSize: options.plainBlockSize,
        cipherBlockSize: options.cipherBlockSize,
        encrypt_buffer: options.encrypt_buffer
    };

    self.chunkManager = new ChunkManager(params);

    self.chunkManager.on("chunk", function(chunk, is_last) {
        /**
         * @event chunk
         * @param chunk {Buffer}
         */
        self.emit("chunk", chunk, is_last || self.aborted);
    });
};
util.inherits(SecureMessageChunkManager, EventEmitter);

SecureMessageChunkManager.prototype.write_header = function(finalC, buf, length) {
    assert(buf.length > 12);
    assert(finalC.length === 1);
    assert(buf instanceof Buffer);

    const bs = new BinaryStream(buf);

    // message header --------------------------
    const self = this;
    // ---------------------------------------------------------------
    // OPC UA Secure Conversation Message Header : Part 6 page 36
    // MessageType     Byte[3]
    // IsFinal         Byte[1]  C : intermediate, F: Final , A: Final with Error
    // MessageSize     UInt32   The length of the MessageChunk, in bytes. This value includes size of the message header.
    // SecureChannelId UInt32   A unique identifier for the ClientSecureChannelLayer assigned by the server.

    bs.writeUInt8(self.msgType.charCodeAt(0));
    bs.writeUInt8(self.msgType.charCodeAt(1));
    bs.writeUInt8(self.msgType.charCodeAt(2));
    bs.writeUInt8(finalC.charCodeAt(0));

    bs.writeUInt32(length);
    bs.writeUInt32(self.secureChannelId);

    assert(bs.length === 12);

    //xx console.log("securityHeader size = ",this.securityHeader.binaryStoreSize());
    // write Security Header -----------------
    this.securityHeader.encode(bs);
    assert(bs.length === this.headerSize);
};

SecureMessageChunkManager.prototype.writeSequenceHeader = function(block) {
    const bs = new BinaryStream(block);
    // write Sequence Header -----------------
    this.sequenceHeader.sequenceNumber = this.sequenceNumberGenerator.next();
    this.sequenceHeader.encode(bs);
    assert(bs.length === 8);
};

/**
 * @method write
 * @param buffer {Buffer}
 * @param length {Integer} - optional if not provided  buffer.length is used instead.
 */
SecureMessageChunkManager.prototype.write = function(buffer, length) {
    length = length || buffer.length;
    this.chunkManager.write(buffer, length);
};

/**
 * @method abort
 *
 */
SecureMessageChunkManager.prototype.abort = function() {
    this.aborted = true;
    this.end();
};

/**
 * @method end
 */
SecureMessageChunkManager.prototype.end = function() {
    this.chunkManager.end();
    this.emit("finished");
};

exports.SecureMessageChunkManager = SecureMessageChunkManager;