Home Reference Source

lib/serializer/src/types.js

// Low-level types that make up operations

import v from "./SerializerValidation";
import fp from "./FastParser";

import ChainTypes from "../../chain/src/ChainTypes";
import ObjectId from "../../chain/src/ObjectId";

import {PublicKey, Address} from "../../ecc";
import {ChainConfig} from "bitsharesjs-ws";

const Buffer = require("safe-buffer").Buffer;

var Types = {};

const HEX_DUMP = process.env.npm_config__graphene_serializer_hex_dump;

Types.uint8 = {
    fromByteBuffer(b) {
        return b.readUint8();
    },
    appendByteBuffer(b, object) {
        v.require_range(0, 0xff, object, `uint8 ${object}`);
        b.writeUint8(object);
        return;
    },
    fromObject(object) {
        v.require_range(0, 0xff, object, `uint8 ${object}`);
        return object;
    },
    toObject(object, debug = {}) {
        if (debug.use_default && object === undefined) {
            return 0;
        }
        v.require_range(0, 0xff, object, `uint8 ${object}`);
        return parseInt(object);
    }
};

Types.uint16 = {
    fromByteBuffer(b) {
        return b.readUint16();
    },
    appendByteBuffer(b, object) {
        v.require_range(0, 0xffff, object, `uint16 ${object}`);
        b.writeUint16(object);
        return;
    },
    fromObject(object) {
        v.require_range(0, 0xffff, object, `uint16 ${object}`);
        return object;
    },
    toObject(object, debug = {}) {
        if (debug.use_default && object === undefined) {
            return 0;
        }
        v.require_range(0, 0xffff, object, `uint16 ${object}`);
        return parseInt(object);
    }
};

Types.uint32 = {
    fromByteBuffer(b) {
        return b.readUint32();
    },
    appendByteBuffer(b, object) {
        v.require_range(0, 0xffffffff, object, `uint32 ${object}`);
        b.writeUint32(object);
        return;
    },
    fromObject(object) {
        v.require_range(0, 0xffffffff, object, `uint32 ${object}`);
        return object;
    },
    toObject(object, debug = {}) {
        if (debug.use_default && object === undefined) {
            return 0;
        }
        v.require_range(0, 0xffffffff, object, `uint32 ${object}`);
        return parseInt(object);
    }
};

var MIN_SIGNED_32 = -1 * Math.pow(2, 31);
var MAX_SIGNED_32 = Math.pow(2, 31) - 1;

Types.varint32 = {
    fromByteBuffer(b) {
        return b.readVarint32();
    },
    appendByteBuffer(b, object) {
        v.require_range(
            MIN_SIGNED_32,
            MAX_SIGNED_32,
            object,
            `uint32 ${object}`
        );
        b.writeVarint32(object);
        return;
    },
    fromObject(object) {
        v.require_range(
            MIN_SIGNED_32,
            MAX_SIGNED_32,
            object,
            `uint32 ${object}`
        );
        return object;
    },
    toObject(object, debug = {}) {
        if (debug.use_default && object === undefined) {
            return 0;
        }
        v.require_range(
            MIN_SIGNED_32,
            MAX_SIGNED_32,
            object,
            `uint32 ${object}`
        );
        return parseInt(object);
    }
};

Types.int64 = {
    fromByteBuffer(b) {
        return b.readInt64();
    },
    appendByteBuffer(b, object) {
        v.required(object);
        b.writeInt64(v.to_long(object));
        return;
    },
    fromObject(object) {
        v.required(object);
        return v.to_long(object);
    },
    toObject(object, debug = {}) {
        if (debug.use_default && object === undefined) {
            return "0";
        }
        v.required(object);
        return v.to_long(object).toString();
    }
};

Types.uint64 = {
    fromByteBuffer(b) {
        return b.readUint64();
    },
    appendByteBuffer(b, object) {
        b.writeUint64(v.to_long(v.unsigned(object), undefined, true));
        return;
    },
    fromObject(object) {
        return v.to_long(v.unsigned(object), undefined, true);
    },
    toObject(object, debug = {}) {
        if (debug.use_default && object === undefined) {
            return "0";
        }
        return v.to_long(object, undefined, true).toString();
    }
};

