APIs

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

var assert = require("node-opcua-assert");
var util = require("util");

var buffer_utils = require("node-opcua-buffer-utils");
var createFastUninitializedBuffer = buffer_utils.createFastUninitializedBuffer;

var noAssert = false;
var performCheck = false;
/**
 * a BinaryStream can be use to perform sequential read or write
 * inside a buffer.
 * The BinaryStream maintains a cursor up to date as the caller
 * operates on the stream using the various read/write methods.
 * It uses the [Little Endian](http://en.wikipedia.org/wiki/Little_endian#Little-endian)
 * convention.
 *
 * data can either be:
 *
 * * a Buffer , in this case the BinaryStream operates on this Buffer
 * * null     , in this case a BinaryStream with 1024 bytes is created
 * * any data , in this case the object is converted into a binary buffer.
 *
 * example:
 *
 *    ``` javascript
 *    var stream = new BinaryStream(32)
 *    ```
 *
 * @class BinaryStream
 * @param {null|Buffer|number} data
 * @constructor
 *
 *
 *
 */
function BinaryStream(data) {

    if (data === undefined) {
        this._buffer = createFastUninitializedBuffer(1024);
    } else if (typeof data === "number") {
        this._buffer = createFastUninitializedBuffer(data);// new Buffer(/*size=*/data);
    } else {
        assert(data instanceof Buffer);
        this._buffer = data;
    }
    this.length = 0;
}

/**
 * set the cursor to the begining of the stream
 * @method BinaryStream.rewind
 * @return null
 */
BinaryStream.prototype.rewind = function () {
    this.length = 0;
};

/**
 * write a single signed byte (8 bits) to the stream.
 * value must be in the range of [-127,128]
 * @method writeInt8
 * @param {Number} value
 */
BinaryStream.prototype.writeInt8 = function (value) {
    !performCheck || assert(this._buffer.length >= this.length + 1 , "not enough space in buffer");
    !performCheck || assert(value >= -128 && value < 128);
    this._buffer.writeInt8(value, this.length, noAssert);
    this.length += 1;
};

/**
 * write a single unsigned byte (8 bits) to the stream.
 * @method writeUInt8
 * @param {Number} value
 */
BinaryStream.prototype.writeUInt8 = function (value) {
    !performCheck || assert(this._buffer.length >= this.length + 1 , "not enough space in buffer");
    !performCheck || assert(value >= 0 && value < 256 && " writeUInt8 : out of bound ");
    this._buffer.writeUInt8(value, this.length, noAssert);
    this.length += 1;
};

/**
 * write a single 16 bit signed integer to the stream.
 * @method writeInt16
 * @param {Number} value
 */
BinaryStream.prototype.writeInt16 = function (value) {
    !performCheck || assert(this._buffer.length >= this.length + 2 , "not enough space in buffer");
    this._buffer.writeInt16LE(value, this.length, noAssert);
    this.length += 2;
};

/**
 * write a single 16 bit unsigned integer to the stream.
 * @method writeUInt16
 * @param {Number} value
 */
BinaryStream.prototype.writeUInt16 = function (value) {
    !performCheck || assert(this._buffer.length >= this.length + 2 , "not enough space in buffer");
    this._buffer.writeUInt16LE(value, this.length, noAssert);
    this.length += 2;
};


/**
 * write a single 32 bit signed integer to the stream.
 * @method writeInteger
 * @param {Number} value
 */
BinaryStream.prototype.writeInteger = function (value) {
    !performCheck || assert(this._buffer.length >= this.length + 4 , "not enough space in buffer");
    this._buffer.writeInt32LE(value, this.length, noAssert);
    this.length += 4;
};

const _ = require("underscore");
const MAXUINT32 = 4294967295 ; // 2**32 -1;
/**
 * write a single 32 bit unsigned integer to the stream.
 * @method writeUInt32
 * @param {Number} value
 */
BinaryStream.prototype.writeUInt32 = function (value) {
    !performCheck || assert(this._buffer.length >= this.length + 4 , "not enough space in buffer");
    !performCheck || assert(_.isFinite(value));
    !performCheck || assert(value >= 0  && value <= MAXUINT32);
    this._buffer.writeUInt32LE(value, this.length, noAssert);
    this.length += 4;
    /*
      assert(this._buffer[this.length - 4] === value % 256);
      assert(this._buffer[this.length - 3] === (value >>> 8) % 256);
      assert(this._buffer[this.length - 2] === (value >>> 16) % 256);
      assert(this._buffer[this.length - 1] === (value >>> 24) % 256);
      */
};

