'use strict'; const assert = require('assert'); const inspect = require('util').inspect; const debug = require('debug')('ref'); const os = require('os'); const path = require('path'); exports = module.exports = require('node-gyp-build')(path.join(__dirname, '..')); exports.endianness = os.endianness(); /** * A `Buffer` that references the C NULL pointer. That is, its memory address * points to 0. Its `length` is 0 because accessing any data from this buffer * would cause a _segmentation fault_. * * ``` * console.log(ref.NULL); * * ``` * * @name NULL * @type Buffer */ /** * A string that represents the native endianness of the machine's processor. * The possible values are either `"LE"` or `"BE"`. * * ``` * console.log(ref.endianness); * 'LE' * ``` * * @name endianness * @type String */ /** * Accepts a `Buffer` instance and returns the memory address of the buffer * instance. Returns a JavaScript Number, which can't hold 64-bit integers, * so this function is unsafe on 64-bit systems. * ``` * console.log(ref.address(new Buffer(1))); * 4320233616 * * console.log(ref.address(ref.NULL))); * 0 * ``` * * @param {Buffer} buffer The buffer to get the memory address of. * @return {Number} The memory address the buffer instance. * @name address * @type method */ /** * Accepts a `Buffer` instance and returns _true_ if the buffer represents the * NULL pointer, _false_ otherwise. * * ``` * console.log(ref.isNull(new Buffer(1))); * false * * console.log(ref.isNull(ref.NULL)); * true * ``` * * @param {Buffer} buffer The buffer to check for NULL. * @return {Boolean} true or false. * @name isNull * @type method */ /** * Reads a JavaScript Object that has previously been written to the given * _buffer_ at the given _offset_. * * ``` * var obj = { foo: 'bar' }; * var buf = ref.alloc('Object', obj); * * var obj2 = ref.readObject(buf, 0); * console.log(obj === obj2); * true * ``` * * @param {Buffer} buffer The buffer to read an Object from. * @param {Number} offset The offset to begin reading from. * @return {Object} The Object that was read from _buffer_. * @name readObject * @type method */ /** * Reads a Buffer instance from the given _buffer_ at the given _offset_. * The _size_ parameter specifies the `length` of the returned Buffer instance, * which defaults to __0__. * * ``` * var buf = new Buffer('hello world'); * var pointer = ref.alloc('pointer', buf); * * var buf2 = ref.readPointer(pointer, 0, buf.length); * console.log(buf2.toString()); * 'hello world' * ``` * * @param {Buffer} buffer The buffer to read a Buffer from. * @param {Number} offset The offset to begin reading from. * @param {Number} length (optional) The length of the returned Buffer. Defaults to 0. * @return {Buffer} The Buffer instance that was read from _buffer_. * @name readPointer * @type method */ /** * Returns a JavaScript String read from _buffer_ at the given _offset_. The * C String is read until the first NULL byte, which indicates the end of the * String. * * This function can read beyond the `length` of a Buffer. * * ``` * var buf = new Buffer('hello\0world\0'); * * var str = ref.readCString(buf, 0); * console.log(str); * 'hello' * ``` * * @param {Buffer} buffer The buffer to read a Buffer from. * @param {Number} offset The offset to begin reading from. * @return {String} The String that was read from _buffer_. * @name readCString * @type method */ /** * Returns a big-endian signed 64-bit int read from _buffer_ at the given * _offset_. * * If the returned value will fit inside a JavaScript Number without losing * precision, then a Number is returned, otherwise a String is returned. * * ``` * var buf = ref.alloc('int64'); * ref.writeInt64BE(buf, 0, '9223372036854775807'); * * var val = ref.readInt64BE(buf, 0) * console.log(val) * '9223372036854775807' * ``` * * @param {Buffer} buffer The buffer to read a Buffer from. * @param {Number} offset The offset to begin reading from. * @return {Number|String} The Number or String that was read from _buffer_. * @name readInt64BE * @type method */ /** * Returns a little-endian signed 64-bit int read from _buffer_ at the given * _offset_. * * If the returned value will fit inside a JavaScript Number without losing * precision, then a Number is returned, otherwise a String is returned. * * ``` * var buf = ref.alloc('int64'); * ref.writeInt64LE(buf, 0, '9223372036854775807'); * * var val = ref.readInt64LE(buf, 0) * console.log(val) * '9223372036854775807' * ``` * * @param {Buffer} buffer The buffer to read a Buffer from. * @param {Number} offset The offset to begin reading from. * @return {Number|String} The Number or String that was read from _buffer_. * @name readInt64LE * @type method */ /** * Returns a big-endian unsigned 64-bit int read from _buffer_ at the given * _offset_. * * If the returned value will fit inside a JavaScript Number without losing * precision, then a Number is returned, otherwise a String is returned. * * ``` * var buf = ref.alloc('uint64'); * ref.writeUInt64BE(buf, 0, '18446744073709551615'); * * var val = ref.readUInt64BE(buf, 0) * console.log(val) * '18446744073709551615' * ``` * * @param {Buffer} buffer The buffer to read a Buffer from. * @param {Number} offset The offset to begin reading from. * @return {Number|String} The Number or String that was read from _buffer_. * @name readUInt64BE * @type method */ /** * Returns a little-endian unsigned 64-bit int read from _buffer_ at the given * _offset_. * * If the returned value will fit inside a JavaScript Number without losing * precision, then a Number is returned, otherwise a String is returned. * * ``` * var buf = ref.alloc('uint64'); * ref.writeUInt64LE(buf, 0, '18446744073709551615'); * * var val = ref.readUInt64LE(buf, 0) * console.log(val) * '18446744073709551615' * ``` * * @param {Buffer} buffer The buffer to read a Buffer from. * @param {Number} offset The offset to begin reading from. * @return {Number|String} The Number or String that was read from _buffer_. * @name readUInt64LE * @type method */ /** * Writes the _input_ Number or String as a big-endian signed 64-bit int into * _buffer_ at the given _offset_. * * ``` * var buf = ref.alloc('int64'); * ref.writeInt64BE(buf, 0, '9223372036854775807'); * ``` * * @param {Buffer} buffer The buffer to write to. * @param {Number} offset The offset to begin writing from. * @param {Number|String} input This String or Number which gets written. * @name writeInt64BE * @type method */ /** * Writes the _input_ Number or String as a little-endian signed 64-bit int into * _buffer_ at the given _offset_. * * ``` * var buf = ref.alloc('int64'); * ref.writeInt64LE(buf, 0, '9223372036854775807'); * ``` * * @param {Buffer} buffer The buffer to write to. * @param {Number} offset The offset to begin writing from. * @param {Number|String} input This String or Number which gets written. * @name writeInt64LE * @type method */ /** * Writes the _input_ Number or String as a big-endian unsigned 64-bit int into * _buffer_ at the given _offset_. * * ``` * var buf = ref.alloc('uint64'); * ref.writeUInt64BE(buf, 0, '18446744073709551615'); * ``` * * @param {Buffer} buffer The buffer to write to. * @param {Number} offset The offset to begin writing from. * @param {Number|String} input This String or Number which gets written. * @name writeUInt64BE * @type method */ /** * Writes the _input_ Number or String as a little-endian unsigned 64-bit int * into _buffer_ at the given _offset_. * * ``` * var buf = ref.alloc('uint64'); * ref.writeUInt64LE(buf, 0, '18446744073709551615'); * ``` * * @param {Buffer} buffer The buffer to write to. * @param {Number} offset The offset to begin writing from. * @param {Number|String} input This String or Number which gets written. * @name writeUInt64LE * @type method */ /** * Returns a new clone of the given "type" object, with its * `indirection` level incremented by **1**. * * Say you wanted to create a type representing a `void *`: * * ``` * var voidPtrType = ref.refType(ref.types.void); * ``` * * @param {Object|String} type The "type" object to create a reference type from. Strings get coerced first. * @return {Object} The new "type" object with its `indirection` incremented by 1. */ exports.refType = function refType (type) { const _type = exports.coerceType(type); const rtn = Object.create(_type); rtn.indirection++; if (_type.name) { Object.defineProperty(rtn, 'name', { value: _type.name + '*', configurable: true, enumerable: true, writable: true }); } return rtn; } /** * Returns a new clone of the given "type" object, with its * `indirection` level decremented by 1. * * @param {Object|String} type The "type" object to create a dereference type from. Strings get coerced first. * @return {Object} The new "type" object with its `indirection` decremented by 1. */ exports.derefType = function derefType (type) { const _type = exports.coerceType(type); if (_type.indirection === 1) { throw new Error('Cannot create deref\'d type for type with indirection 1'); } let rtn = Object.getPrototypeOf(_type); if (rtn.indirection !== _type.indirection - 1) { // slow case rtn = Object.create(_type); rtn.indirection--; } return rtn; } /** * Coerces a "type" object from a String or an actual "type" object. String values * are looked up from the `ref.types` Object. So: * * * `"int"` gets coerced into `ref.types.int`. * * `"int *"` gets translated into `ref.refType(ref.types.int)` * * `ref.types.int` gets translated into `ref.types.int` (returns itself) * * Throws an Error if no valid "type" object could be determined. Most `ref` * functions use this function under the hood, so anywhere a "type" object is * expected, a String may be passed as well, including simply setting the * `buffer.type` property. * * ``` * var type = ref.coerceType('int **'); * * console.log(type.indirection); * 3 * ``` * * @param {Object|String} type The "type" Object or String to coerce. * @return {Object} A "type" object */ exports.coerceType = function coerceType (type) { let rtn = type; if (typeof rtn === 'string') { rtn = exports.types[type]; if (rtn) return rtn; // strip whitespace rtn = type.replace(/\s+/g, '').toLowerCase(); if (rtn === 'pointer') { // legacy "pointer" being used :( rtn = exports.refType(exports.types.void); // void * } else if (rtn === 'string') { rtn = exports.types.CString; // special char * type } else { var refCount = 0; rtn = rtn.replace(/\*/g, function() { refCount++; return ''; }); // allow string names to be passed in rtn = exports.types[rtn]; if (refCount > 0) { if (!(rtn && 'size' in rtn && 'indirection' in rtn)) { throw new TypeError('could not determine a proper "type" from: ' + inspect(type)); } for (let i = 0; i < refCount; i++) { rtn = exports.refType(rtn); } } } } if (!(rtn && 'size' in rtn && 'indirection' in rtn)) { throw new TypeError('could not determine a proper "type" from: ' + inspect(type)); } return rtn; } /** * Returns the "type" property of the given Buffer. * Creates a default type for the buffer when none exists. * * @param {Buffer} buffer The Buffer instance to get the "type" object from. * @return {Object} The "type" object from the given Buffer. */ exports.getType = function getType (buffer) { if (!buffer.type) { debug('WARN: no "type" found on buffer, setting default "type"', buffer); buffer.type = {}; buffer.type.size = buffer.length; buffer.type.indirection = 1; buffer.type.get = function get () { throw new Error('unknown "type"; cannot get()'); }; buffer.type.set = function set () { throw new Error('unknown "type"; cannot set()'); }; } return exports.coerceType(buffer.type); } /** * Calls the `get()` function of the Buffer's current "type" (or the * passed in _type_ if present) at the given _offset_. * * This function handles checking the "indirection" level and returning a * proper "dereferenced" Bufffer instance when necessary. * * @param {Buffer} buffer The Buffer instance to read from. * @param {Number} offset (optional) The offset on the Buffer to start reading from. Defaults to 0. * @param {Object|String} type (optional) The "type" object to use when reading. Defaults to calling `getType()` on the buffer. * @return {?} Whatever value the "type" used when reading returns. */ exports.get = function get (buffer, offset, type) { if (!offset) { offset = 0; } if (type) { type = exports.coerceType(type); } else { type = exports.getType(buffer); } debug('get(): (offset: %d)', offset, buffer); assert(type.indirection > 0, `"indirection" level must be at least 1, saw ${type.indirection}`); if (type.indirection === 1) { // need to check "type" return type.get(buffer, offset); } else { // need to create a deref'd Buffer const size = type.indirection === 2 ? type.size : exports.sizeof.pointer; const reference = exports.readPointer(buffer, offset, size); reference.type = exports.derefType(type); return reference; } } /** * Calls the `set()` function of the Buffer's current "type" (or the * passed in _type_ if present) at the given _offset_. * * This function handles checking the "indirection" level writing a pointer rather * than calling the `set()` function if the indirection is greater than 1. * * @param {Buffer} buffer The Buffer instance to write to. * @param {Number} offset The offset on the Buffer to start writing to. * @param {?} value The value to write to the Buffer instance. * @param {Object|String} type (optional) The "type" object to use when reading. Defaults to calling `getType()` on the buffer. */ exports.set = function set (buffer, offset, value, type) { if (!offset) { offset = 0; } if (type) { type = exports.coerceType(type); } else { type = exports.getType(buffer); } debug('set(): (offset: %d)', offset, buffer, value); assert(type.indirection >= 1, '"indirection" level must be at least 1'); if (type.indirection === 1) { type.set(buffer, offset, value); } else { exports.writePointer(buffer, offset, value); } } /** * Returns a new Buffer instance big enough to hold `type`, * with the given `value` written to it. * * ``` js * var intBuf = ref.alloc(ref.types.int) * var int_with_4 = ref.alloc(ref.types.int, 4) * ``` * * @param {Object|String} type The "type" object to allocate. Strings get coerced first. * @param {?} value (optional) The initial value set on the returned Buffer, using _type_'s `set()` function. * @return {Buffer} A new Buffer instance with it's `type` set to "type", and (optionally) "value" written to it. */ exports.alloc = function alloc (_type, value) { var type = exports.coerceType(_type); debug('allocating Buffer for type with "size"', type.size); let size; if (type.indirection === 1) { size = type.size; } else { size = exports.sizeof.pointer; } const buffer = Buffer.alloc(size); buffer.type = type; if (arguments.length >= 2) { debug('setting value on allocated buffer', value); exports.set(buffer, 0, value, type); } return buffer; } /** * Returns a new `Buffer` instance with the given String written to it with the * given encoding (defaults to __'utf8'__). The buffer is 1 byte longer than the * string itself, and is NUL terminated. * * ``` * var buf = ref.allocCString('hello world'); * * console.log(buf.toString()); * 'hello world\u0000' * ``` * * @param {String} string The JavaScript string to be converted to a C string. * @param {String} encoding (optional) The encoding to use for the C string. Defaults to __'utf8'__. * @return {Buffer} The new `Buffer` instance with the specified String wrtten to it, and a trailing NUL byte. */ exports.allocCString = function allocCString (string, encoding) { if (null == string || (Buffer.isBuffer(string) && exports.isNull(string))) { return exports.NULL; } const size = Buffer.byteLength(string, encoding) + 1; const buffer = Buffer.allocUnsafe(size); exports.writeCString(buffer, 0, string, encoding); buffer.type = charPtrType; return buffer; } /** * Writes the given string as a C String (NULL terminated) to the given buffer * at the given offset. "encoding" is optional and defaults to __'utf8'__. * * Unlike `readCString()`, this function requires the buffer to actually have the * proper length. * * @param {Buffer} buffer The Buffer instance to write to. * @param {Number} offset The offset of the buffer to begin writing at. * @param {String} string The JavaScript String to write that will be written to the buffer. * @param {String} encoding (optional) The encoding to read the C string as. Defaults to __'utf8'__. */ exports.writeCString = function writeCString (buffer, offset, string, encoding) { assert(Buffer.isBuffer(buffer), 'expected a Buffer as the first argument'); assert.strictEqual('string', typeof string, 'expected a "string" as the third argument'); if (!offset) { offset = 0; } if (!encoding) { encoding = 'utf8'; } const size = buffer.length - offset - 1; const len = buffer.write(string, offset, size, encoding); buffer.writeUInt8(0, offset + len); // NUL terminate } exports['readInt64' + exports.endianness] = exports.readInt64; exports['readUInt64' + exports.endianness] = exports.readUInt64; exports['writeInt64' + exports.endianness] = exports.writeInt64; exports['writeUInt64' + exports.endianness] = exports.writeUInt64; var opposite = exports.endianness == 'LE' ? 'BE' : 'LE'; var int64temp = Buffer.alloc(exports.sizeof.int64); var uint64temp = Buffer.alloc(exports.sizeof.uint64); exports['readInt64' + opposite] = function (buffer, offset) { for (let i = 0; i < exports.sizeof.int64; i++) { int64temp[i] = buffer[offset + exports.sizeof.int64 - i - 1]; } return exports.readInt64(int64temp, 0); } exports['readUInt64' + opposite] = function (buffer, offset) { for (let i = 0; i < exports.sizeof.uint64; i++) { uint64temp[i] = buffer[offset + exports.sizeof.uint64 - i - 1]; } return exports.readUInt64(uint64temp, 0); } exports['writeInt64' + opposite] = function (buffer, offset, value) { exports.writeInt64(int64temp, 0, value); for (let i = 0; i < exports.sizeof.int64; i++) { buffer[offset + i] = int64temp[exports.sizeof.int64 - i - 1]; } } exports['writeUInt64' + opposite] = function (buffer, offset, value) { exports.writeUInt64(uint64temp, 0, value); for (let i = 0; i < exports.sizeof.uint64; i++) { buffer[offset + i] = uint64temp[exports.sizeof.uint64 - i - 1]; } } /** * `ref()` accepts a Buffer instance and returns a new Buffer * instance that is "pointer" sized and has its data pointing to the given * Buffer instance. Essentially the created Buffer is a "reference" to the * original pointer, equivalent to the following C code: * * ``` c * char *buf = buffer; * char **ref = &buf; * ``` * * @param {Buffer} buffer A Buffer instance to create a reference to. * @return {Buffer} A new Buffer instance pointing to _buffer_. */ exports.ref = function ref (buffer) { debug('creating a reference to buffer', buffer); var type = exports.refType(exports.getType(buffer)); return exports.alloc(type, buffer); } /** * Accepts a Buffer instance and attempts to "dereference" it. * That is, first it checks the `indirection` count of _buffer_'s "type", and if * it's greater than __1__ then it merely returns another Buffer, but with one * level less `indirection`. * * When _buffer_'s indirection is at __1__, then it checks for `buffer.type` * which should be an Object with its own `get()` function. * * ``` * var buf = ref.alloc('int', 6); * * var val = ref.deref(buf); * console.log(val); * 6 * ``` * * * @param {Buffer} buffer A Buffer instance to dereference. * @return {?} The returned value after dereferencing _buffer_. */ exports.deref = function deref (buffer) { debug('dereferencing buffer', buffer); return exports.get(buffer); } const kAttachedRefs = Symbol('attached'); /** * Attaches _object_ to _buffer_ such that it prevents _object_ from being garbage * collected until _buffer_ does. * * @param {Buffer} buffer A Buffer instance to attach _object_ to. * @param {Object|Buffer} object An Object or Buffer to prevent from being garbage collected until _buffer_ does. * @api private */ exports._attach = function _attach (buf, obj) { if (!buf[kAttachedRefs]) { buf[kAttachedRefs] = []; } buf[kAttachedRefs].push(obj); } /** * @param {Buffer} buffer * @param {Number} offset * @param {Object} object * @name _writeObject * @api private */ /** * Writes a pointer to _object_ into _buffer_ at the specified _offset. * * This function "attaches" _object_ to _buffer_ to prevent it from being garbage * collected. * * ``` * var buf = ref.alloc('Object'); * ref.writeObject(buf, 0, { foo: 'bar' }); * * ``` * * @param {Buffer} buffer A Buffer instance to write _object_ to. * @param {Number} offset The offset on the Buffer to start writing at. * @param {Object} object The Object to be written into _buffer_. */ exports.writeObject = function writeObject (buf, offset, obj) { debug('writing Object to buffer', buf, offset, obj); exports._writeObject(buf, offset, obj); exports._attach(buf, obj); } /** * Same as `ref.writePointer()`, except that this version does not attach * _pointer_ to _buffer_, which is potentially unsafe if the garbage collector * runs. * * @param {Buffer} buffer A Buffer instance to write _pointer to. * @param {Number} offset The offset on the Buffer to start writing at. * @param {Buffer} pointer The Buffer instance whose memory address will be written to _buffer_. * @name _writePointer * @api private */ /** * Writes the memory address of _pointer_ to _buffer_ at the specified _offset_. * * This function "attaches" _object_ to _buffer_ to prevent it from being garbage * collected. * * ``` * var someBuffer = new Buffer('whatever'); * var buf = ref.alloc('pointer'); * ref.writePointer(buf, 0, someBuffer); * ``` * * @param {Buffer} buffer A Buffer instance to write _pointer to. * @param {Number} offset The offset on the Buffer to start writing at. * @param {Buffer} pointer The Buffer instance whose memory address will be written to _buffer_. */ exports.writePointer = function writePointer (buf, offset, ptr) { debug('writing pointer to buffer', buf, offset, ptr); // Passing true as a fourth parameter does an a stronger // version of attach which ensures ptr is only collected after // the finalizer for buf has run. See // https://github.com/node-ffi-napi/ref-napi/issues/54 // for why this is necessary exports._writePointer(buf, offset, ptr, true); }; /** * Same as `ref.reinterpret()`, except that this version does not attach * _buffer_ to the returned Buffer, which is potentially unsafe if the * garbage collector runs. * * @param {Buffer} buffer A Buffer instance to base the returned Buffer off of. * @param {Number} size The `length` property of the returned Buffer. * @param {Number} offset The offset of the Buffer to begin from. * @return {Buffer} A new Buffer instance with the same memory address as _buffer_, and the requested _size_. * @name _reinterpret * @api private */ /** * Returns a new Buffer instance with the specified _size_, with the same memory * address as _buffer_. * * This function "attaches" _buffer_ to the returned Buffer to prevent it from * being garbage collected. * * @param {Buffer} buffer A Buffer instance to base the returned Buffer off of. * @param {Number} size The `length` property of the returned Buffer. * @param {Number} offset The offset of the Buffer to begin from. * @return {Buffer} A new Buffer instance with the same memory address as _buffer_, and the requested _size_. */ exports.reinterpret = function reinterpret (buffer, size, offset) { debug('reinterpreting buffer to "%d" bytes', size); const rtn = exports._reinterpret(buffer, size, offset || 0); exports._attach(rtn, buffer); return rtn; } /** * Same as `ref.reinterpretUntilZeros()`, except that this version does not * attach _buffer_ to the returned Buffer, which is potentially unsafe if the * garbage collector runs. * * @param {Buffer} buffer A Buffer instance to base the returned Buffer off of. * @param {Number} size The number of sequential, aligned `NULL` bytes that are required to terminate the buffer. * @param {Number} offset The offset of the Buffer to begin from. * @return {Buffer} A new Buffer instance with the same memory address as _buffer_, and a variable `length` that is terminated by _size_ NUL bytes. * @name _reinterpretUntilZeros * @api private */ /** * Accepts a `Buffer` instance and a number of `NULL` bytes to read from the * pointer. This function will scan past the boundary of the Buffer's `length` * until it finds `size` number of aligned `NULL` bytes. * * This is useful for finding the end of NUL-termintated array or C string. For * example, the `readCString()` function _could_ be implemented like: * * ``` * function readCString (buf) { * return ref.reinterpretUntilZeros(buf, 1).toString('utf8') * } * ``` * * This function "attaches" _buffer_ to the returned Buffer to prevent it from * being garbage collected. * * @param {Buffer} buffer A Buffer instance to base the returned Buffer off of. * @param {Number} size The number of sequential, aligned `NULL` bytes are required to terminate the buffer. * @param {Number} offset The offset of the Buffer to begin from. * @return {Buffer} A new Buffer instance with the same memory address as _buffer_, and a variable `length` that is terminated by _size_ NUL bytes. */ exports.reinterpretUntilZeros = function reinterpretUntilZeros (buffer, size, offset) { debug('reinterpreting buffer to until "%d" NULL (0) bytes are found', size); var rtn = exports._reinterpretUntilZeros(buffer, size, offset || 0); exports._attach(rtn, buffer); return rtn; }; // the built-in "types" const types = exports.types = {}; /** * The `void` type. * * @section types */ types.void = { size: 0, indirection: 1, get: function get (buf, offset) { debug('getting `void` type (returns `null`)'); return null; }, set: function set (buf, offset, val) { debug('setting `void` type (no-op)'); } }; /** * The `int8` type. */ types.int8 = { size: exports.sizeof.int8, indirection: 1, get: function get (buf, offset) { return buf.readInt8(offset || 0); }, set: function set (buf, offset, val) { if (typeof val === 'string') { val = val.charCodeAt(0); } return buf.writeInt8(val, offset || 0); } }; /** * The `uint8` type. */ types.uint8 = { size: exports.sizeof.uint8, indirection: 1, get: function get (buf, offset) { return buf.readUInt8(offset || 0); }, set: function set (buf, offset, val) { if (typeof val === 'string') { val = val.charCodeAt(0); } return buf.writeUInt8(val, offset || 0); } }; /** * The `int16` type. */ types.int16 = { size: exports.sizeof.int16, indirection: 1, get: function get (buf, offset) { return buf['readInt16' + exports.endianness](offset || 0); }, set: function set (buf, offset, val) { return buf['writeInt16' + exports.endianness](val, offset || 0); } } /** * The `uint16` type. */ types.uint16 = { size: exports.sizeof.uint16, indirection: 1, get: function get (buf, offset) { return buf['readUInt16' + exports.endianness](offset || 0); }, set: function set (buf, offset, val) { return buf['writeUInt16' + exports.endianness](val, offset || 0); } } /** * The `int32` type. */ types.int32 = { size: exports.sizeof.int32, indirection: 1, get: function get (buf, offset) { return buf['readInt32' + exports.endianness](offset || 0); }, set: function set (buf, offset, val) { return buf['writeInt32' + exports.endianness](val, offset || 0); } } /** * The `uint32` type. */ types.uint32 = { size: exports.sizeof.uint32, indirection: 1, get: function get (buf, offset) { return buf['readUInt32' + exports.endianness](offset || 0); }, set: function set (buf, offset, val) { return buf['writeUInt32' + exports.endianness](val, offset || 0); } } /** * The `int64` type. */ types.int64 = { size: exports.sizeof.int64, indirection: 1, get: function get (buf, offset) { return buf['readInt64' + exports.endianness](offset || 0); }, set: function set (buf, offset, val) { return buf['writeInt64' + exports.endianness](val, offset || 0); } } /** * The `uint64` type. */ types.uint64 = { size: exports.sizeof.uint64, indirection: 1, get: function get (buf, offset) { return buf['readUInt64' + exports.endianness](offset || 0); }, set: function set (buf, offset, val) { return buf['writeUInt64' + exports.endianness](val, offset || 0); } } /** * The `float` type. */ types.float = { size: exports.sizeof.float, indirection: 1, get: function get (buf, offset) { return buf['readFloat' + exports.endianness](offset || 0); }, set: function set (buf, offset, val) { return buf['writeFloat' + exports.endianness](val, offset || 0); } } /** * The `double` type. */ types.double = { size: exports.sizeof.double, indirection: 1, get: function get (buf, offset) { return buf['readDouble' + exports.endianness](offset || 0) }, set: function set (buf, offset, val) { return buf['writeDouble' + exports.endianness](val, offset || 0) } } /** * The `Object` type. This can be used to read/write regular JS Objects * into raw memory. */ types.Object = { size: exports.sizeof.Object, indirection: 1, get: function get (buf, offset) { return buf.readObject(offset || 0); }, set: function set (buf, offset, val) { return buf.writeObject(val, offset || 0); } } /** * The `CString` (a.k.a `"string"`) type. * * CStrings are a kind of weird thing. We say it's `sizeof(char *)`, and * `indirection` level of 1, which means that we have to return a Buffer that * is pointer sized, and points to a some utf8 string data, so we have to create * a 2nd "in-between" buffer. */ types.CString = { size: exports.sizeof.pointer, alignment: exports.alignof.pointer, indirection: 1, get: function get (buf, offset) { const _buf = exports.readPointer(buf, offset) if (exports.isNull(_buf)) { return null; } return exports.readCString(_buf, 0); }, set: function set (buf, offset, val) { let _buf if (Buffer.isBuffer(val)) { _buf = val; } else { // assume string _buf = exports.allocCString(val); } return exports.writePointer(buf, offset, _buf); } } // alias Utf8String var utfstringwarned = false; Object.defineProperty(types, 'Utf8String', { enumerable: false, configurable: true, get: function() { if (!utfstringwarned) { utfstringwarned = true; console.error('"Utf8String" type is deprecated, use "CString" instead'); } return types.CString; } }); /** * The `bool` type. * * Wrapper type around `types.uint8` that accepts/returns `true` or * `false` Boolean JavaScript values. * * @name bool * */ /** * The `byte` type. * * @name byte */ /** * The `char` type. * * @name char */ /** * The `uchar` type. * * @name uchar */ /** * The `short` type. * * @name short */ /** * The `ushort` type. * * @name ushort */ /** * The `int` type. * * @name int */ /** * The `uint` type. * * @name uint */ /** * The `long` type. * * @name long */ /** * The `ulong` type. * * @name ulong */ /** * The `longlong` type. * * @name longlong */ /** * The `ulonglong` type. * * @name ulonglong */ /** * The `size_t` type. * * @name size_t */ // "typedef"s for the variable-sized types [ 'bool', 'byte', 'char', 'uchar', 'short', 'ushort', 'int', 'uint', 'long', 'ulong', 'longlong', 'ulonglong', 'size_t' ].forEach(name => { const unsigned = name === 'bool' || name === 'byte' || name === 'size_t' || name[0] === 'u'; const size = exports.sizeof[name]; assert(size >= 1 && size <= 8); let typeName = 'int' + (size * 8); if (unsigned) { typeName = 'u' + typeName; } const type = exports.types[typeName]; assert(type); exports.types[name] = Object.create(type); }); // set the "alignment" property on the built-in types Object.keys(exports.alignof).forEach((name) => { if (name === 'pointer') return; exports.types[name].alignment = exports.alignof[name]; assert(exports.types[name].alignment > 0); }); // make the `bool` type work with JS true/false values exports.types.bool.get = (function (_get) { return function get (buf, offset) { return _get(buf, offset) ? true : false; } })(exports.types.bool.get); exports.types.bool.set = (function (_set) { return function set (buf, offset, val) { if (typeof val !== 'number') { val = val ? 1 : 0; } return _set(buf, offset, val); } })(exports.types.bool.set); /*! * Set the `name` property of the types. Used for debugging... */ Object.keys(exports.types).forEach((name) => { exports.types[name].name = name; }); /*! * This `char *` type is used by "allocCString()" above. */ const charPtrType = exports.refType(exports.types.char); /*! * Set the `type` property of the `NULL` pointer Buffer object. */ exports.NULL.type = exports.types.void; /** * `NULL_POINTER` is a pointer-sized `Buffer` instance pointing to `NULL`. * Conceptually, it's equivalent to the following C code: * * ``` c * char *null_pointer; * null_pointer = NULL; * ``` * * @type Buffer */ exports.NULL_POINTER = exports.ref(exports.NULL); /** * All these '...' comment blocks below are for the documentation generator. * * @section buffer */ Buffer.prototype.address = function address () { return exports.address(this, 0); }; /** * ... */ Buffer.prototype.hexAddress = function hexAddress () { return exports.hexAddress(this, 0); }; /** * ... */ Buffer.prototype.isNull = function isNull () { return exports.isNull(this, 0); }; /** * ... */ Buffer.prototype.ref = function ref () { return exports.ref(this); }; /** * ... */ Buffer.prototype.deref = function deref () { return exports.deref(this); }; /** * ... */ Buffer.prototype.readObject = function readObject (offset) { return exports.readObject(this, offset); }; /** * ... */ Buffer.prototype.writeObject = function writeObject (obj, offset) { return exports.writeObject(this, offset, obj); }; /** * ... */ Buffer.prototype.readPointer = function readPointer (offset, size) { return exports.readPointer(this, offset, size); }; /** * ... */ Buffer.prototype.writePointer = function writePointer (ptr, offset) { return exports.writePointer(this, offset, ptr); }; /** * ... */ Buffer.prototype.readCString = function readCString (offset) { return exports.readCString(this, offset); }; /** * ... */ Buffer.prototype.writeCString = function writeCString (string, offset, encoding) { return exports.writeCString(this, offset, string, encoding); }; /** * ... */ Buffer.prototype.readInt64BE = function readInt64BE (offset) { return exports.readInt64BE(this, offset); }; /** * ... */ Buffer.prototype.writeInt64BE = function writeInt64BE (val, offset) { return exports.writeInt64BE(this, offset, val); }; /** * ... */ Buffer.prototype.readUInt64BE = function readUInt64BE (offset) { return exports.readUInt64BE(this, offset); }; /** * ... */ Buffer.prototype.writeUInt64BE = function writeUInt64BE (val, offset) { return exports.writeUInt64BE(this, offset, val); }; /** * ... */ Buffer.prototype.readInt64LE = function readInt64LE (offset) { return exports.readInt64LE(this, offset); }; /** * ... */ Buffer.prototype.writeInt64LE = function writeInt64LE (val, offset) { return exports.writeInt64LE(this, offset, val); }; /** * ... */ Buffer.prototype.readUInt64LE = function readUInt64LE (offset) { return exports.readUInt64LE(this, offset); }; /** * ... */ Buffer.prototype.writeUInt64LE = function writeUInt64LE (val, offset) { return exports.writeUInt64LE(this, offset, val); }; /** * ... */ Buffer.prototype.reinterpret = function reinterpret (size, offset) { return exports.reinterpret(this, size, offset); }; /** * ... */ Buffer.prototype.reinterpretUntilZeros = function reinterpretUntilZeros (size, offset) { return exports.reinterpretUntilZeros(this, size, offset); }; /** * `ref` overwrites the default `Buffer#inspect()` function to include the * hex-encoded memory address of the Buffer instance when invoked. * * This is simply a nice-to-have. * * **Before**: * * ``` js * console.log(new Buffer('ref')); * * ``` * * **After**: * * ``` js * console.log(new Buffer('ref')); * * ``` */ var inspectSym = inspect.custom || 'inspect'; /** * in node 6.91, inspect.custom does not give a correct value; so in this case, don't torch the whole process. * fixed in >6.9.2 */ if (Buffer.prototype[inspectSym]) { Buffer.prototype[inspectSym] = overwriteInspect(Buffer.prototype[inspectSym]); } // does SlowBuffer inherit from Buffer? (node >= v0.7.9) if (!(exports.NULL instanceof Buffer)) { debug('extending SlowBuffer\'s prototype since it doesn\'t inherit from Buffer.prototype'); /*! * SlowBuffer convenience methods. */ var SlowBuffer = require('buffer').SlowBuffer; SlowBuffer.prototype.address = Buffer.prototype.address; SlowBuffer.prototype.hexAddress = Buffer.prototype.hexAddress; SlowBuffer.prototype.isNull = Buffer.prototype.isNull; SlowBuffer.prototype.ref = Buffer.prototype.ref; SlowBuffer.prototype.deref = Buffer.prototype.deref; SlowBuffer.prototype.readObject = Buffer.prototype.readObject; SlowBuffer.prototype.writeObject = Buffer.prototype.writeObject; SlowBuffer.prototype.readPointer = Buffer.prototype.readPointer; SlowBuffer.prototype.writePointer = Buffer.prototype.writePointer; SlowBuffer.prototype.readCString = Buffer.prototype.readCString; SlowBuffer.prototype.writeCString = Buffer.prototype.writeCString; SlowBuffer.prototype.reinterpret = Buffer.prototype.reinterpret; SlowBuffer.prototype.reinterpretUntilZeros = Buffer.prototype.reinterpretUntilZeros; SlowBuffer.prototype.readInt64BE = Buffer.prototype.readInt64BE; SlowBuffer.prototype.writeInt64BE = Buffer.prototype.writeInt64BE; SlowBuffer.prototype.readUInt64BE = Buffer.prototype.readUInt64BE; SlowBuffer.prototype.writeUInt64BE = Buffer.prototype.writeUInt64BE; SlowBuffer.prototype.readInt64LE = Buffer.prototype.readInt64LE; SlowBuffer.prototype.writeInt64LE = Buffer.prototype.writeInt64LE; SlowBuffer.prototype.readUInt64LE = Buffer.prototype.readUInt64LE; SlowBuffer.prototype.writeUInt64LE = Buffer.prototype.writeUInt64LE; /** * in node 6.9.1, inspect.custom does not give a correct value; so in this case, don't torch the whole process. * fixed in >6.9.2 */ if (SlowBuffer.prototype[inspectSym]){ SlowBuffer.prototype[inspectSym] = overwriteInspect(SlowBuffer.prototype[inspectSym]); } } function overwriteInspect (inspect) { if (inspect.name === 'refinspect') { return inspect; } else { return function refinspect () { var v = inspect.apply(this, arguments); return v.replace('Buffer', 'Buffer@0x' + this.hexAddress()); } } }