reader.js

/*
 * node-qtdatastream
 * https://github.com/magne4000/node-qtdatastream
 *
 * Copyright (c) 2014 Joël Charles
 * Licensed under the MIT license.
 */

/** @module qtdatastream/reader */

var qtdatastream = require('./qtdatastream'),
    util = require('./util'),
    T = qtdatastream.Types,
    logger = require('debug')('qtdatastream:reader'),
    Int64 = require('int64-buffer').Int64BE,
    UInt64 = require('int64-buffer').Uint64BE,
    debug = qtdatastream.debug,
    Iterator = require('./iterator');

/**
 * @class
 * @classdesc Class to read and parse a given Qt buffer
 * @param {Buffer} b Qt formatted buffer
 * @example
 * var r = new Reader(buffer);
 * var parsed = r.parse();
 * 
 * // parsed = {
 * //   "AString": "BString",
 * //   "CString": ["DString", 1, 4, true],
 * //   "EString": ""
 * // };
 */
var Reader = function (b) {
    this.buffer = b.slice(0, 4);
    this.pos = 0;
    this.parsed = null;
    try {
        this.size = this.getUInt();
    } catch (e) {
        logger("Error while fetching buffer size", e.message);
        this.size = 0;
    }
    if (debug) {
        logger("buffer size : " + this.size);
    }
    this.buffer = b.slice(0, this.size+4);
    if (debug) {
        logger("buffer length : " + this.buffer.length);
    }
    this.remaining = b.slice(this.size+4);
    if (debug) {
        logger("remaining length : " + this.remaining.length);
    }
};

Reader.prototype.getUserTypeIterator = function(key) {
    return new Iterator(qtdatastream.getUserType(key), this.getQVariantByType, this);
};

/**
 * Converts an UTF-16 buffer to an UTF-8 buffer
 * @param {Buffer} msg An UTF-16 Buffer
 * @returns {Buffer} An UTF-8 Buffer
 */
Reader.conv = function(msg) {
    return qtdatastream.toggleEndianness(msg).toString("ucs2");
};

/**
 * Parse buffer
 * @returns {Object} buffer converted to a Javascript object
 */
Reader.prototype.parse = function(){
    this.parsed = this.getQVariant();
    return this.parsed;
};

/**
 * @param {Number} type A QVariant Type as defined in {@link module:qtdatastream.Types}
 * @param {string} [userType] Can specify userType in case of encapsulated userTypes
 * @returns {*} Corresponding QVariant result corresponding to the given type
 */
Reader.prototype.getQVariantByType = function(type, userType){
    switch(type) {
        case T.INVALID:
            return undefined;
        case T.BOOL:
            return this.getBool();
        case T.SHORT:
            return this.getShort();
        case T.INT:
            return this.getInt();
        case T.UINT:
            return this.getUInt();
        case T.INT64:
            return this.getInt64();
        case T.UINT64:
            return this.getUInt64();
        case T.DOUBLE:
            return this.getDouble();
        case T.MAP:
            return this.getMap();
        case T.LIST:
            return this.getList();
        case T.STRING:
            return this.getString();
        case T.CHAR:
            return this.getChar();
        case T.STRINGLIST:
            return this.getStringList();
        case T.BYTEARRAY:
            return this.getByteArray();
        case T.TIME:
            return this.getTime();
        case T.DATETIME:
            return this.getDateTime();
        case T.USERTYPE:
            var userTypeName = "";
            if (userType) {
                userTypeName = userType;
            } else {
                var t = this.getByteArray();
                userTypeName = util.str(t);
            }
            if (qtdatastream.isUserTypeComplex(userTypeName)) {
                var iter = this.getUserTypeIterator(userTypeName), ret = {};
                while(iter.hasNext()){
                    var item = iter.next();
                    ret[item.key] = item.value;
                }
                return ret;
            }
            var qvtype = qtdatastream.getUserType(userTypeName);
            if (qvtype) {
                return this.getQVariantByType(qvtype);
            } else {
                throw "Unhandled userType " + userTypeName;
            }
        default:
            throw "Unhandled type " + type;
    }
};

/**
 * @returns {number} Current QTime (milliseconds since midnight)
 */
Reader.prototype.getTime = function(){
    return this.getUInt();
};