Types.string = {
    fromByteBuffer(b) {
        var b_copy;
        var len = b.readVarint32();
        (b_copy = b.copy(b.offset, b.offset + len)), b.skip(len);
        return Buffer.from(b_copy.toBinary(), "binary");
    },
    appendByteBuffer(b, object) {
        v.required(object);
        b.writeVarint32(object.length);
        b.append(object.toString("binary"), "binary");
        return;
    },
    fromObject(object) {
        v.required(object);
        return Buffer.from(object);
    },
    toObject(object, debug = {}) {
        if (debug.use_default && object === undefined) {
            return "";
        }
        return object.toString();
    }
};

Types.bytes = function(size) {
    return {
        fromByteBuffer(b) {
            if (size === undefined) {
                var b_copy;
                var len = b.readVarint32();
                (b_copy = b.copy(b.offset, b.offset + len)), b.skip(len);
                return Buffer.from(b_copy.toBinary(), "binary");
            } else {
                (b_copy = b.copy(b.offset, b.offset + size)), b.skip(size);
                return Buffer.from(b_copy.toBinary(), "binary");
            }
        },
        appendByteBuffer(b, object) {
            v.required(object);
            if (typeof object === "string") object = Buffer.from(object, "hex");

            if (size === undefined) {
                b.writeVarint32(object.length);
            }
            b.append(object.toString("binary"), "binary");
            return;
        },
        fromObject(object) {
            v.required(object);
            if (Buffer.isBuffer(object)) return object;

            return Buffer.from(object, "hex");
        },
        toObject(object, debug = {}) {
            if (debug.use_default && object === undefined) {
                var zeros = function(num) {
                    return new Array(num).join("00");
                };
                return zeros(size);
            }
            v.required(object);
            return object.toString("hex");
        }
    };
};

Types.bool = {
    fromByteBuffer(b) {
        return b.readUint8() === 1;
    },
    appendByteBuffer(b, object) {
        // supports boolean or integer
        b.writeUint8(JSON.parse(object) ? 1 : 0);
        return;
    },
    fromObject(object) {
        return JSON.parse(object) ? true : false;
    },
    toObject(object, debug = {}) {
        if (debug.use_default && object === undefined) {
            return false;
        }
        return JSON.parse(object) ? true : false;
    }
};

Types.void = {
    fromByteBuffer(b) {
        throw new Error("(void) undefined type");
    },
    appendByteBuffer(b, object) {
        throw new Error("(void) undefined type");
    },
    fromObject(object) {
        throw new Error("(void) undefined type");
    },
    toObject(object, debug = {}) {
        if (debug.use_default && object === undefined) {
            return undefined;
        }
        throw new Error("(void) undefined type");
    }
};

Types.array = function(st_operation) {
    return {
        fromByteBuffer(b) {
            var size = b.readVarint32();
            if (HEX_DUMP) {
                console.log("varint32 size = " + size.toString(16));
            }
            var result = [];
            for (
                var i = 0;
                0 < size ? i < size : i > size;
                0 < size ? i++ : i++
            ) {
                result.push(st_operation.fromByteBuffer(b));
            }
            return sortOperation(result, st_operation);
        },
        appendByteBuffer(b, object) {
            v.required(object);
            object = sortOperation(object, st_operation);
            b.writeVarint32(object.length);
            for (var i = 0, o; i < object.length; i++) {
                o = object[i];
                st_operation.appendByteBuffer(b, o);
            }
        },
        fromObject(object) {
            v.required(object);
            object = sortOperation(object, st_operation);
            var result = [];
            for (var i = 0, o; i < object.length; i++) {
                o = object[i];
                result.push(st_operation.fromObject(o));
            }
            return result;
        },
        toObject(object, debug = {}) {
            if (debug.use_default && object === undefined) {
                return [st_operation.toObject(object, debug)];
            }
            v.required(object);
            object = sortOperation(object, st_operation);

            var result = [];
            for (var i = 0, o; i < object.length; i++) {
                o = object[i];
                result.push(st_operation.toObject(o, debug));
            }
            return result;
        }
    };
};

