APIs

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


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

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

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


function chooseSecurityHeader(msgType) {

    var securityHeader = (msgType === "OPN") ?
        new AsymmetricAlgorithmSecurityHeader() :
        new SymmetricAlgorithmSecurityHeader();
    return securityHeader;
}

exports.chooseSecurityHeader = chooseSecurityHeader;


/**
 * @class SecureMessageChunkManager
 *
 * @param msgType
 * @param options
 * @param options.chunkSize {Integer} [=8196]
 * @param options.secureChannelId
 * @param options.requestId
 * @param options.signatureLength  {Integer}  [undefined]
 * @param options.signingFunc {Function} [undefined]
 *
 * @param securityHeader
 * @param sequenceNumberGenerator
 * @constructor
 */
var SecureMessageChunkManager = function (msgType, options, securityHeader, sequenceNumberGenerator) {

    var 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 8196
    self.chunkSize = options.chunkSize || 1024*128;

    self.msgType = msgType;

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

    var 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});

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

    self.headerSize = 12 + securityHeaderSize;

    var params = {
        chunkSize: self.chunkSize,

        headerSize: self.headerSize,
        writeHeaderFunc: function (block, isLast, totalLength) {

            var 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);

    var bs = new BinaryStream(buf);

    // message header --------------------------
    var 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) {
    var 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;