Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RW BigInt64 methods #267

Merged
merged 2 commits into from
Nov 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 284 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,48 @@ Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {
this[offset + 3])
}

Buffer.prototype.readBigUInt64LE = defineBigIntMethod(function readBigUInt64LE (offset = 0) {
validateNumber(offset, 'offset')
const first = this[offset]
const last = this[offset + 7]
if (first === undefined || last === undefined) {
boundsError(offset, this.length - 8)
}

const lo = first +
this[++offset] * 2 ** 8 +
this[++offset] * 2 ** 16 +
this[++offset] * 2 ** 24

const hi = this[++offset] +
this[++offset] * 2 ** 8 +
this[++offset] * 2 ** 16 +
last * 2 ** 24

return BigInt(lo) + (BigInt(hi) << 32n)
})

Buffer.prototype.readBigUInt64BE = defineBigIntMethod(function readBigUInt64BE (offset = 0) {
validateNumber(offset, 'offset')
const first = this[offset]
const last = this[offset + 7]
if (first === undefined || last === undefined) {
boundsError(offset, this.length - 8)
}

const hi = first * 2 ** 24 +
this[++offset] * 2 ** 16 +
this[++offset] * 2 ** 8 +
this[++offset]

const lo = this[++offset] * 2 ** 24 +
this[++offset] * 2 ** 16 +
this[++offset] * 2 ** 8 +
last

return (BigInt(hi) << 32n) + BigInt(lo)
})

Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {
offset = offset >>> 0
byteLength = byteLength >>> 0
Expand Down Expand Up @@ -1264,6 +1306,46 @@ Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {
(this[offset + 3])
}

Buffer.prototype.readBigInt64LE = defineBigIntMethod(function readBigInt64LE (offset = 0) {
validateNumber(offset, 'offset')
const first = this[offset]
const last = this[offset + 7]
if (first === undefined || last === undefined) {
boundsError(offset, this.length - 8)
}

const val = this[offset + 4] +
this[offset + 5] * 2 ** 8 +
this[offset + 6] * 2 ** 16 +
(last << 24) // Overflow

return (BigInt(val) << 32n) +
BigInt(first +
this[++offset] * 2 ** 8 +
this[++offset] * 2 ** 16 +
this[++offset] * 2 ** 24)
})

Buffer.prototype.readBigInt64BE = defineBigIntMethod(function readBigInt64BE (offset = 0) {
validateNumber(offset, 'offset')
const first = this[offset]
const last = this[offset + 7]
if (first === undefined || last === undefined) {
boundsError(offset, this.length - 8)
}

const val = (first << 24) + // Overflow
this[++offset] * 2 ** 16 +
this[++offset] * 2 ** 8 +
this[++offset]

return (BigInt(val) << 32n) +
BigInt(this[++offset] * 2 ** 24 +
this[++offset] * 2 ** 16 +
this[++offset] * 2 ** 8 +
last)
})

Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 4, this.length)
Expand Down Expand Up @@ -1380,6 +1462,58 @@ Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert
return offset + 4
}

function wrtBigUInt64LE (buf, value, offset, min, max) {
checkIntBI(value, min, max, buf, offset, 7)

let lo = Number(value & 0xffffffffn)
buf[offset++] = lo
lo = lo >> 8
buf[offset++] = lo
lo = lo >> 8
buf[offset++] = lo
lo = lo >> 8
buf[offset++] = lo
let hi = Number(value >> 32n & 0xffffffffn)
buf[offset++] = hi
hi = hi >> 8
buf[offset++] = hi
hi = hi >> 8
buf[offset++] = hi
hi = hi >> 8
buf[offset++] = hi
return offset
}

function wrtBigUInt64BE (buf, value, offset, min, max) {
checkIntBI(value, min, max, buf, offset, 7)

let lo = Number(value & 0xffffffffn)
buf[offset + 7] = lo
lo = lo >> 8
buf[offset + 6] = lo
lo = lo >> 8
buf[offset + 5] = lo
lo = lo >> 8
buf[offset + 4] = lo
let hi = Number(value >> 32n & 0xffffffffn)
buf[offset + 3] = hi
hi = hi >> 8
buf[offset + 2] = hi
hi = hi >> 8
buf[offset + 1] = hi
hi = hi >> 8
buf[offset] = hi
return offset + 8
}