Types.time_point_sec = {
    fromByteBuffer(b) {
        return b.readUint32();
    },
    appendByteBuffer(b, object) {
        if (typeof object !== "number")
            object = Types.time_point_sec.fromObject(object);

        b.writeUint32(object);
        return;
    },
    fromObject(object) {
        v.required(object);

        if (typeof object === "number") return object;

        if (object.getTime) return Math.floor(object.getTime() / 1000);

        if (typeof object !== "string")
            throw new Error("Unknown date type: " + object);

        if (/T[0-2][0-9]:[0-5][0-9]:[0-5][0-9]$/.test(object))
            object = object + "Z";

        return Math.floor(new Date(object).getTime() / 1000);
    },
    toObject(object, debug = {}) {
        if (debug.use_default && object === undefined)
            return new Date(0).toISOString().split(".")[0];

        v.required(object);

        if (typeof object === "string") return object;

        if (object.getTime) return object.toISOString().split(".")[0];

        var int = parseInt(object);
        v.require_range(0, 0xffffffff, int, `uint32 ${object}`);
        return new Date(int * 1000).toISOString().split(".")[0];
    }
};

Types.set = function(st_operation) {
    return {
        validate(array) {
            var dup_map = {};
            for (var i = 0, o; i < array.length; i++) {
                o = array[i];
                var ref;
                if (
                    ((ref = typeof o), ["string", "number"].indexOf(ref) >= 0)
                ) {
                    if (dup_map[o] !== undefined) {
                        throw new Error("duplicate (set)");
                    }
                    dup_map[o] = true;
                }
            }
            return sortOperation(array, st_operation);
        },
        fromByteBuffer(b) {
            var size = b.readVarint32();
            if (HEX_DUMP) {
                console.log("varint32 size = " + size.toString(16));
            }
            return this.validate(
                (() => {
                    var result = [];
                    for (
                        var i = 0;
                        0 < size ? i < size : i > size;
                        0 < size ? i++ : i++
                    ) {
                        result.push(st_operation.fromByteBuffer(b));
                    }
                    return result;
                })()
            );
        },
        appendByteBuffer(b, object) {
            if (!object) {
                object = [];
            }
            b.writeVarint32(object.length);
            var iterable = this.validate(object);
            for (var i = 0, o; i < iterable.length; i++) {
                o = iterable[i];
                st_operation.appendByteBuffer(b, o);
            }
            return;
        },
        fromObject(object) {
            if (!object) {
                object = [];
            }
            return this.validate(
                (() => {
                    var result = [];
                    for (var i = 0, o; i < object.length; i++) {
                        o = object[i];
                        result.push(st_operation.fromObject(o));
                    }
                    return result;
                })()
            );
        },
        toObject(object, debug = {}) {
            if (debug.use_default && object === undefined) {
                return [st_operation.toObject(object, debug)];
            }
            if (!object) {
                object = [];
            }
            return this.validate(
                (() => {
                    var result = [];
                    for (var i = 0, o; i < object.length; i++) {
                        o = object[i];
                        result.push(st_operation.toObject(o, debug));
                    }
                    return result;
                })()
            );
        }
    };
};

// global_parameters_update_operation current_fees
Types.fixed_array = function(count, st_operation) {
    return {
        fromByteBuffer: function(b) {
            var i, j, ref, results;
            results = [];
            for (i = j = 0, ref = count; j < ref; i = j += 1) {
                results.push(st_operation.fromByteBuffer(b));
            }
            return sortOperation(results, st_operation);
        },
        appendByteBuffer: function(b, object) {
            var i, j, ref;
            if (count !== 0) {
                v.required(object);
                object = sortOperation(object, st_operation);
            }
            for (i = j = 0, ref = count; j < ref; i = j += 1) {
                st_operation.appendByteBuffer(b, object[i]);
            }
        },
        fromObject: function(object) {
            var i, j, ref, results;
            if (count !== 0) {
                v.required(object);
            }
            results = [];
            for (i = j = 0, ref = count; j < ref; i = j += 1) {
                results.push(st_operation.fromObject(object[i]));
            }
            return results;
        },
        toObject: function(object, debug) {
            var i, j, k, ref, ref1, results, results1;
            if (debug == null) {
                debug = {};
            }
            if (debug.use_default && object === void 0) {
                results = [];
                for (i = j = 0, ref = count; j < ref; i = j += 1) {
                    results.push(st_operation.toObject(void 0, debug));
                }
                return results;
            }
            if (count !== 0) {
                v.required(object);
            }
            results1 = [];
            for (i = k = 0, ref1 = count; k < ref1; i = k += 1) {
                results1.push(st_operation.toObject(object[i], debug));
            }
            return results1;
        }
    };
};

