enricostara/telegram-tl-node

View on GitHub
lib/type-object.js

Summary

Maintainability
D
2 days
Test Coverage
//     telegram-tl-node
//     Copyright 2014 Enrico Stara 'enrico.stara@gmail.com'
//     Released under the MIT License
//     https://github.com/enricostara/telegram-tl-node

//     TypeObject class
//
// The `TypeObject` class is the implementation of the `TypeLanguage Object`, it provides
// read/write methods to serialize/de-serialize in the TypeLanguage binary format

/*jshint bitwise:false*/

// Import dependencies
var utility = require('./utility');

// The constructor may be called giving a `Buffer` with the binary image - eventually starting from an `offset` -
// in order to de-serialize a `TypeLanguage entity` via `read*` methods,
// otherwise you can call it without any argument and start serializing a new one via `write*` methods
function TypeObject(buffer, offset, noId) {
    if (buffer) {
        this._writeBuffers = null;
        this._readOffset = 0;
        buffer = Buffer.isBuffer(buffer) ? buffer : new Buffer(buffer);
        this._buffer = offset ? buffer.slice(offset) : buffer;
        this._noId = noId;
    } else {
        this._writeBuffers = [];
        this._writeOffset = 0;
    }
}

// The base method to de-serialize the object
TypeObject.prototype.deserialize = function (options) {
    var logger = this.constructor.logger;
    var isBare = options && options.isBare;
    if (!this.isReadonly()) {
        var msg = 'Unable to de-serialize, the buffer is undefined';
        logger.error(msg);
        throw new Error(msg);
    }
    if (!this._noId && !isBare) {
        var id = this._readBytes(4).toString('hex');
        if (logger && logger.isDebugEnabled()) {
            logger.debug('read ID = %s', id);
        }
        if (this._typeId !== id) {
            var msg = 'Unable to de-serialize, (read id) ' + id + ' != (this._typeId) ' + this._typeId;
            logger.error(msg);
            throw new Error(msg);
        }
    }
    return this;
};

// The base method to serialize the object
TypeObject.prototype.serialize = function (options) {
    var logger = this.constructor.logger;
    var isBare = options && options.isBare;
    if (this._typeId && !isBare) {
        if (logger && logger.isDebugEnabled()) {
            logger.debug('write ID = %s', this._typeId);
        }
        return this._writeBytes(new Buffer(this._typeId, 'hex'));
    }
    return true;
};

// Finalize the serialization process and retrieve the `Buffer` image of the object,
// putting the instance in `readonly` state
TypeObject.prototype.retrieveBuffer = function () {
    if (!this._buffer) {
        this._buffer = Buffer.concat(this._writeBuffers);
    }
    this._writeBuffers = null;
    this._writeOffset = 0;
    this._readOffset = 0;
    return this._buffer;
};

TypeObject.prototype._addWriteBuffer = function (buffer) {
    this._writeBuffers.push(buffer);
    this._writeOffset += buffer.length;
    var logger = this.constructor.logger;
    if (logger && logger.isDebugEnabled()) {
        logger.debug('Write offset %s', this._writeOffset);
    }
};

// Write the `int` value given as argument
TypeObject.prototype.writeInt = function (intValue) {
    if (this.isReadonly()) {
        return false;
    }
    var buffer = new Buffer(4);
    buffer.writeUInt32LE(intValue, 0, true);
    var logger = this.constructor.logger;
    if (logger && logger.isDebugEnabled()) {
        logger.debug('intValue %s, intBuffer %s', intValue, buffer.toString('hex'));
    }
    this._addWriteBuffer(buffer);
    return true;
};

// Write the `double` value given as argument
TypeObject.prototype.writeDouble = function (doubleValue) {
    if (this.isReadonly()) {
        return false;
    }
    var buffer = new Buffer(8);
    buffer.writeDoubleLE(doubleValue, 0, true);
    var logger = this.constructor.logger;
    if (logger && logger.isDebugEnabled()) {
        logger.debug('doubleValue %s, doubleBuffer %s', doubleValue, buffer.toString('hex'));
    }
    this._addWriteBuffer(buffer);
    return true;
};

// Write the `BigInteger` value given as argument, you have to provide the value as `String` type
// and specify a `byte length`, where `length % 4 == 0`
TypeObject.prototype._writeBigInt = function (bigInteger, byteLength) {
    if (this.isReadonly() || (byteLength % 4) !== 0) {
        return false;
    }
    this._addWriteBuffer(utility.stringValue2Buffer('' + bigInteger, byteLength));
    return true;
};

// Write the `byte[]` value given as argument,
// adding the bytes length at the beginning
// and adding padding at the end if needed
TypeObject.prototype.writeBytes = function (bytes, useWordLength) {
    if (this.isReadonly()) {
        return false;
    }
    var bLength = useWordLength ? bytes.length / 4 : bytes.length;
    var isShort = bLength < (useWordLength ? 0x7F : 0xFE);
    var buffer = new Buffer(isShort ? 1 : 4);
    var offset = 0;
    if (isShort) {
        buffer.writeUInt8(bLength, offset++);
    } else {
        buffer.writeUInt8((useWordLength ? 0x7F : 0xFE), offset++);
        buffer.writeUInt8(bLength & 0xFF, offset++);
        buffer.writeUInt8((bLength >> 8) & 0xFF, offset++);
        buffer.writeUInt8((bLength >> 16) & 0xFF, offset++);
    }
    this._addWriteBuffer(buffer);
    this._writeBytes(bytes);
    // add padding if needed
    if (!useWordLength) {
        var padding = (offset + bytes.length) % 4;
        if (padding > 0) {
            buffer = new Buffer(4 - padding);
            buffer.fill(0);
            this._addWriteBuffer(buffer);
        }
    }
    return true;
};