Buffer.prototype.writeBigUInt64LE = defineBigIntMethod(function writeBigUInt64LE (value, offset = 0) {
return wrtBigUInt64LE(this, value, offset, 0n, 0xffffffffffffffffn)
})

Buffer.prototype.writeBigUInt64BE = defineBigIntMethod(function writeBigUInt64BE (value, offset = 0) {
return wrtBigUInt64BE(this, value, offset, 0n, 0xffffffffffffffffn)
})

Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {
value = +value
offset = offset >>> 0
Expand Down Expand Up @@ -1476,6 +1610,14 @@ Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert)
return offset + 4
}

Buffer.prototype.writeBigInt64LE = defineBigIntMethod(function writeBigInt64LE (value, offset = 0) {
return wrtBigUInt64LE(this, value, offset, -0x8000000000000000n, 0x7fffffffffffffffn)
})

Buffer.prototype.writeBigInt64BE = defineBigIntMethod(function writeBigInt64BE (value, offset = 0) {
return wrtBigUInt64BE(this, value, offset, -0x8000000000000000n, 0x7fffffffffffffffn)
})

function checkIEEE754 (buf, value, offset, ext, max, min) {
if (offset + ext > buf.length) throw new RangeError('Index out of range')
if (offset < 0) throw new RangeError('Index out of range')
Expand Down Expand Up @@ -1635,6 +1777,139 @@ Buffer.prototype.fill = function fill (val, start, end, encoding) {
return this
}

// CUSTOM ERRORS
// =============

// Simplified versions from Node, changed for Buffer-only usage
var errors = {}
function E (sym, getMessage, Base) {
errors[sym] = class NodeError extends Base {
constructor () {
super()

Object.defineProperty(this, 'message', {
value: getMessage.apply(this, arguments),
writable: true,
configurable: true
})

// Add the error code to the name to include it in the stack trace.
this.name = `${this.name} [${sym}]`
// Access the stack to generate the error message including the error code
// from the name.
this.stack // eslint-disable-line no-unused-expressions
// Reset the name to the actual name.
delete this.name
}

get code () {
return sym
}

set code (value) {
Object.defineProperty(this, 'code', {
configurable: true,
enumerable: true,
value,
writable: true
})
}

toString () {
return `${this.name} [${sym}]: ${this.message}`
}
}
}

E('ERR_BUFFER_OUT_OF_BOUNDS',
function (name) {
if (name) {
return `${name} is outside of buffer bounds`
}

return 'Attempt to access memory outside buffer bounds'
}, RangeError)
E('ERR_INVALID_ARG_TYPE',
function (name, actual) {
return `The "${name}" argument must be of type number. Received type ${typeof actual}`
}, TypeError)
E('ERR_OUT_OF_RANGE',
function (str, range, input) {
let msg = `The value of "${str}" is out of range.`
let received = input
if (Number.isInteger(input) && Math.abs(input) > 2 ** 32) {
received = addNumericalSeparator(String(input))
} else if (typeof input === 'bigint') {
received = String(input)
if (input > 2n ** 32n || input < -(2n ** 32n)) {
received = addNumericalSeparator(received)
}
received += 'n'
}
msg += ` It must be ${range}. Received ${received}`
return msg
}, RangeError)

function addNumericalSeparator (val) {
let res = ''
let i = val.length
const start = val[0] === '-' ? 1 : 0
for (; i >= start + 4; i -= 3) {
res = `_${val.slice(i - 3, i)}${res}`
}
return `${val.slice(0, i)}${res}`
}

// CHECK FUNCTIONS
// ===============

function checkBounds (buf, offset, byteLength) {
validateNumber(offset, 'offset')
if (buf[offset] === undefined || buf[offset + byteLength] === undefined) {
boundsError(offset, buf.length - (byteLength + 1))
}
}

function checkIntBI (value, min, max, buf, offset, byteLength) {
if (value > max || value < min) {
const n = typeof min === 'bigint' ? 'n' : ''
let range
if (byteLength > 3) {
if (min === 0 || min === 0n) {
range = `>= 0${n} and < 2${n} ** ${(byteLength + 1) * 8}${n}`
} else {
range = `>= -(2${n} ** ${(byteLength + 1) * 8 - 1}${n}) and < 2 ** ` +
`${(byteLength + 1) * 8 - 1}${n}`
}
} else {
range = `>= ${min}${n} and <= ${max}${n}`
}
throw new errors.ERR_OUT_OF_RANGE('value', range, value)
}
checkBounds(buf, offset, byteLength)
}

function validateNumber (value, name) {
if (typeof value !== 'number') {
throw new errors.ERR_INVALID_ARG_TYPE(name, 'number', value)
}
}

function boundsError (value, length, type) {
if (Math.floor(value) !== value) {
validateNumber(value, type)
throw new errors.ERR_OUT_OF_RANGE(type || 'offset', 'an integer', value)
}

if (length < 0) {
throw new errors.ERR_BUFFER_OUT_OF_BOUNDS()
}

throw new errors.ERR_OUT_OF_RANGE(type || 'offset',
`>= ${type ? 1 : 0} and <= ${length}`,
value)
}

// HELPER FUNCTIONS
// ================

Expand Down Expand Up @@ -1797,3 +2072,12 @@ var hexSliceLookupTable = (function () {
}
return table
})()