/* Supports instance numbers (11) or object types (1.2.11).  Object type
Validation is enforced when an object type is used. */
var id_type = function(reserved_spaces, object_type) {
    v.required(reserved_spaces, "reserved_spaces");
    v.required(object_type, "object_type");
    return {
        fromByteBuffer(b) {
            return b.readVarint32();
        },
        appendByteBuffer(b, object) {
            v.required(object);
            if (object.resolve !== undefined) {
                object = object.resolve;
            }
            // convert 1.2.n into just n
            if (/^[0-9]+\.[0-9]+\.[0-9]+$/.test(object)) {
                object = v.get_instance(reserved_spaces, object_type, object);
            }
            b.writeVarint32(v.to_number(object));
            return;
        },
        fromObject(object) {
            v.required(object);
            if (object.resolve !== undefined) {
                object = object.resolve;
            }
            if (v.is_digits(object)) {
                return v.to_number(object);
            }
            return v.get_instance(reserved_spaces, object_type, object);
        },
        toObject(object, debug = {}) {
            var object_type_id = ChainTypes.object_type[object_type];
            if (debug.use_default && object === undefined) {
                return `${reserved_spaces}.${object_type_id}.0`;
            }
            v.required(object);
            if (object.resolve !== undefined) {
                object = object.resolve;
            }
            if (/^[0-9]+\.[0-9]+\.[0-9]+$/.test(object)) {
                object = v.get_instance(reserved_spaces, object_type, object);
            }

            return `${reserved_spaces}.${object_type_id}.` + object;
        }
    };
};

Types.protocol_id_type = function(name) {
    v.required(name, "name");
    return id_type(ChainTypes.reserved_spaces.protocol_ids, name);
};

Types.object_id_type = {
    fromByteBuffer(b) {
        return ObjectId.fromByteBuffer(b);
    },
    appendByteBuffer(b, object) {
        v.required(object);
        if (object.resolve !== undefined) {
            object = object.resolve;
        }
        object = ObjectId.fromString(object);
        object.appendByteBuffer(b);
        return;
    },
    fromObject(object) {
        v.required(object);
        if (object.resolve !== undefined) {
            object = object.resolve;
        }
        return ObjectId.fromString(object);
    },
    toObject(object, debug = {}) {
        if (debug.use_default && object === undefined) {
            return "0.0.0";
        }
        v.required(object);
        if (object.resolve !== undefined) {
            object = object.resolve;
        }
        object = ObjectId.fromString(object);
        return object.toString();
    }
};

Types.vote_id = {
    TYPE: 0x000000ff,
    ID: 0xffffff00,
    fromByteBuffer(b) {
        var value = b.readUint32();
        return {
            type: value & this.TYPE,
            id: value & this.ID
        };
    },
    appendByteBuffer(b, object) {
        v.required(object);
        if (object === "string") object = Types.vote_id.fromObject(object);

        var value = (object.id << 8) | object.type;
        b.writeUint32(value);
        return;
    },
    fromObject(object) {
        v.required(object, "(type vote_id)");
        if (typeof object === "object") {
            v.required(object.type, "type");
            v.required(object.id, "id");
            return object;
        }
        v.require_test(/^[0-9]+:[0-9]+$/, object, `vote_id format ${object}`);
        var [type, id] = object.split(":");
        v.require_range(0, 0xff, type, `vote type ${object}`);
        v.require_range(0, 0xffffff, id, `vote id ${object}`);
        return {type, id};
    },
    toObject(object, debug = {}) {
        if (debug.use_default && object === undefined) {
            return "0:0";
        }
        v.required(object);
        if (typeof object === "string")
            object = Types.vote_id.fromObject(object);

        return object.type + ":" + object.id;
    },
    compare(a, b) {
        if (typeof a !== "object") a = Types.vote_id.fromObject(a);
        if (typeof b !== "object") b = Types.vote_id.fromObject(b);
        return parseInt(a.id) - parseInt(b.id);
    }
};

