├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── appveyor.yml ├── code-of-conduct.md ├── lib └── struct.js ├── package.json └── test ├── binding.gyp ├── struct.js └── struct_tests.cc /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | .nyc_output/ 17 | coverage 18 | build 19 | node-addon-api 20 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "asi": true, 3 | "laxcomma": true, 4 | "node": true, 5 | "strict": false 6 | } 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /test -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | addons: 4 | apt: 5 | sources: 6 | - ubuntu-toolchain-r-test 7 | 8 | language: node_js 9 | 10 | node_js: 11 | - "14" 12 | - "15" 13 | - "16" 14 | 15 | after_success: 16 | - npm install coveralls 17 | - nyc report --reporter=text-lcov | ./node_modules/.bin/coveralls 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 1.1.0 / 2016-08-02 3 | ================== 4 | 5 | * [[`eb4550fdaa`](https://github.com/TooTallNate/ref-struct/commit/eb4550fdaa)] - Implement `pack` option (Lee, SungUk) 6 | * [[`17c1c7a8ef`](https://github.com/TooTallNate/ref-struct/commit/17c1c7a8ef)] - fix StructType constructor name in README (typo) (David Corticchiato) 7 | * [[`54b72bde07`](https://github.com/TooTallNate/ref-struct/commit/54b72bde07)] - **appveyor**: fix node v0.8, test io.js v2.5 and v3 (Nathan Rajlich) 8 | 9 | 1.0.2 / 2015-08-27 10 | ================== 11 | 12 | * appveyor: drop v0.11, iojs v1.5.1, test x64 13 | * travis: drop v0.11, test iojs v2.5 and v3 14 | * package: add license attribute (#17, @pdehaan) 15 | * package: update "ref-array" to v1.1.2 16 | * package: update "nan" v2 for native tests 17 | * README: use SVG for appveyor badge 18 | 19 | 1.0.1 / 2015-03-24 20 | ================== 21 | 22 | * removed travis testing for node_js version "0.6" and added 0.12 and iojs 23 | * added appveyor test versions as per node-ffi library for iojs & 0.12 24 | * package: allow any "ref" v "1" 25 | * npmignore: add `test` dir 26 | 27 | 1.0.0 / 2014-11-03 28 | ================== 29 | 30 | * bumping to v1.0.0 for better-defined semver semantics 31 | 32 | 0.0.7 / 2014-11-03 33 | ================== 34 | 35 | * gitignore: ignore single letter ?.js test files 36 | * lib: only slice buffer in set() in the non-instance case 37 | * package: allow any "debug" v2 38 | 39 | 0.0.6 / 2014-06-19 40 | ================== 41 | 42 | * package: update "ref" to v0.3.2 43 | * package: update "debug" to v1.0.1 44 | * test: remove v8 namespace import 45 | * test: use "bindings" module to load the native tests 46 | * README: add appveyor build badge 47 | * History: match `git changelog` syntax 48 | * package: loosely pin the deps 49 | * test: nan-ify tests 50 | * README: fix Travis badge 51 | * README: use svg for Travis badge 52 | * travis: test node v0.8, v0.10, and v0.11 53 | * add appveyor.yml file for Windows testing 54 | * package: remove "engines" field 55 | * package: beautify 56 | * add a couple comments 57 | 58 | 0.0.5 / 2013-01-24 59 | ================== 60 | 61 | * rename the backing buffer property to `ref.buffer` 62 | * add .jshintrc file 63 | * some minor optimizations 64 | 65 | 0.0.4 / 2012-09-26 66 | ================== 67 | 68 | * struct: correct the field alignment logic (TJ Fontaine) 69 | * test: add failing test from #1 70 | * test: more stucts with arrays tests 71 | * add support for "ref-array" types 72 | * add `toObject()`, `toJSON()`, and `inspect()` functions to struct instances 73 | * change `_pointer` to `buffer` 74 | * don't allow types with size == 0 like 'void' 75 | * test: add test case using "void *" as the type 76 | * test: fix deprecation warning 77 | * package: use the -C switch on node-gyp for the `npm test` command 78 | * travis: test node v0.7 and node v0.8 79 | * adjust the custom `toString()` output 80 | 81 | 0.0.3 / 2012-06-01 82 | ================== 83 | 84 | * set the "name" property of StructType instances 85 | * add a `toString()` override 86 | * fix a bug in the alignment calculation logic 87 | 88 | 0.0.2 / 2012-05-16 89 | ================== 90 | 91 | * Windows support (only the test suite needed tweaks) 92 | * make ref().deref() work 93 | * make string type identifiers work (type coersion) 94 | * don't make the constructors' prototype inherit from `Function.protoype` 95 | 96 | 0.0.1 / 2012-05-09 97 | ================== 98 | 99 | * Initial release 100 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Nathan Rajlich <nathan@tootallnate.net> 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | 'Software'), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ref-struct-di 2 | ============= 3 | ### Create ABI-compliant "[struct][]" instances on top of Buffers 4 | 5 | [![Greenkeeper badge](https://badges.greenkeeper.io/node-ffi-napi/ref-struct-di.svg)](https://greenkeeper.io/) 6 | 7 | [![NPM Version](https://img.shields.io/npm/v/ref-struct-di.svg?style=flat)](https://npmjs.org/package/ref-struct-di) 8 | [![NPM Downloads](https://img.shields.io/npm/dm/ref-struct-di.svg?style=flat)](https://npmjs.org/package/ref-struct-di) 9 | [![Build Status](https://travis-ci.org/node-ffi-napi/ref-struct-di.svg?style=flat&branch=master)](https://travis-ci.org/node-ffi-napi/ref-struct-di?branch=master) 10 | [![Coverage Status](https://coveralls.io/repos/node-ffi-napi/ref-struct-di/badge.svg?branch=master)](https://coveralls.io/r/node-ffi-napi/ref-struct-di?branch=master) 11 | [![Dependency Status](https://david-dm.org/node-ffi-napi/ref-struct-di.svg?style=flat)](https://david-dm.org/node-ffi-napi/ref-struct-di) 12 | 13 | This module offers a "struct" implementation on top of Node.js Buffers 14 | using the ref "type" interface. 15 | 16 | **Note**: The only difference to `ref-struct` is that this module takes its 17 | dependency on `ref` via dependency injection, so that it is easier to use 18 | e.g. `ref-napi` instead. 19 | 20 | Installation 21 | ------------ 22 | 23 | Install with `npm`: 24 | 25 | ``` bash 26 | $ npm install ref-struct-di 27 | ``` 28 | 29 | 30 | Examples 31 | -------- 32 | 33 | Say you wanted to emulate the `timeval` struct from the stdlib: 34 | 35 | ``` c 36 | struct timeval { 37 | time_t tv_sec; /* seconds since Jan. 1, 1970 */ 38 | suseconds_t tv_usec; /* and microseconds */ 39 | }; 40 | ``` 41 | 42 | ``` js 43 | var ref = require('ref') 44 | var StructType = require('ref-struct-di')(ref) 45 | 46 | // define the time types 47 | var time_t = ref.types.long 48 | var suseconds_t = ref.types.long 49 | 50 | // define the "timeval" struct type 51 | var timeval = StructType({ 52 | tv_sec: time_t, 53 | tv_usec: suseconds_t 54 | }) 55 | 56 | // now we can create instances of it 57 | var tv = new timeval 58 | ``` 59 | 60 | #### With `node-ffi` 61 | 62 | This gets very powerful when combined with `node-ffi` to invoke C functions: 63 | 64 | ``` js 65 | var ffi = require('ffi') 66 | 67 | var tv = new timeval 68 | gettimeofday(tv.ref(), null) 69 | ``` 70 | 71 | #### Progressive API 72 | 73 | You can build up a Struct "type" incrementally (useful when interacting with a 74 | parser) using the `defineProperty()` function. But as soon as you _create_ an 75 | instance of the struct type, then the struct type is finalized, and no more 76 | properties may be added to it. 77 | 78 | ``` js 79 | var ref = require('ref') 80 | var StructType = require('ref-struct') 81 | 82 | var MyStruct = StructType() 83 | MyStruct.defineProperty('width', ref.types.int) 84 | MyStruct.defineProperty('height', ref.types.int) 85 | 86 | var i = new MyStruct({ width: 5, height: 10 }) 87 | 88 | MyStruct.defineProperty('weight', ref.types.int) 89 | // AssertionError: an instance of this Struct type has already been created, cannot add new "fields" anymore 90 | // at Function.defineProperty (/Users/nrajlich/ref-struct/lib/struct.js:180:3) 91 | ``` 92 | 93 | 94 | License 95 | ------- 96 | 97 | (The MIT License) 98 | 99 | Copyright (c) 2012 Nathan Rajlich <nathan@tootallnate.net> 100 | 101 | Permission is hereby granted, free of charge, to any person obtaining 102 | a copy of this software and associated documentation files (the 103 | 'Software'), to deal in the Software without restriction, including 104 | without limitation the rights to use, copy, modify, merge, publish, 105 | distribute, sublicense, and/or sell copies of the Software, and to 106 | permit persons to whom the Software is furnished to do so, subject to 107 | the following conditions: 108 | 109 | The above copyright notice and this permission notice shall be 110 | included in all copies or substantial portions of the Software. 111 | 112 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 113 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 114 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 115 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 116 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 117 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 118 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 119 | 120 | [struct]: http://wikipedia.org/wiki/Struct_(C_programming_language) 121 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # http://www.appveyor.com/docs/appveyor-yml 2 | 3 | # Test against these versions of Node.js. 4 | environment: 5 | # Test against these versions of Node.js and io.js 6 | matrix: 7 | # node.js 8 | - nodejs_version: "14" 9 | - nodejs_version: "15" 10 | 11 | platform: 12 | - x86 13 | - x64 14 | 15 | # Install scripts. (runs after repo cloning) 16 | install: 17 | - python -V 18 | - set PYTHON=python 19 | - ps: Install-Product node $env:nodejs_version $env:platform 20 | - node -p process.arch 21 | - node -p process.version 22 | - npm install 23 | 24 | # Post-install test scripts. 25 | test_script: 26 | # Output useful info for debugging. 27 | - node --version 28 | - npm --version 29 | # run tests 30 | - npm test 31 | 32 | # Don't actually build. 33 | build: off 34 | 35 | # Set build version format here instead of in the admin panel. 36 | version: "{build}" 37 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project maintainer at anna@addaleax.net. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /lib/struct.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * An interface for modeling and instantiating C-style data structures. This is 4 | * not a constructor per-say, but a constructor generator. It takes an array of 5 | * tuples, the left side being the type, and the right side being a field name. 6 | * The order should be the same order it would appear in the C-style struct 7 | * definition. It returns a function that can be used to construct an object that 8 | * reads and writes to the data structure using properties specified by the 9 | * initial field list. 10 | * 11 | * The only verboten field names are "ref", which is used used on struct 12 | * instances as a function to retrieve the backing Buffer instance of the 13 | * struct, and "ref.buffer" which contains the backing Buffer instance. 14 | * 15 | * 16 | * Example: 17 | * 18 | * ``` javascript 19 | * var ref = require('ref') 20 | * var Struct = require('ref-struct') 21 | * 22 | * // create the `char *` type 23 | * var charPtr = ref.refType(ref.types.char) 24 | * var int = ref.types.int 25 | * 26 | * // create the struct "type" / constructor 27 | * var PasswordEntry = Struct({ 28 | * 'username': 'string' 29 | * , 'password': 'string' 30 | * , 'salt': int 31 | * }) 32 | * 33 | * // create an instance of the struct, backed a Buffer instance 34 | * var pwd = new PasswordEntry() 35 | * pwd.username = 'ricky' 36 | * pwd.password = 'rbransonlovesnode.js' 37 | * pwd.salt = (Math.random() * 1000000) | 0 38 | * 39 | * pwd.username // → 'ricky' 40 | * pwd.password // → 'rbransonlovesnode.js' 41 | * pwd.salt // → 820088 42 | * ``` 43 | */ 44 | 45 | /** 46 | * Module dependencies. 47 | */ 48 | 49 | var util = require('util') 50 | var assert = require('assert') 51 | var debug = require('debug')('ref:struct') 52 | 53 | /** 54 | * Module exports. 55 | */ 56 | 57 | module.exports = function (ref) { 58 | 59 | /** 60 | * The Struct "type" meta-constructor. 61 | */ 62 | 63 | function Struct () { 64 | debug('defining new struct "type"') 65 | 66 | /** 67 | * This is the "constructor" of the Struct type that gets returned. 68 | * 69 | * Invoke it with `new` to create a new Buffer instance backing the struct. 70 | * Pass it an existing Buffer instance to use that as the backing buffer. 71 | * Pass in an Object containing the struct fields to auto-populate the 72 | * struct with the data. 73 | */ 74 | 75 | function StructType (arg, data) { 76 | if (!(this instanceof StructType)) { 77 | return new StructType(arg, data) 78 | } 79 | debug('creating new struct instance') 80 | var store 81 | if (Buffer.isBuffer(arg)) { 82 | debug('using passed-in Buffer instance to back the struct', arg) 83 | assert(arg.length >= StructType.size, 'Buffer instance must be at least ' + 84 | StructType.size + ' bytes to back this struct type') 85 | store = arg 86 | arg = data 87 | } else { 88 | debug('creating new Buffer instance to back the struct (size: %d)', StructType.size) 89 | store = Buffer.alloc(StructType.size) 90 | } 91 | 92 | // set the backing Buffer store 93 | store.type = StructType 94 | this['ref.buffer'] = store 95 | 96 | if (arg) { 97 | for (var key in arg) { 98 | // hopefully hit the struct setters 99 | this[key] = arg[key] 100 | } 101 | } 102 | StructType._instanceCreated = true 103 | } 104 | 105 | // make instances inherit from the `proto` 106 | StructType.prototype = Object.create(proto, { 107 | constructor: { 108 | value: StructType 109 | , enumerable: false 110 | , writable: true 111 | , configurable: true 112 | } 113 | }) 114 | 115 | StructType.defineProperty = defineProperty 116 | StructType.toString = toString 117 | StructType.fields = {} 118 | 119 | var opt = (arguments.length > 0 && arguments[1]) ? arguments[1] : {}; 120 | // Setup the ref "type" interface. The constructor doubles as the "type" object 121 | StructType.size = 0 122 | StructType.alignment = 0 123 | StructType.indirection = 1 124 | StructType.isPacked = opt.packed ? Boolean(opt.packed) : false 125 | StructType.get = get 126 | StructType.set = set 127 | 128 | // Read the fields list and apply all the fields to the struct 129 | // TODO: Better arg handling... (maybe look at ES6 binary data API?) 130 | var arg = arguments[0] 131 | if (Array.isArray(arg)) { 132 | // legacy API 133 | arg.forEach(function (a) { 134 | var type = a[0] 135 | var name = a[1] 136 | StructType.defineProperty(name, type) 137 | }) 138 | } else if (typeof arg === 'object') { 139 | Object.keys(arg).forEach(function (name) { 140 | var type = arg[name] 141 | StructType.defineProperty(name, type) 142 | }) 143 | } 144 | 145 | return StructType 146 | } 147 | 148 | /** 149 | * The "get" function of the Struct "type" interface 150 | */ 151 | 152 | function get (buffer, offset) { 153 | debug('Struct "type" getter for buffer at offset', buffer, offset) 154 | if (offset > 0) { 155 | buffer = buffer.slice(offset) 156 | } 157 | return new this(buffer) 158 | } 159 | 160 | /** 161 | * The "set" function of the Struct "type" interface 162 | */ 163 | 164 | function set (buffer, offset, value) { 165 | debug('Struct "type" setter for buffer at offset', buffer, offset, value) 166 | var isStruct = value instanceof this 167 | if (isStruct) { 168 | // optimization: copy the buffer contents directly rather 169 | // than going through the ref-struct constructor 170 | value['ref.buffer'].copy(buffer, offset, 0, this.size) 171 | } else { 172 | if (offset > 0) { 173 | buffer = buffer.slice(offset) 174 | } 175 | new this(buffer, value) 176 | } 177 | } 178 | 179 | /** 180 | * Custom `toString()` override for struct type instances. 181 | */ 182 | 183 | function toString () { 184 | return '[StructType]' 185 | } 186 | 187 | /** 188 | * Adds a new field to the struct instance with the given name and type. 189 | * Note that this function will throw an Error if any instances of the struct 190 | * type have already been created, therefore this function must be called at the 191 | * beginning, before any instances are created. 192 | */ 193 | 194 | function defineProperty (name, type) { 195 | debug('defining new struct type field', name) 196 | 197 | // allow string types for convenience 198 | type = ref.coerceType(type) 199 | 200 | assert(!this._instanceCreated, 'an instance of this Struct type has already ' + 201 | 'been created, cannot add new "fields" anymore') 202 | assert.equal('string', typeof name, 'expected a "string" field name') 203 | assert(type && /object|function/i.test(typeof type) && 'size' in type && 204 | 'indirection' in type 205 | , 'expected a "type" object describing the field type: "' + type + '"') 206 | assert(type.indirection > 1 || type.size > 0, 207 | '"type" object must have a size greater than 0') 208 | assert(!(name in this.prototype), 'the field "' + name + 209 | '" already exists in this Struct type') 210 | 211 | var field = { 212 | type: type 213 | } 214 | this.fields[name] = field 215 | 216 | // define the getter/setter property 217 | var desc = { enumerable: true , configurable: true } 218 | desc.get = function () { 219 | debug('getting "%s" struct field (offset: %d)', name, field.offset) 220 | return ref.get(this['ref.buffer'], field.offset, type) 221 | } 222 | desc.set = function (value) { 223 | debug('setting "%s" struct field (offset: %d)', name, field.offset, value) 224 | return ref.set(this['ref.buffer'], field.offset, value, type) 225 | } 226 | 227 | // calculate the new size and field offsets 228 | recalc(this) 229 | 230 | Object.defineProperty(this.prototype, name, desc) 231 | } 232 | 233 | function recalc (struct) { 234 | 235 | // reset size and alignment 236 | struct.size = 0 237 | struct.alignment = 0 238 | 239 | var fieldNames = Object.keys(struct.fields) 240 | 241 | // first loop through is to determine the `alignment` of this struct 242 | fieldNames.forEach(function (name) { 243 | var field = struct.fields[name] 244 | var type = field.type 245 | var alignment = type.alignment || ref.alignof.pointer 246 | if (type.indirection > 1) { 247 | alignment = ref.alignof.pointer 248 | } 249 | if (struct.isPacked) { 250 | struct.alignment = Math.min(struct.alignment || alignment, alignment) 251 | } else { 252 | struct.alignment = Math.max(struct.alignment, alignment) 253 | } 254 | }) 255 | 256 | // second loop through sets the `offset` property on each "field" 257 | // object, and sets the `struct.size` as we go along 258 | fieldNames.forEach(function (name) { 259 | var field = struct.fields[name] 260 | var type = field.type 261 | 262 | if (null != type.fixedLength) { 263 | // "ref-array" types set the "fixedLength" prop. don't treat arrays like one 264 | // contiguous entity. instead, treat them like individual elements in the 265 | // struct. doing this makes the padding end up being calculated correctly. 266 | field.offset = addType(type.type) 267 | for (var i = 1; i < type.fixedLength; i++) { 268 | addType(type.type) 269 | } 270 | } else { 271 | field.offset = addType(type) 272 | } 273 | }) 274 | 275 | function addType (type) { 276 | var offset = struct.size 277 | var align = type.indirection === 1 ? type.alignment : ref.alignof.pointer 278 | var padding = struct.isPacked ? 0 : (align - (offset % align)) % align 279 | var size = type.indirection === 1 ? type.size : ref.sizeof.pointer 280 | 281 | offset += padding 282 | 283 | if (!struct.isPacked) { 284 | assert.equal(offset % align, 0, "offset should align") 285 | } 286 | 287 | // adjust the "size" of the struct type 288 | struct.size = offset + size 289 | 290 | // return the calulated offset 291 | return offset 292 | } 293 | 294 | // any final padding? 295 | var left = struct.size % struct.alignment 296 | if (left > 0) { 297 | debug('additional padding to the end of struct:', struct.alignment - left) 298 | struct.size += struct.alignment - left 299 | } 300 | } 301 | 302 | /** 303 | * this is the custom prototype of Struct type instances. 304 | */ 305 | 306 | var proto = {} 307 | 308 | /** 309 | * set a placeholder variable on the prototype so that defineProperty() will 310 | * throw an error if you try to define a struct field with the name "buffer". 311 | */ 312 | 313 | proto['ref.buffer'] = ref.NULL 314 | 315 | /** 316 | * Flattens the Struct instance into a regular JavaScript Object. This function 317 | * "gets" all the defined properties. 318 | * 319 | * @api public 320 | */ 321 | 322 | proto.toObject = function toObject () { 323 | var obj = {} 324 | Object.keys(this.constructor.fields).forEach(function (k) { 325 | obj[k] = this[k] 326 | }, this) 327 | return obj 328 | } 329 | 330 | /** 331 | * Basic `JSON.stringify(struct)` support. 332 | */ 333 | 334 | proto.toJSON = function toJSON () { 335 | return this.toObject() 336 | } 337 | 338 | /** 339 | * `.inspect()` override. For the REPL. 340 | * 341 | * @api public 342 | */ 343 | 344 | proto.inspect = function inspect () { 345 | var obj = this.toObject() 346 | // add instance's "own properties" 347 | Object.keys(this).forEach(function (k) { 348 | obj[k] = this[k] 349 | }, this) 350 | return util.inspect(obj) 351 | } 352 | 353 | /** 354 | * returns a Buffer pointing to this struct data structure. 355 | */ 356 | 357 | proto.ref = function ref () { 358 | return this['ref.buffer'] 359 | } 360 | 361 | return Struct; 362 | 363 | }; 364 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ref-struct-di", 3 | "description": "Create ABI-compliant \"struct\" instances on top of Buffers", 4 | "keywords": [ 5 | "struct", 6 | "ref", 7 | "abi", 8 | "c", 9 | "c++", 10 | "ffi" 11 | ], 12 | "version": "1.1.1", 13 | "author": "Anna Henningsen ", 14 | "contributors": [ 15 | "Nathan Rajlich " 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/node-ffi-napi/ref-struct-di.git" 20 | }, 21 | "main": "./lib/struct.js", 22 | "license": "MIT", 23 | "scripts": { 24 | "test": "node-gyp rebuild --directory test && nyc mocha --expose-gc --reporter spec" 25 | }, 26 | "dependencies": { 27 | "debug": "^4.3.1" 28 | }, 29 | "devDependencies": { 30 | "mocha": "^7.1.1", 31 | "nyc": "^15.0.0", 32 | "node-gyp-build": "^4.2.1", 33 | "node-addon-api": "^3.0.0", 34 | "ref-array-di": "^1.2.2", 35 | "ref-napi": "^3.0.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'struct_tests', 5 | 'sources': [ 'struct_tests.cc' ], 6 | 'include_dirs': [ 7 | " 2 | #include 3 | 4 | #ifdef _WIN32 5 | #define __alignof__ __alignof 6 | #endif 7 | 8 | using namespace Napi; 9 | 10 | namespace { 11 | 12 | typedef struct _test1 { 13 | int a; 14 | int b; 15 | double c; 16 | } test1; 17 | 18 | typedef struct _test2 { 19 | int a; 20 | double b; 21 | int c; 22 | } test2; 23 | 24 | typedef struct _test3 { 25 | double a; 26 | int b; 27 | int c; 28 | } test3; 29 | 30 | typedef struct _test4 { 31 | double a; 32 | double b; 33 | int c; 34 | } test4; 35 | 36 | typedef struct _test5 { 37 | int a; 38 | double b; 39 | double c; 40 | } test5; 41 | 42 | typedef struct _test6 { 43 | char a; 44 | short b; 45 | int c; 46 | } test6; 47 | 48 | typedef struct _test7 { 49 | int a; 50 | short b; 51 | char c; 52 | } test7; 53 | 54 | typedef struct _test8 { 55 | int a; 56 | short b; 57 | char c; 58 | char d; 59 | } test8; 60 | 61 | typedef struct _test9 { 62 | int a; 63 | short b; 64 | char c; 65 | char d; 66 | char e; 67 | } test9; 68 | 69 | typedef struct _test10 { 70 | test1 a; 71 | char b; 72 | } test10; 73 | 74 | // this one simulates the `ffi_type` struct 75 | typedef struct _test11 { 76 | size_t a; 77 | unsigned short b; 78 | unsigned short c; 79 | struct _test11 **d; 80 | } test11; 81 | 82 | typedef struct _test12 { 83 | char *a; 84 | int b; 85 | } test12; 86 | 87 | typedef struct _test13 { 88 | char a; 89 | char b[2]; 90 | } test13; 91 | 92 | typedef struct _test14 { 93 | char a; 94 | char b[2]; 95 | short c; 96 | char d; 97 | } test14; 98 | 99 | typedef struct _test15 { 100 | test1 a; 101 | test1 b; 102 | } test15; 103 | 104 | typedef struct _test16 { 105 | double a[10]; 106 | char b[3]; 107 | int c[6]; 108 | } test16; 109 | 110 | typedef struct _test17 { 111 | char a[3]; 112 | } test17; 113 | 114 | typedef struct _test18 { 115 | test17 a[100]; 116 | } test18; 117 | 118 | /* test19 example is from libdespotify 119 | * See: https://github.com/TooTallNate/ref-struct/issues/1 120 | */ 121 | 122 | #define STRING_LENGTH 256 123 | typedef struct _test19 { 124 | bool has_meta_data; 125 | bool playable; 126 | bool geo_restricted; 127 | unsigned char track_id[33]; 128 | unsigned char file_id[41]; 129 | unsigned int file_bitrate; 130 | unsigned char album_id[33]; 131 | unsigned char cover_id[41]; 132 | unsigned char *key; 133 | 134 | char *allowed; 135 | char *forbidden; 136 | 137 | char title[STRING_LENGTH]; 138 | struct artist* artist; 139 | char album[STRING_LENGTH]; 140 | int length; 141 | int tracknumber; 142 | int year; 143 | float popularity; 144 | struct _test19 *next; /* in case of multiple tracks 145 | in an album or playlist struct */ 146 | } test19; 147 | 148 | #pragma pack(1) 149 | typedef struct _test20 { 150 | char a; 151 | void *p; 152 | } test20; 153 | 154 | #pragma pack() 155 | typedef struct _test21 { 156 | char a; 157 | void *p; 158 | } test21; 159 | 160 | Object Initialize(Env env, Object exports) { 161 | 162 | exports.Set("test1 sizeof", sizeof(test1)); 163 | exports.Set("test1 alignof", __alignof__(test1)); 164 | exports.Set("test1 offsetof a", offsetof(test1, a)); 165 | exports.Set("test1 offsetof b", offsetof(test1, b)); 166 | exports.Set("test1 offsetof c", offsetof(test1, c)); 167 | 168 | exports.Set("test2 sizeof", sizeof(test2)); 169 | exports.Set("test2 alignof", __alignof__(test2)); 170 | exports.Set("test2 offsetof a", offsetof(test2, a)); 171 | exports.Set("test2 offsetof b", offsetof(test2, b)); 172 | exports.Set("test2 offsetof c", offsetof(test2, c)); 173 | 174 | exports.Set("test3 sizeof", sizeof(test3)); 175 | exports.Set("test3 alignof", __alignof__(test3)); 176 | exports.Set("test3 offsetof a", offsetof(test3, a)); 177 | exports.Set("test3 offsetof b", offsetof(test3, b)); 178 | exports.Set("test3 offsetof c", offsetof(test3, c)); 179 | 180 | exports.Set("test4 sizeof", sizeof(test4)); 181 | exports.Set("test4 alignof", __alignof__(test4)); 182 | exports.Set("test4 offsetof a", offsetof(test4, a)); 183 | exports.Set("test4 offsetof b", offsetof(test4, b)); 184 | exports.Set("test4 offsetof c", offsetof(test4, c)); 185 | 186 | exports.Set("test5 sizeof", sizeof(test5)); 187 | exports.Set("test5 alignof", __alignof__(test5)); 188 | exports.Set("test5 offsetof a", offsetof(test5, a)); 189 | exports.Set("test5 offsetof b", offsetof(test5, b)); 190 | exports.Set("test5 offsetof c", offsetof(test5, c)); 191 | 192 | exports.Set("test6 sizeof", sizeof(test6)); 193 | exports.Set("test6 alignof", __alignof__(test6)); 194 | exports.Set("test6 offsetof a", offsetof(test6, a)); 195 | exports.Set("test6 offsetof b", offsetof(test6, b)); 196 | exports.Set("test6 offsetof c", offsetof(test6, c)); 197 | 198 | exports.Set("test7 sizeof", sizeof(test7)); 199 | exports.Set("test7 alignof", __alignof__(test7)); 200 | exports.Set("test7 offsetof a", offsetof(test7, a)); 201 | exports.Set("test7 offsetof b", offsetof(test7, b)); 202 | exports.Set("test7 offsetof c", offsetof(test7, c)); 203 | 204 | exports.Set("test8 sizeof", sizeof(test8)); 205 | exports.Set("test8 alignof", __alignof__(test8)); 206 | exports.Set("test8 offsetof a", offsetof(test8, a)); 207 | exports.Set("test8 offsetof b", offsetof(test8, b)); 208 | exports.Set("test8 offsetof c", offsetof(test8, c)); 209 | exports.Set("test8 offsetof d", offsetof(test8, d)); 210 | 211 | exports.Set("test9 sizeof", sizeof(test9)); 212 | exports.Set("test9 alignof", __alignof__(test9)); 213 | exports.Set("test9 offsetof a", offsetof(test9, a)); 214 | exports.Set("test9 offsetof b", offsetof(test9, b)); 215 | exports.Set("test9 offsetof c", offsetof(test9, c)); 216 | exports.Set("test9 offsetof d", offsetof(test9, d)); 217 | exports.Set("test9 offsetof e", offsetof(test9, e)); 218 | 219 | exports.Set("test10 sizeof", sizeof(test10)); 220 | exports.Set("test10 alignof", __alignof__(test10)); 221 | exports.Set("test10 offsetof a", offsetof(test10, a)); 222 | exports.Set("test10 offsetof b", offsetof(test10, b)); 223 | 224 | exports.Set("test11 sizeof", sizeof(test11)); 225 | exports.Set("test11 alignof", __alignof__(test11)); 226 | exports.Set("test11 offsetof a", offsetof(test11, a)); 227 | exports.Set("test11 offsetof b", offsetof(test11, b)); 228 | exports.Set("test11 offsetof c", offsetof(test11, c)); 229 | exports.Set("test11 offsetof d", offsetof(test11, d)); 230 | 231 | exports.Set("test12 sizeof", sizeof(test12)); 232 | exports.Set("test12 alignof", __alignof__(test12)); 233 | exports.Set("test12 offsetof a", offsetof(test12, a)); 234 | exports.Set("test12 offsetof b", offsetof(test12, b)); 235 | 236 | exports.Set("test13 sizeof", sizeof(test13)); 237 | exports.Set("test13 alignof", __alignof__(test13)); 238 | exports.Set("test13 offsetof a", offsetof(test13, a)); 239 | exports.Set("test13 offsetof b", offsetof(test13, b)); 240 | 241 | exports.Set("test14 sizeof", sizeof(test14)); 242 | exports.Set("test14 alignof", __alignof__(test14)); 243 | exports.Set("test14 offsetof a", offsetof(test14, a)); 244 | exports.Set("test14 offsetof b", offsetof(test14, b)); 245 | exports.Set("test14 offsetof c", offsetof(test14, c)); 246 | exports.Set("test14 offsetof d", offsetof(test14, d)); 247 | 248 | exports.Set("test15 sizeof", sizeof(test15)); 249 | exports.Set("test15 alignof", __alignof__(test15)); 250 | exports.Set("test15 offsetof a", offsetof(test15, a)); 251 | exports.Set("test15 offsetof b", offsetof(test15, b)); 252 | 253 | exports.Set("test16 sizeof", sizeof(test16)); 254 | exports.Set("test16 alignof", __alignof__(test16)); 255 | exports.Set("test16 offsetof a", offsetof(test16, a)); 256 | exports.Set("test16 offsetof b", offsetof(test16, b)); 257 | exports.Set("test16 offsetof c", offsetof(test16, c)); 258 | 259 | exports.Set("test17 sizeof", sizeof(test17)); 260 | exports.Set("test17 alignof", __alignof__(test17)); 261 | exports.Set("test17 offsetof a", offsetof(test17, a)); 262 | 263 | exports.Set("test18 sizeof", sizeof(test18)); 264 | exports.Set("test18 alignof", __alignof__(test18)); 265 | exports.Set("test18 offsetof a", offsetof(test18, a)); 266 | 267 | exports.Set("test19 sizeof", sizeof(test19)); 268 | exports.Set("test19 alignof", __alignof__(test19)); 269 | exports.Set("test19 offsetof has_meta_data", offsetof(test19, has_meta_data)); 270 | exports.Set("test19 offsetof playable", offsetof(test19, playable)); 271 | exports.Set("test19 offsetof geo_restricted", offsetof(test19, geo_restricted)); 272 | exports.Set("test19 offsetof track_id", offsetof(test19, track_id)); 273 | exports.Set("test19 offsetof file_id", offsetof(test19, file_id)); 274 | exports.Set("test19 offsetof file_bitrate", offsetof(test19, file_bitrate)); 275 | exports.Set("test19 offsetof album_id", offsetof(test19, album_id)); 276 | exports.Set("test19 offsetof cover_id", offsetof(test19, cover_id)); 277 | exports.Set("test19 offsetof key", offsetof(test19, key)); 278 | exports.Set("test19 offsetof allowed", offsetof(test19, allowed)); 279 | exports.Set("test19 offsetof forbidden", offsetof(test19, forbidden)); 280 | exports.Set("test19 offsetof title", offsetof(test19, title)); 281 | exports.Set("test19 offsetof artist", offsetof(test19, artist)); 282 | exports.Set("test19 offsetof album", offsetof(test19, album)); 283 | exports.Set("test19 offsetof length", offsetof(test19, length)); 284 | exports.Set("test19 offsetof tracknumber", offsetof(test19, tracknumber)); 285 | exports.Set("test19 offsetof year", offsetof(test19, year)); 286 | exports.Set("test19 offsetof popularity", offsetof(test19, popularity)); 287 | exports.Set("test19 offsetof next", offsetof(test19, next)); 288 | 289 | exports.Set("test20 sizeof", sizeof(test20)); 290 | exports.Set("test20 alignof", __alignof__(test20)); 291 | 292 | exports.Set("test21 sizeof", sizeof(test21)); 293 | exports.Set("test21 alignof", __alignof__(test21)); 294 | 295 | return exports; 296 | } 297 | 298 | } // anonymous namespace 299 | 300 | NODE_API_MODULE(struct_tests, Initialize); 301 | --------------------------------------------------------------------------------