/**
 * @returns {Date} Current QDateTime as string
 */
Reader.prototype.getDateTime = function(){
    var julianDay = this.getUInt();
    var msecondsSinceMidnight = this.getUInt();
    var isUTC = this.getBool();
    var dateAtMidnight = util.julianDayToDate(julianDay);
    dateAtMidnight.setMilliseconds(msecondsSinceMidnight);
    return dateAtMidnight;
};

/**
 * @returns {*} Corresponding QVariant result corresponding current QVariant type
 */
Reader.prototype.getQVariant = function(){
    var type = this.getQVariantType(),
        isNull = this.getBool();
    try {
        return this.getQVariantByType(type);
    } catch (e) {
        logger(e);
        return null;
    }
};

/**
 * @returns {Number} Current QVariant type as defined in {@link module:qtdatastream.Types}
 */
Reader.prototype.getQVariantType = function(){
    return this.getUInt();
};

/**
 * @returns {string} Current string
 */
Reader.prototype.getString = function(){
    var stringSize = this.getUInt();
    if (stringSize === 0 || stringSize === 0xffffffff) return "";
    
    var stringBuffer = this.buffer.slice(this.pos, this.pos+stringSize);
    var resultingString = Reader.conv(stringBuffer).toString();
    this.pos += stringSize;
    return resultingString;
};

/**
 * @returns {string} Current char
 */
Reader.prototype.getChar = function(){
    var charBuffer = this.buffer.slice(this.pos, this.pos+2);
    var resultingChar = Reader.conv(charBuffer).toString();
    this.pos += 2;
    return resultingChar;
};

/**
 * @returns {Buffer} Current ByteArray
 */
Reader.prototype.getByteArray = function(){
    var arraySize = this.getUInt();
    if (arraySize === 0 || arraySize === 0xffffffff) return null;
    var buffer = this.buffer.slice(this.pos, this.pos+arraySize);
    this.pos += arraySize;
    return buffer;
};

/**
 * @returns {Array} Current list
 */
Reader.prototype.getList = function(){
    var listSize = this.getUInt(), i, list = [], val;
    for (i=0; i<listSize; i++) {
        // get value
        val = this.getQVariant();
        list.push(val);
    }
    return list;
};

/**
 * @returns {Array<string>} Current list of string
 */
Reader.prototype.getStringList = function(){
    var listSize = this.getUInt(), i, list = [], val;
    for (i=0; i<listSize; i++) {
        // get value
        val = this.getString();
        list.push(val);
    }
    return list;
};

/**
 * @returns {Object} Current map
 */
Reader.prototype.getMap = function(){
    var mapSize = this.getUInt(), i, map = {}, key, val;
    for (i=0; i<mapSize; i++) {
        // get key
        key = this.getString();
        // get value
        val = this.getQVariant();
        map[key] = val;
    }
    return map;
};

/**
 * @returns {Number} Current short
 */
Reader.prototype.getShort = function(){
    var i = this.buffer.readInt16BE(this.pos);
    this.pos += 2;
    return i;
};

/**
 * @returns {Number} Current int
 */
Reader.prototype.getInt = function(){
    var i = this.buffer.readInt32BE(this.pos);
    this.pos += 4;
    return i;
};

/**
 * @returns {Number} Current uint
 */
Reader.prototype.getUInt = function(){
    var i = this.buffer.readUInt32BE(this.pos);
    this.pos += 4;
    return i;
};

/**
 * @returns {Number} Current int64
 */
Reader.prototype.getInt64 = function(){
    var i = Int64(this.buffer, this.pos).toNumber();
    this.pos += 8;
    return i;
};

/**
 * @returns {Number} Current uint64
 */
Reader.prototype.getUInt64 = function(){
    var i = UInt64(this.buffer, this.pos).toNumber();
    this.pos += 8;
    return i;
};

/**
 * @returns {Number} Current double
 */
Reader.prototype.getDouble = function(){
    var i = this.buffer.readDoubleBE(this.pos);
    this.pos += 8;
    return i;
};

/**
 * @returns {Number} Current boolean
 */
Reader.prototype.getBool = function(){
    var i = this.buffer.readInt8(this.pos);
    this.pos += 1;
    return i;
};

module.exports = Reader;