Types.optional = function(st_operation) {
    v.required(st_operation, "st_operation");
    return {
        fromByteBuffer(b) {
            if (!(b.readUint8() === 1)) {
                return undefined;
            }
            return st_operation.fromByteBuffer(b);
        },
        appendByteBuffer(b, object) {
            if (object !== null && object !== undefined) {
                b.writeUint8(1);
                st_operation.appendByteBuffer(b, object);
            } else {
                b.writeUint8(0);
            }
            return;
        },
        fromObject(object) {
            if (object === undefined) {
                return undefined;
            }
            return st_operation.fromObject(object);
        },
        toObject(object, debug = {}) {
            // toObject is only null save if use_default is true
            var result_object = (() => {
                if (!debug.use_default && object === undefined) {
                    return undefined;
                } else {
                    return st_operation.toObject(object, debug);
                }
            })();

            if (debug.annotate) {
                if (typeof result_object === "object") {
                    result_object.__optional = "parent is optional";
                } else {
                    result_object = {__optional: result_object};
                }
            }
            return result_object;
        }
    };
};

Types.static_variant = function(_st_operations) {
    return {
        nosort: true,
        st_operations: _st_operations,
        fromByteBuffer(b) {
            var type_id = b.readVarint32();
            var st_operation = this.st_operations[type_id];
            if (HEX_DUMP) {
                console.error(
                    `static_variant id 0x${type_id.toString(16)} (${type_id})`
                );
            }
            v.required(st_operation, `operation ${type_id}`);
            return [type_id, st_operation.fromByteBuffer(b)];
        },
        appendByteBuffer(b, object) {
            v.required(object);
            var type_id = object[0];
            var st_operation = this.st_operations[type_id];
            v.required(st_operation, `operation ${type_id}`);
            b.writeVarint32(type_id);
            st_operation.appendByteBuffer(b, object[1]);
            return;
        },
        fromObject(object) {
            v.required(object);
            var type_id = object[0];
            var st_operation = this.st_operations[type_id];
            v.required(st_operation, `operation ${type_id}`);
            return [type_id, st_operation.fromObject(object[1])];
        },
        toObject(object, debug = {}) {
            if (debug.use_default && object === undefined) {
                return [0, this.st_operations[0].toObject(undefined, debug)];
            }
            v.required(object);
            var type_id = object[0];
            var st_operation = this.st_operations[type_id];
            v.required(st_operation, `operation ${type_id}`);
            return [type_id, st_operation.toObject(object[1], debug)];
        }
    };
};