/**
 * write a single 32 bit floating number to the stream.
 * @method writeFloat
 * @param {Number} value
 */
BinaryStream.prototype.writeFloat = function (value) {
    !performCheck || assert(this._buffer.length >= this.length + 4 , "not enough space in buffer");
    this._buffer.writeFloatLE(value, this.length, noAssert);
    this.length += 4;
};

/**
 * write a single 64 bit floating number to the stream.
 * @method writeDouble
 * @param value
 */
BinaryStream.prototype.writeDouble = function (value) {
    !performCheck || assert(this._buffer.length >= this.length + 8 , "not enough space in buffer");
    this._buffer.writeDoubleLE(value, this.length, noAssert);
    this.length += 8;
};


/**
 * @method writeArrayBuffer
 * @param arrayBuf {ArrayBuffer}
 * @param offset   {Number}
 * @param length   {Number}
 */
BinaryStream.prototype.writeArrayBuffer = function (arrayBuf, offset, length) {

    offset = offset || 0;
    !performCheck || assert(arrayBuf instanceof ArrayBuffer);
    var byteArr = new Uint8Array(arrayBuf);
    var n = (length || byteArr.length) + offset;
    for (var i = offset; i < n; i++) {
        this._buffer[this.length++] = byteArr[i];
    }
};

/**
 * @method readArrayBuffer
 * @param length
 * @returns {Uint8Array}
 */
BinaryStream.prototype.readArrayBuffer = function (length) {
    !performCheck || assert(this.length + length <= this._buffer.length, "not enough bytes in buffer");
    var slice = this._buffer.slice(this.length, this.length + length);
    !performCheck || assert(slice.length === length);
    var byteArr = new Uint8Array(slice);
    !performCheck || assert(byteArr.length === length);
    this.length += length;
    return byteArr;
}


var displayWarnings = false;
require("colors");

function display_memcpy_missing_message() {
    console.warn("\n Warning : the memcpy package is not installed on your system ".yellow);
    console.warn("\n           memcpy could allow you to get better encoding/decoding performance on typed array ".yellow);
    console.warn("             you can install it memcpy using this command ( extra c++ compilation tools may be required on your system)");
    console.warn("              $ npm install  memcpy".yellow.bold);
    console.warn("");
}

// note :
// nodejs doesn't provide  efficient api to mix& match ArrayBuffer and Buffer
//
// consider using https://github.com/dcodeIO/node-memcpy
try {
    var my_memcpy = function my_memcpy(target, targetStart, source, sourceStart, sourceEnd) {
        //xx assert(target instanceof Buffer || target instanceof Uint8Array);
        //xx assert(source instanceof Buffer || source instanceof Uint8Array);
        var l = targetStart;
        for (var i = sourceStart; i < sourceEnd; i++) {
            target[l++] = source[i];
        }
        return sourceEnd - sourceStart;
    };

    var memcpy = require("memcpy");      // C++ binding if available, else native JS
    my_memcpy = memcpy;
    console.log("Warning : using memcpy : OK".yellow);

    BinaryStream.prototype.writeArrayBuffer = function (arrayBuf, offset, length) {

        offset = offset || 0;

        assert(arrayBuf instanceof ArrayBuffer);
        var byteArr = new Uint8Array(arrayBuf);
        length = length || byteArr.length;
        if (length === 0) {
            return;
        }
        this.length += my_memcpy(this._buffer, this.length, byteArr, offset, offset + length);
    };

    BinaryStream.prototype.readArrayBuffer = function (length) {
        assert(this.length + length <= this._buffer.length, "not enough bytes in buffer");
        var byteArr = new Uint8Array(new ArrayBuffer(length));
        my_memcpy(byteArr, 0, this._buffer, this.length, this.length + length);
        this.length += length;
        return byteArr;
    };

}
catch (err) {
    if (displayWarnings) {
        display_memcpy_missing_message();
    }
}

/**
 * read a single signed byte  (8 bits) from the stream.
 * @method readByte
 * @return {Number}
 */
