2016-12-08 18:36:12 -08:00
|
|
|
/*!
|
2018-08-01 20:00:09 -07:00
|
|
|
* scriptnum.js - script number object for hsd.
|
2017-08-16 20:35:54 -07:00
|
|
|
* Copyright (c) 2017, Christopher Jeffrey (MIT License).
|
2018-08-01 20:00:09 -07:00
|
|
|
* https://github.com/handshake-org/hsd
|
2016-12-08 18:36:12 -08:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
2018-07-19 05:40:48 -07:00
|
|
|
const assert = require('bsert');
|
2017-10-30 21:22:31 -07:00
|
|
|
const {I64} = require('n64');
|
2017-08-21 04:32:06 -07:00
|
|
|
const ScriptError = require('./scripterror');
|
2017-08-16 20:35:54 -07:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Constants
|
|
|
|
|
*/
|
|
|
|
|
|
2017-06-29 20:54:07 -07:00
|
|
|
const EMPTY_ARRAY = Buffer.alloc(0);
|
2016-12-08 18:36:12 -08:00
|
|
|
|
2017-08-16 20:35:54 -07:00
|
|
|
/**
|
|
|
|
|
* Script Number
|
|
|
|
|
* @see https://github.com/chjj/n64
|
|
|
|
|
* @alias module:script.ScriptNum
|
|
|
|
|
* @property {Number} hi
|
|
|
|
|
* @property {Number} lo
|
|
|
|
|
* @property {Number} sign
|
|
|
|
|
*/
|
|
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
class ScriptNum extends I64 {
|
|
|
|
|
/**
|
|
|
|
|
* Create a script number.
|
|
|
|
|
* @constructor
|
2024-09-28 18:24:51 +04:00
|
|
|
* @param {(Number|String|Buffer|Object)?} [num]
|
|
|
|
|
* @param {(String|Number)?} [base]
|
2017-11-16 18:44:38 -08:00
|
|
|
*/
|
2016-12-08 18:36:12 -08:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
constructor(num, base) {
|
|
|
|
|
super(num, base);
|
|
|
|
|
}
|
2017-08-16 20:35:54 -07:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
/**
|
|
|
|
|
* Cast to int32.
|
|
|
|
|
* @returns {Number}
|
|
|
|
|
*/
|
2016-12-08 21:17:39 -08:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
getInt() {
|
|
|
|
|
if (this.lt(I64.INT32_MIN))
|
|
|
|
|
return I64.LONG_MIN;
|
2017-07-30 10:41:58 -07:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
if (this.gt(I64.INT32_MAX))
|
|
|
|
|
return I64.LONG_MAX;
|
2016-12-08 18:36:12 -08:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
return this.toInt();
|
2016-12-08 18:36:12 -08:00
|
|
|
}
|
|
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
/**
|
|
|
|
|
* Serialize script number.
|
|
|
|
|
* @returns {Buffer}
|
|
|
|
|
*/
|
|
|
|
|
|
2018-07-15 05:44:38 -07:00
|
|
|
encode() {
|
2017-11-16 18:44:38 -08:00
|
|
|
let num = this;
|
|
|
|
|
|
|
|
|
|
// Zeroes are always empty arrays.
|
|
|
|
|
if (num.isZero())
|
|
|
|
|
return EMPTY_ARRAY;
|
|
|
|
|
|
|
|
|
|
// Need to append sign bit.
|
|
|
|
|
let neg = false;
|
|
|
|
|
if (num.isNeg()) {
|
|
|
|
|
num = num.neg();
|
|
|
|
|
neg = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate size.
|
|
|
|
|
const size = num.byteLength();
|
|
|
|
|
|
|
|
|
|
let offset = 0;
|
|
|
|
|
|
|
|
|
|
if (num.testn((size * 8) - 1))
|
|
|
|
|
offset = 1;
|
|
|
|
|
|
|
|
|
|
// Write number.
|
|
|
|
|
const data = Buffer.allocUnsafe(size + offset);
|
|
|
|
|
|
|
|
|
|
switch (size) {
|
|
|
|
|
case 8:
|
|
|
|
|
data[7] = (num.hi >>> 24) & 0xff;
|
|
|
|
|
case 7:
|
|
|
|
|
data[6] = (num.hi >> 16) & 0xff;
|
|
|
|
|
case 6:
|
|
|
|
|
data[5] = (num.hi >> 8) & 0xff;
|
|
|
|
|
case 5:
|
|
|
|
|
data[4] = num.hi & 0xff;
|
|
|
|
|
case 4:
|
|
|
|
|
data[3] = (num.lo >>> 24) & 0xff;
|
|
|
|
|
case 3:
|
|
|
|
|
data[2] = (num.lo >> 16) & 0xff;
|
|
|
|
|
case 2:
|
|
|
|
|
data[1] = (num.lo >> 8) & 0xff;
|
|
|
|
|
case 1:
|
|
|
|
|
data[0] = num.lo & 0xff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Append sign bit.
|
|
|
|
|
if (data[size - 1] & 0x80) {
|
|
|
|
|
assert(offset === 1);
|
|
|
|
|
assert(data.length === size + offset);
|
|
|
|
|
data[size] = neg ? 0x80 : 0;
|
|
|
|
|
} else if (neg) {
|
|
|
|
|
assert(offset === 0);
|
|
|
|
|
assert(data.length === size);
|
|
|
|
|
data[size - 1] |= 0x80;
|
|
|
|
|
} else {
|
|
|
|
|
assert(offset === 0);
|
|
|
|
|
assert(data.length === size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return data;
|
2016-12-08 18:36:12 -08:00
|
|
|
}
|
|
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
/**
|
|
|
|
|
* Instantiate script number from serialized data.
|
|
|
|
|
* @private
|
|
|
|
|
* @param {Buffer} data
|
|
|
|
|
* @returns {ScriptNum}
|
|
|
|
|
*/
|
|
|
|
|
|
2018-07-15 05:44:38 -07:00
|
|
|
_decode(data) {
|
2017-11-16 18:44:38 -08:00
|
|
|
assert(Buffer.isBuffer(data));
|
|
|
|
|
|
|
|
|
|
// Empty arrays are always zero.
|
|
|
|
|
if (data.length === 0)
|
|
|
|
|
return this;
|
|
|
|
|
|
|
|
|
|
// Read number (9 bytes max).
|
|
|
|
|
switch (data.length) {
|
|
|
|
|
case 8:
|
|
|
|
|
this.hi |= data[7] << 24;
|
|
|
|
|
case 7:
|
|
|
|
|
this.hi |= data[6] << 16;
|
|
|
|
|
case 6:
|
|
|
|
|
this.hi |= data[5] << 8;
|
|
|
|
|
case 5:
|
|
|
|
|
this.hi |= data[4];
|
|
|
|
|
case 4:
|
|
|
|
|
this.lo |= data[3] << 24;
|
|
|
|
|
case 3:
|
|
|
|
|
this.lo |= data[2] << 16;
|
|
|
|
|
case 2:
|
|
|
|
|
this.lo |= data[1] << 8;
|
|
|
|
|
case 1:
|
|
|
|
|
this.lo |= data[0];
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
for (let i = 0; i < data.length; i++)
|
|
|
|
|
this.orb(i, data[i]);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove high bit and flip sign.
|
|
|
|
|
if (data[data.length - 1] & 0x80) {
|
|
|
|
|
this.setn((data.length * 8) - 1, 0);
|
|
|
|
|
this.ineg();
|
|
|
|
|
}
|
2016-12-08 18:36:12 -08:00
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
/**
|
|
|
|
|
* Decode and verify script number.
|
|
|
|
|
* @private
|
|
|
|
|
* @param {Buffer} data
|
2024-09-28 18:24:51 +04:00
|
|
|
* @param {Boolean?} [minimal] - Require minimal encoding.
|
|
|
|
|
* @param {Number?} [limit] - Size limit.
|
2017-11-16 18:44:38 -08:00
|
|
|
* @returns {ScriptNum}
|
|
|
|
|
*/
|
2016-12-08 18:36:12 -08:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
decode(data, minimal, limit) {
|
|
|
|
|
assert(Buffer.isBuffer(data));
|
2017-08-16 20:35:54 -07:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
if (limit != null && data.length > limit)
|
|
|
|
|
throw new ScriptError('UNKNOWN_ERROR', 'Script number overflow.');
|
2017-08-16 20:35:54 -07:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
if (minimal && !ScriptNum.isMinimal(data))
|
|
|
|
|
throw new ScriptError('UNKNOWN_ERROR', 'Non-minimal script number.');
|
2017-08-16 20:35:54 -07:00
|
|
|
|
2018-07-15 05:44:38 -07:00
|
|
|
return this._decode(data);
|
2017-11-16 18:44:38 -08:00
|
|
|
}
|
2016-12-08 18:36:12 -08:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
/**
|
|
|
|
|
* Inspect script number.
|
|
|
|
|
* @returns {String}
|
|
|
|
|
*/
|
2016-12-08 18:36:12 -08:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
inspect() {
|
|
|
|
|
return `<ScriptNum: ${this.toString(10)}>`;
|
|
|
|
|
}
|
2016-12-08 18:36:12 -08:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
/**
|
|
|
|
|
* Test wether a serialized script
|
|
|
|
|
* number is in its most minimal form.
|
|
|
|
|
* @param {Buffer} data
|
|
|
|
|
* @returns {Boolean}
|
|
|
|
|
*/
|
2016-12-08 18:36:12 -08:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
static isMinimal(data) {
|
|
|
|
|
assert(Buffer.isBuffer(data));
|
2017-08-16 20:35:54 -07:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
if (data.length === 0)
|
|
|
|
|
return true;
|
2016-12-08 18:36:12 -08:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
if ((data[data.length - 1] & 0x7f) === 0) {
|
|
|
|
|
if (data.length === 1)
|
|
|
|
|
return false;
|
2017-08-16 20:35:54 -07:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
if ((data[data.length - 2] & 0x80) === 0)
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-08-16 15:33:44 -07:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
/**
|
|
|
|
|
* Decode and verify script number.
|
|
|
|
|
* @param {Buffer} data
|
2024-09-28 18:24:51 +04:00
|
|
|
* @param {Boolean?} [minimal] - Require minimal encoding.
|
|
|
|
|
* @param {Number?} [limit] - Size limit.
|
2017-11-16 18:44:38 -08:00
|
|
|
* @returns {ScriptNum}
|
|
|
|
|
*/
|
2016-12-08 18:36:12 -08:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
static decode(data, minimal, limit) {
|
|
|
|
|
return new this().decode(data, minimal, limit);
|
|
|
|
|
}
|
2017-08-16 20:35:54 -07:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
/**
|
|
|
|
|
* Test whether object is a script number.
|
|
|
|
|
* @param {Object} obj
|
|
|
|
|
* @returns {Boolean}
|
|
|
|
|
*/
|
2016-12-08 18:36:12 -08:00
|
|
|
|
2017-11-16 18:44:38 -08:00
|
|
|
static isScriptNum(obj) {
|
|
|
|
|
return obj instanceof ScriptNum;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-08-16 20:35:54 -07:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Expose
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
module.exports = ScriptNum;
|