// Return not function with Error if BigInt not supported
function defineBigIntMethod (fn) {
return typeof BigInt === 'undefined' ? BufferBigIntNotDefined : fn
}

function BufferBigIntNotDefined () {
throw new Error('BigInt not supported')
}
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@
"update-authors": "./bin/update-authors.sh"
},
"standard": {
"globals": [
"BigInt"
],
"ignore": [
"test/node/**/*.js",
"test/common.js",
Expand Down
57 changes: 57 additions & 0 deletions test/node/test-buffer-bigint64.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use strict'
var Buffer = require('../../').Buffer
const assert = require('assert')

const buf = Buffer.allocUnsafe(8)

;['LE', 'BE'].forEach(function(endianness) {
// Should allow simple BigInts to be written and read
let val = 123456789n
buf['writeBigInt64' + endianness](val, 0)
let rtn = buf['readBigInt64' + endianness](0)
assert.strictEqual(val, rtn)

// Should allow INT64_MAX to be written and read
val = 0x7fffffffffffffffn
buf['writeBigInt64' + endianness](val, 0)
rtn = buf['readBigInt64' + endianness](0)
assert.strictEqual(val, rtn)

// Should read and write a negative signed 64-bit integer
val = -123456789n
buf['writeBigInt64' + endianness](val, 0)
assert.strictEqual(val, buf['readBigInt64' + endianness](0))

// Should read and write an unsigned 64-bit integer
val = 123456789n
buf['writeBigUInt64' + endianness](val, 0)
assert.strictEqual(val, buf['readBigUInt64' + endianness](0))

// Should throw a RangeError upon INT64_MAX+1 being written
assert.throws(function() {
const val = 0x8000000000000000n
buf['writeBigInt64' + endianness](val, 0)
}, RangeError)

// Should throw a RangeError upon UINT64_MAX+1 being written
assert.throws(function() {
const val = 0x10000000000000000n
buf['writeBigUInt64' + endianness](val, 0)
}, function(err) {
assert(err instanceof RangeError)
assert(err.code === 'ERR_OUT_OF_RANGE')
assert(err.message === 'The value of "value" is out of range. It must be ' +
'>= 0n and < 2n ** 64n. Received 18_446_744_073_709_551_616n')
return true;
})

// Should throw a TypeError upon invalid input
assert.throws(function() {
buf['writeBigInt64' + endianness]('bad', 0)
}, TypeError)

// Should throw a TypeError upon invalid input
assert.throws(function() {
buf['writeBigUInt64' + endianness]('bad', 0)
}, TypeError)
})