Types.map = function(key_st_operation, value_st_operation) {
    return {
        validate(array) {
            if (!Array.isArray(array)) {
                throw new Error("expecting array");
            }
            var dup_map = {};
            for (var i = 0, o; i < array.length; i++) {
                o = array[i];
                var ref;
                if (!(o.length === 2)) {
                    throw new Error("expecting two elements");
                }
                if (
                    ((ref = typeof o[0]),
                    ["number", "string"].indexOf(ref) >= 0)
                ) {
                    if (dup_map[o[0]] !== undefined) {
                        throw new Error("duplicate (map)");
                    }
                    dup_map[o[0]] = true;
                }
            }
            return sortOperation(array, key_st_operation);
        },

        fromByteBuffer(b) {
            var result = [];
            var end = b.readVarint32();
            for (var i = 0; 0 < end ? i < end : i > end; 0 < end ? i++ : i++) {
                result.push([
                    key_st_operation.fromByteBuffer(b),
                    value_st_operation.fromByteBuffer(b)
                ]);
            }
            return this.validate(result);
        },

        appendByteBuffer(b, object) {
            this.validate(object);
            b.writeVarint32(object.length);
            for (var i = 0, o; i < object.length; i++) {
                o = object[i];
                key_st_operation.appendByteBuffer(b, o[0]);
                value_st_operation.appendByteBuffer(b, o[1]);
            }
            return;
        },
        fromObject(object) {
            v.required(object);
            var result = [];
            for (var i = 0, o; i < object.length; i++) {
                o = object[i];
                result.push([
                    key_st_operation.fromObject(o[0]),
                    value_st_operation.fromObject(o[1])
                ]);
            }
            return this.validate(result);
        },
        toObject(object, debug = {}) {
            if (debug.use_default && object === undefined) {
                return [
                    [
                        key_st_operation.toObject(undefined, debug),
                        value_st_operation.toObject(undefined, debug)
                    ]
                ];
            }
            v.required(object);
            object = this.validate(object);
            var result = [];
            for (var i = 0, o; i < object.length; i++) {
                o = object[i];
                result.push([
                    key_st_operation.toObject(o[0], debug),
                    value_st_operation.toObject(o[1], debug)
                ]);
            }
            return result;
        }
    };
};

Types.public_key = {
    toPublic(object) {
        if (object.resolve !== undefined) {
            object = object.resolve;
        }
        return object == null
            ? object
            : object.Q
                ? object
                : PublicKey.fromStringOrThrow(object);
    },
    fromByteBuffer(b) {
        return fp.public_key(b);
    },
    appendByteBuffer(b, object) {
        v.required(object);
        fp.public_key(b, Types.public_key.toPublic(object));
        return;
    },
    fromObject(object) {
        v.required(object);
        if (object.Q) {
            return object;
        }
        return Types.public_key.toPublic(object);
    },
    toObject(object, debug = {}) {
        if (debug.use_default && object === undefined) {
            return (
                ChainConfig.address_prefix +
                "859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2HqhToVM"
            );
        }
        v.required(object);
        return object.toString();
    },
    compare(a, b) {
        return Types.public_key
            .fromObject(a)
            .toBlockchainAddress()
            .compare(Types.public_key.fromObject(b).toBlockchainAddress());
    }
};

Types.address = {
    _to_address(object) {
        v.required(object);
        if (object.addy) {
            return object;
        }
        return Address.fromString(object);
    },
    fromByteBuffer(b) {
        return new Address(fp.ripemd160(b));
    },
    appendByteBuffer(b, object) {
        fp.ripemd160(b, Types.address._to_address(object).toBuffer());
        return;
    },
    fromObject(object) {
        return Types.address._to_address(object);
    },
    toObject(object, debug = {}) {
        if (debug.use_default && object === undefined) {
            return (
                ChainConfig.address_prefix + "664KmHxSuQyDsfwo4WEJvWpzg1QKdg67S"
            );
        }
        return Types.address._to_address(object).toString();
    },
    compare(a, b) {
        return strCmp(a.toString(), b.toString());
    }
};

let strCmp = (a, b) => (a > b ? 1 : a < b ? -1 : 0);
let firstEl = el => (Array.isArray(el) ? el[0] : el);
let sortOperation = (array, st_operation) =>
    st_operation.nosort
        ? array
        : st_operation.compare
            ? array.sort((a, b) => st_operation.compare(firstEl(a), firstEl(b))) // custom compare operation
            : array.sort(
                  (a, b) =>
                      typeof firstEl(a) === "number" &&
                      typeof firstEl(b) === "number"
                          ? firstEl(a) - firstEl(b)
                          : // A binary string compare does not work. Performanance is very good so HEX is used..  localeCompare is another option.
                            Buffer.isBuffer(firstEl(a)) &&
                            Buffer.isBuffer(firstEl(b))
                              ? strCmp(
                                    firstEl(a).toString("hex"),
                                    firstEl(b).toString("hex")
                                )
                              : strCmp(
                                    firstEl(a).toString(),
                                    firstEl(b).toString()
                                )
              );

export default Types;