// Write the `string` value given as argument
TypeObject.prototype.writeString = function (str) {
    return this.writeBytes(str);
};

// Write the `byte[]` value given as argument
TypeObject.prototype._writeBytes = function (bytes) {
    if (this.isReadonly()) {
        return false;
    }
    var buffer = !Buffer.isBuffer(bytes) ? new Buffer(bytes) : bytes;
    this._addWriteBuffer(buffer);
    return true;
};

// Write the `long` value given as argument
TypeObject.prototype.writeLong = function (bigInteger) {
    return this._writeIntN(bigInteger, 8);
};

// Write the `int128` value given as argument
TypeObject.prototype.writeInt128 = function (bigInteger) {
    return this._writeIntN(bigInteger, 16);
};

// Write the `int256` value given as argument
TypeObject.prototype.writeInt256 = function (bigInteger) {
    return this._writeIntN(bigInteger, 32);
};

TypeObject.prototype._writeIntN = function (bigInteger, byteLength) {
    return (typeof bigInteger === 'string' || typeof bigInteger === 'number') ? this._writeBigInt(bigInteger, byteLength) :
        this._writeBytes(bigInteger);
};

// Read an `int` value starting from the current position
TypeObject.prototype.readInt = function () {
    if (!this.isReadonly() || (this._readOffset + 4) > this._buffer.length) {
        return undefined;
    }
    var intValue = this._buffer.readUInt32LE(this._readOffset);
    // Reading position will be increased of 4
    this._readOffset += 4;
    return intValue;
};

// Read a `double` value starting from the current position
TypeObject.prototype.readDouble = function () {
    if (!this.isReadonly() || (this._readOffset + 8) > this._buffer.length) {
        return undefined;
    }
    var doubleValue = this._buffer.readDoubleLE(this._readOffset);
    // Reading position will be increased of 8
    this._readOffset += 8;
    return doubleValue;
};

// Read a `byte[]` value starting from the current position, using the first byte(s) to get the length
TypeObject.prototype.readBytes = function (useWordLength) {
    var start = this._readOffset;
    var bLength = this._buffer.readUInt8(this._readOffset++);
    var logger = this.constructor.logger;
    var isShort = bLength < (useWordLength ? 0x7F : 0xFE);
    if (!isShort) {
        bLength = this._buffer.readUInt8(this._readOffset++) +
            (this._buffer.readUInt8(this._readOffset++) << 8) +
            (this._buffer.readUInt8(this._readOffset++) << 16);
    }
    if (logger && logger.isDebugEnabled()) {
        logger.debug('bufferLength = %s', bLength);
    }
    var buffer = (bLength === 0) ?
        new Buffer(0) :
        this._readBytes(useWordLength ? bLength * 4 : bLength);
    // consider padding if needed
    var padding = (this._readOffset - start) % 4;
    if (padding > 0) {
        this._readOffset += 4 - padding;
    }
    return buffer;
};

// Read a `string` value starting from the current position
TypeObject.prototype.readString = function () {
    return this.readBytes().toString('utf8');
};

// Read a `byte[]` value starting from the current position
TypeObject.prototype._readBytes = function (byteLength) {
    var end = byteLength ? this._readOffset + byteLength : this._buffer.length;
    if (!this.isReadonly() ) {
        throw new TypeError('Try to read a not readonly object');
    }
    if(end > this._buffer.length) {
        this.constructor.logger.info('end > this._buffer.length', end, this._buffer.length);
        end = this._buffer.length;
    }
    var buffer = this._buffer.slice(this._readOffset, end);
    this._readOffset = end;
    return buffer;
};

// Read a `BigInteger` value with a given byte length starting from the current position
TypeObject.prototype._readBigInt = function (byteLength) {
    var buffer = this._readBytes(byteLength);
    return buffer ? utility.buffer2StringValue(buffer) : undefined;
};

// Read a `long` value with  starting from the current position
TypeObject.prototype.readLong = function () {
    return this._readBigInt(8);
};

// Read a `int128` value with  starting from the current position
TypeObject.prototype.readInt128 = function () {
    return this._readBigInt(16);
};

// Read a `int256` value with  starting from the current position
TypeObject.prototype.readInt256 = function () {
    return this._readBigInt(32);
};

// Check if the object has been already serialized and then it's `readonly`
TypeObject.prototype.isReadonly = function () {
    if (this._buffer) {
        return true;
    }
    return false;
};

// Retrieve the current read position
TypeObject.prototype.getReadOffset = function () {
    return this._readOffset;
};

//Get the type name
TypeObject.prototype.getTypeName = function () {
    return this._typeName;
};

// Check if it is instance of the type identified by the full-name
TypeObject.prototype.instanceOf = function (typeName) {
    return this._typeName === typeName;
};

// Return a printable representation
TypeObject.prototype.toPrintable = function (exclude, noColor) {
    return utility.toPrintable.call(this, exclude, noColor);
};

// Export the class
module.exports = exports = TypeObject;