BinaryStream.prototype.readByte = function () {
    var retVal = this._buffer.readInt8(this.length,noAssert);
    this.length += 1;
    return retVal;
};
BinaryStream.prototype.readInt8 = BinaryStream.prototype.readByte;
/**
 * read a single unsigned byte (8 bits) from the stream.
 * @method readUInt8
 * @return {Number}
 */
BinaryStream.prototype.readUInt8 = function () {
    !performCheck || assert (this._buffer.length >= this.length+1);
    var retVal = this._buffer.readUInt8(this.length,noAssert);
    this.length += 1;
    return retVal;
};

/**
 * read a single signed 16-bit integer from the stream.
 * @method readInt16
 * @return {Number}
 */
BinaryStream.prototype.readInt16 = function () {
    var retVal = this._buffer.readInt16LE(this.length,noAssert);
    this.length += 2;
    return retVal;
};

/**
 * read a single unsigned 16-bit integer from the stream.
 * @method readUInt16
 * @return {Number}  q
 */
BinaryStream.prototype.readUInt16 = function () {
    var retVal = this._buffer.readUInt16LE(this.length,noAssert);
    this.length += 2;
    return retVal;
};

/**
 * read a single signed 32-bit integer from the stream.
 * @method readInteger
 * @return {Number}
 */
BinaryStream.prototype.readInteger = function () {
    var retVal = this._buffer.readInt32LE(this.length,noAssert);
    this.length += 4;
    return retVal;
};

/**
 * read a single unsigned 32-bit integer from the stream.
 * @method readUInt32
 * @return {Number} the value read from the stream
 */
BinaryStream.prototype.readUInt32 = function () {
    var retVal = this._buffer.readUInt32LE(this.length,noAssert);
    this.length += 4;
    return retVal;
};

/**
 * read a single  32-bit floating point number from the stream.
 * @method readFloat
 * @return {Number} the value read from the stream
 */
BinaryStream.prototype.readFloat = function () {
    var retVal = this._buffer.readFloatLE(this.length,noAssert);
    this.length += 4;
    return retVal;
};

/**
 * read a single 64-bit floating point number from the stream.
 * @method readDouble
 * @return {Number} the value read from the stream
 */
BinaryStream.prototype.readDouble = function () {
    var retVal = this._buffer.readDoubleLE(this.length,noAssert);
    this.length += 8;
    return retVal;
};

/**
 * write a byte stream to the stream.
 * The method writes the length of the byte array into the stream as a 32 bits integer before the byte stream.
 *
 * @method writeByteStream
 * @param {Buffer} buf the    buffer to write.
 *   the buffer buf.length the buffer to write
 */
BinaryStream.prototype.writeByteStream = function (buf) {

    if (!buf) {
        this.writeInteger(-1);
        return;
    }
    assert(buf instanceof Buffer);
    this.writeInteger(buf.length);
    // make sure there is enough room in destination buffer
    var remaining_bytes = this._buffer.length - this.length;

    /* istanbul ignore next */
    if (remaining_bytes < buf.length) {
        throw new Error("BinaryStream.writeByteStream error : not enough bytes left in buffer :  bufferLength is " + buf.length + " but only " + remaining_bytes + " left");
    }
    buf.copy(this._buffer, this.length, 0, buf.length);
    this.length += buf.length;
};


/**
 * @function calculateByteLength
 * calculate the size in bytes of a utf8 string
 * @param str {String}
 */
function calculateByteLength(str) {
    // returns the byte length of an utf8 string
    var s = str.length;
    for (var i = str.length - 1; i >= 0; i--) {
        var code = str.charCodeAt(i);
        if (code > 0x7f && code <= 0x7ff) {
            s++;
        }
        else if (code > 0x7ff && code <= 0xffff) {
            s += 2;
        }
        if (code >= 0xDC00 && code <= 0xDFFF) {
            //trail surrogate
            i--;
        }
    }
    return s;
}

BinaryStream.prototype.writeString = function (value) {
    if (value === undefined || value === null) {
        this.writeInteger(-1);
        return;
    }
    var byteLength = calculateByteLength(value);
    this.writeInteger(byteLength);
    // make sure there is enough room in destination buffer
    var remaining_bytes = this._buffer.length - this.length;
    /* istanbul ignore next */
    if (remaining_bytes < byteLength) {
        throw new Error("BinaryStream.writeByteStream error : not enough bytes left in buffer :  bufferLength is " +byteLength+ " but only " + remaining_bytes + " left");
    }
    if (byteLength>0) {
        this._buffer.write(value,this.length,"utf-8");
        this.length += byteLength;
    }
};


