123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- /**
- * An interface for modeling and instantiating C-style data structures. This is
- * not a constructor per-say, but a constructor generator. It takes an array of
- * tuples, the left side being the type, and the right side being a field name.
- * The order should be the same order it would appear in the C-style struct
- * definition. It returns a function that can be used to construct an object that
- * reads and writes to the data structure using properties specified by the
- * initial field list.
- *
- * The only verboten field names are "ref", which is used used on struct
- * instances as a function to retrieve the backing Buffer instance of the
- * struct, and "ref.buffer" which contains the backing Buffer instance.
- *
- *
- * Example:
- *
- * ``` javascript
- * var ref = require('ref')
- * var Struct = require('ref-struct')
- *
- * // create the `char *` type
- * var charPtr = ref.refType(ref.types.char)
- * var int = ref.types.int
- *
- * // create the struct "type" / constructor
- * var PasswordEntry = Struct({
- * 'username': 'string'
- * , 'password': 'string'
- * , 'salt': int
- * })
- *
- * // create an instance of the struct, backed a Buffer instance
- * var pwd = new PasswordEntry()
- * pwd.username = 'ricky'
- * pwd.password = 'rbransonlovesnode.js'
- * pwd.salt = (Math.random() * 1000000) | 0
- *
- * pwd.username // → 'ricky'
- * pwd.password // → 'rbransonlovesnode.js'
- * pwd.salt // → 820088
- * ```
- */
- /**
- * Module dependencies.
- */
- var ref = require('ref')
- var util = require('util')
- var assert = require('assert')
- var debug = require('debug')('ref:struct')
- /**
- * Module exports.
- */
- module.exports = Struct
- /**
- * The Struct "type" meta-constructor.
- */
- function Struct () {
- debug('defining new struct "type"')
- /**
- * This is the "constructor" of the Struct type that gets returned.
- *
- * Invoke it with `new` to create a new Buffer instance backing the struct.
- * Pass it an existing Buffer instance to use that as the backing buffer.
- * Pass in an Object containing the struct fields to auto-populate the
- * struct with the data.
- */
- function StructType (arg, data) {
- if (!(this instanceof StructType)) {
- return new StructType(arg, data)
- }
- debug('creating new struct instance')
- var store
- if (Buffer.isBuffer(arg)) {
- debug('using passed-in Buffer instance to back the struct', arg)
- assert(arg.length >= StructType.size, 'Buffer instance must be at least ' +
- StructType.size + ' bytes to back this struct type')
- store = arg
- arg = data
- } else {
- debug('creating new Buffer instance to back the struct (size: %d)', StructType.size)
- store = new Buffer(StructType.size)
- }
- // set the backing Buffer store
- store.type = StructType
- this['ref.buffer'] = store
- if (arg) {
- for (var key in arg) {
- // hopefully hit the struct setters
- this[key] = arg[key]
- }
- }
- StructType._instanceCreated = true
- }
- // make instances inherit from the `proto`
- StructType.prototype = Object.create(proto, {
- constructor: {
- value: StructType
- , enumerable: false
- , writable: true
- , configurable: true
- }
- })
- StructType.defineProperty = defineProperty
- StructType.toString = toString
- StructType.fields = {}
- var opt = (arguments.length > 0 && arguments[1]) ? arguments[1] : {};
- // Setup the ref "type" interface. The constructor doubles as the "type" object
- StructType.size = 0
- StructType.alignment = 0
- StructType.indirection = 1
- StructType.isPacked = opt.packed ? Boolean(opt.packed) : false
- StructType.get = get
- StructType.set = set
- // Read the fields list and apply all the fields to the struct
- // TODO: Better arg handling... (maybe look at ES6 binary data API?)
- var arg = arguments[0]
- if (Array.isArray(arg)) {
- // legacy API
- arg.forEach(function (a) {
- var type = a[0]
- var name = a[1]
- StructType.defineProperty(name, type)
- })
- } else if (typeof arg === 'object') {
- Object.keys(arg).forEach(function (name) {
- var type = arg[name]
- StructType.defineProperty(name, type)
- })
- }
- return StructType
- }
- /**
- * The "get" function of the Struct "type" interface
- */
- function get (buffer, offset) {
- debug('Struct "type" getter for buffer at offset', buffer, offset)
- if (offset > 0) {
- buffer = buffer.slice(offset)
- }
- return new this(buffer)
- }
- /**
- * The "set" function of the Struct "type" interface
- */
- function set (buffer, offset, value) {
- debug('Struct "type" setter for buffer at offset', buffer, offset, value)
- var isStruct = value instanceof this
- if (isStruct) {
- // optimization: copy the buffer contents directly rather
- // than going through the ref-struct constructor
- value['ref.buffer'].copy(buffer, offset, 0, this.size)
- } else {
- if (offset > 0) {
- buffer = buffer.slice(offset)
- }
- new this(buffer, value)
- }
- }
- /**
- * Custom `toString()` override for struct type instances.
- */
- function toString () {
- return '[StructType]'
- }
- /**
- * Adds a new field to the struct instance with the given name and type.
- * Note that this function will throw an Error if any instances of the struct
- * type have already been created, therefore this function must be called at the
- * beginning, before any instances are created.
- */
- function defineProperty (name, type) {
- debug('defining new struct type field', name)
- // allow string types for convenience
- type = ref.coerceType(type)
- assert(!this._instanceCreated, 'an instance of this Struct type has already ' +
- 'been created, cannot add new "fields" anymore')
- assert.equal('string', typeof name, 'expected a "string" field name')
- assert(type && /object|function/i.test(typeof type) && 'size' in type &&
- 'indirection' in type
- , 'expected a "type" object describing the field type: "' + type + '"')
- assert(type.indirection > 1 || type.size > 0,
- '"type" object must have a size greater than 0')
- assert(!(name in this.prototype), 'the field "' + name +
- '" already exists in this Struct type')
- var field = {
- type: type
- }
- this.fields[name] = field
- // define the getter/setter property
- var desc = { enumerable: true , configurable: true }
- desc.get = function () {
- debug('getting "%s" struct field (offset: %d)', name, field.offset)
- return ref.get(this['ref.buffer'], field.offset, type)
- }
- desc.set = function (value) {
- debug('setting "%s" struct field (offset: %d)', name, field.offset, value)
- return ref.set(this['ref.buffer'], field.offset, value, type)
- }
- // calculate the new size and field offsets
- recalc(this)
- Object.defineProperty(this.prototype, name, desc)
- }
- function recalc (struct) {
- // reset size and alignment
- struct.size = 0
- struct.alignment = 0
- var fieldNames = Object.keys(struct.fields)
- // first loop through is to determine the `alignment` of this struct
- fieldNames.forEach(function (name) {
- var field = struct.fields[name]
- var type = field.type
- var alignment = type.alignment || ref.alignof.pointer
- if (type.indirection > 1) {
- alignment = ref.alignof.pointer
- }
- if (struct.isPacked) {
- struct.alignment = Math.min(struct.alignment || alignment, alignment)
- } else {
- struct.alignment = Math.max(struct.alignment, alignment)
- }
- })
- // second loop through sets the `offset` property on each "field"
- // object, and sets the `struct.size` as we go along
- fieldNames.forEach(function (name) {
- var field = struct.fields[name]
- var type = field.type
- if (null != type.fixedLength) {
- // "ref-array" types set the "fixedLength" prop. don't treat arrays like one
- // contiguous entity. instead, treat them like individual elements in the
- // struct. doing this makes the padding end up being calculated correctly.
- field.offset = addType(type.type)
- for (var i = 1; i < type.fixedLength; i++) {
- addType(type.type)
- }
- } else {
- field.offset = addType(type)
- }
- })
- function addType (type) {
- var offset = struct.size
- var align = type.indirection === 1 ? type.alignment : ref.alignof.pointer
- var padding = struct.isPacked ? 0 : (align - (offset % align)) % align
- var size = type.indirection === 1 ? type.size : ref.sizeof.pointer
- offset += padding
- if (!struct.isPacked) {
- assert.equal(offset % align, 0, "offset should align")
- }
- // adjust the "size" of the struct type
- struct.size = offset + size
- // return the calulated offset
- return offset
- }
- // any final padding?
- var left = struct.size % struct.alignment
- if (left > 0) {
- debug('additional padding to the end of struct:', struct.alignment - left)
- struct.size += struct.alignment - left
- }
- }
- /**
- * this is the custom prototype of Struct type instances.
- */
- var proto = {}
- /**
- * set a placeholder variable on the prototype so that defineProperty() will
- * throw an error if you try to define a struct field with the name "buffer".
- */
- proto['ref.buffer'] = ref.NULL
- /**
- * Flattens the Struct instance into a regular JavaScript Object. This function
- * "gets" all the defined properties.
- *
- * @api public
- */
- proto.toObject = function toObject () {
- var obj = {}
- Object.keys(this.constructor.fields).forEach(function (k) {
- obj[k] = this[k]
- }, this)
- return obj
- }
- /**
- * Basic `JSON.stringify(struct)` support.
- */
- proto.toJSON = function toJSON () {
- return this.toObject()
- }
- /**
- * `.inspect()` override. For the REPL.
- *
- * @api public
- */
- proto.inspect = function inspect () {
- var obj = this.toObject()
- // add instance's "own properties"
- Object.keys(this).forEach(function (k) {
- obj[k] = this[k]
- }, this)
- return util.inspect(obj)
- }
- /**
- * returns a Buffer pointing to this struct data structure.
- */
- proto.ref = function ref () {
- return this['ref.buffer']
- }
|