var zeroLengthBuffer = createFastUninitializedBuffer(0);
/**
 * read a byte stream to the stream.
 * The method reads the length of the byte array from the stream as a 32 bits integer before reading the byte stream.
 *
 * @method readByteStream
 * @return {Buffer}
 */
BinaryStream.prototype.readByteStream = function () {
    var bufLen = this.readUInt32();
    if (bufLen === 0xFFFFFFFF) {
        return null;
    }
    if (bufLen === 0) {
        return zeroLengthBuffer;
    }
    // check that there is enough space in the buffer
    var remaining_bytes = this._buffer.length - this.length;
    if (remaining_bytes < bufLen) {
        throw new Error("BinaryStream.readByteStream error : not enough bytes left in buffer :  bufferLength is " + bufLen + " but only " + remaining_bytes + " left");
    }
    //create a shared memory buffer ! for speed
    var buf = this._buffer.slice(this.length, this.length + bufLen);
    this.length += bufLen;
    return buf;
};

BinaryStream.prototype.readString = function () {

    var bufLen = this.readUInt32();
    if (bufLen === 0xFFFFFFFF) {
        return null;
    }
    if (bufLen === 0) {
        return "";
    }
    // check that there is enough space in the buffer
    var remaining_bytes = this._buffer.length - this.length;
    if (remaining_bytes < bufLen) {
        throw new Error("BinaryStream.readByteStream error : not enough bytes left in buffer :  bufferLength is " + bufLen + " but only " + remaining_bytes + " left");
    }

    var str = this._buffer.toString("utf-8", this.length , this.length +bufLen);
    this.length += bufLen;
    return str;
};
exports.BinaryStream = BinaryStream;


/**
 * a BinaryStreamSizeCalculator can be used to quickly evaluate the required size
 * of a buffer by performing the same sequence of write operation.
 *
 * a BinaryStreamSizeCalculator has the same writeXXX methods as the BinaryStream stream
 * object.
 *
 * @class BinaryStreamSizeCalculator
 * @extends BinaryStream
 * @constructor
 *
 */
function BinaryStreamSizeCalculator() {
    this.length = 0;
}

BinaryStreamSizeCalculator.prototype.rewind = function () {
    this.length = 0;
};

BinaryStreamSizeCalculator.prototype.writeInt8 = function (value) {
    this.length += 1;
};

BinaryStreamSizeCalculator.prototype.writeUInt8 = function (value) {
    this.length += 1;
};

BinaryStreamSizeCalculator.prototype.writeInt16 = function (value) {
    this.length += 2;
};

BinaryStreamSizeCalculator.prototype.writeInteger = function (value) {
    this.length += 4;
};

BinaryStreamSizeCalculator.prototype.writeUInt32 = function (value) {
    this.length += 4;
};

BinaryStreamSizeCalculator.prototype.writeUInt16 = function (value) {
    this.length += 2;
};

BinaryStreamSizeCalculator.prototype.writeFloat = function (value) {
    this.length += 4;
};

BinaryStreamSizeCalculator.prototype.writeDouble = function (value) {
    this.length += 8;
};

BinaryStreamSizeCalculator.prototype.writeArrayBuffer = function (arrayBuf, offset, byteLength) {
    offset = offset || 0;
    assert(arrayBuf instanceof ArrayBuffer);
    this.length += (byteLength || arrayBuf.byteLength);
};

BinaryStreamSizeCalculator.prototype.writeByteStream = function (buf) {
    if (!buf) {
        this.writeUInt32(0);
    } else {
        this.writeUInt32(buf.length);
        this.length += buf.length;
    }
};
BinaryStreamSizeCalculator.prototype.writeString = function (string) {

    if (string === undefined || string === null) {
        this.writeUInt32(-1);
        return;
    }
    var bufLength =  calculateByteLength(string);
    this.writeUInt32(bufLength);
    this.length += bufLength;
};

exports.BinaryStreamSizeCalculator = BinaryStreamSizeCalculator;