diff --git a/.github/workflows/prebuild-linux-arm64.yml b/.github/workflows/prebuild-linux-arm64.yml new file mode 100644 index 00000000..2e0486d3 --- /dev/null +++ b/.github/workflows/prebuild-linux-arm64.yml @@ -0,0 +1,87 @@ +name: Prebuild Linux ARM64 + +on: + workflow_dispatch: + pull_request: + branches: + - develop + push: + tags: + - '*' + +jobs: + prebuild: + runs-on: ubuntu-24.04-arm + container: + image: ${{ matrix.docker_image }} + strategy: + fail-fast: false + matrix: + node-version: [22.X] + architecture: [arm64] + ros_distribution: + - humble + - jazzy + - kilted + include: + # Humble Hawksbill (May 2022 - May 2027) + - docker_image: ubuntu:jammy + ros_distribution: humble + ubuntu_codename: jammy + # Jazzy Jalisco (May 2024 - May 2029) + - docker_image: ubuntu:noble + ros_distribution: jazzy + ubuntu_codename: noble + # Kilted Kaiju (May 2025 - Dec 2026) + - docker_image: ubuntu:noble + ros_distribution: kilted + ubuntu_codename: noble + + steps: + - name: Setup Node.js ${{ matrix.node-version }} on ${{ matrix.architecture }} + uses: actions/setup-node@v5 + with: + node-version: ${{ matrix.node-version }} + architecture: ${{ matrix.architecture }} + + - name: Setup ROS2 + uses: ros-tooling/setup-ros@v0.7 + with: + required-ros-distributions: ${{ matrix.ros_distribution }} + + - name: Install test-msgs on Linux + shell: bash + run: | + sudo apt install ros-${{ matrix.ros_distribution }}-test-msgs + + - uses: actions/checkout@v5 + + - name: Install dependencies + shell: bash + run: | + source /opt/ros/${{ matrix.ros_distribution }}/setup.bash + npm i + + - name: Generate prebuilt binary + shell: bash + run: | + source /opt/ros/${{ matrix.ros_distribution }}/setup.bash + npm run prebuild + + - name: Upload prebuilt binary + uses: actions/upload-artifact@v4 + with: + name: prebuilt-linux-arm64-node${{ matrix.node-version }}-${{ matrix.ubuntu_codename }}-${{ matrix.ros_distribution }} + path: prebuilds/linux-arm64/*.node + if-no-files-found: error + + - name: Test loading prebuilt + shell: bash + run: | + source /opt/ros/${{ matrix.ros_distribution }}/setup.bash + node -e " + const rclnodejs = require('./index.js'); + console.log('Successfully loaded rclnodejs with prebuilt binary'); + console.log('Platform:', process.platform, 'Arch:', process.arch); + console.log('ROS_DISTRO:', process.env.ROS_DISTRO); + " diff --git a/.github/workflows/prebuild-linux-x64.yml b/.github/workflows/prebuild-linux-x64.yml new file mode 100644 index 00000000..548d1c19 --- /dev/null +++ b/.github/workflows/prebuild-linux-x64.yml @@ -0,0 +1,87 @@ +name: Prebuild Linux x64 + +on: + workflow_dispatch: + pull_request: + branches: + - develop + push: + tags: + - '*' + +jobs: + prebuild: + runs-on: ubuntu-latest + container: + image: ${{ matrix.docker_image }} + strategy: + fail-fast: false + matrix: + node-version: [22.X] + architecture: [x64] + ros_distribution: + - humble + - jazzy + - kilted + include: + # Humble Hawksbill (May 2022 - May 2027) + - docker_image: ubuntu:jammy + ros_distribution: humble + ubuntu_codename: jammy + # Jazzy Jalisco (May 2024 - May 2029) + - docker_image: ubuntu:noble + ros_distribution: jazzy + ubuntu_codename: noble + # Kilted Kaiju (May 2025 - Dec 2026) + - docker_image: ubuntu:noble + ros_distribution: kilted + ubuntu_codename: noble + + steps: + - name: Setup Node.js ${{ matrix.node-version }} on ${{ matrix.architecture }} + uses: actions/setup-node@v5 + with: + node-version: ${{ matrix.node-version }} + architecture: ${{ matrix.architecture }} + + - name: Setup ROS2 + uses: ros-tooling/setup-ros@v0.7 + with: + required-ros-distributions: ${{ matrix.ros_distribution }} + + - name: Install test-msgs on Linux + shell: bash + run: | + sudo apt install ros-${{ matrix.ros_distribution }}-test-msgs + + - uses: actions/checkout@v5 + + - name: Install dependencies + shell: bash + run: | + source /opt/ros/${{ matrix.ros_distribution }}/setup.bash + npm i + + - name: Generate prebuilt binary + shell: bash + run: | + source /opt/ros/${{ matrix.ros_distribution }}/setup.bash + npm run prebuild + + - name: Upload prebuilt binary + uses: actions/upload-artifact@v4 + with: + name: prebuilt-linux-x64-node${{ matrix.node-version }}-${{ matrix.ubuntu_codename }}-${{ matrix.ros_distribution }} + path: prebuilds/linux-x64/*.node + if-no-files-found: error + + - name: Test loading prebuilt + shell: bash + run: | + source /opt/ros/${{ matrix.ros_distribution }}/setup.bash + node -e " + const rclnodejs = require('./index.js'); + console.log('Successfully loaded rclnodejs with prebuilt binary'); + console.log('Platform:', process.platform, 'Arch:', process.arch); + console.log('ROS_DISTRO:', process.env.ROS_DISTRO); + " diff --git a/binding.gyp b/binding.gyp index 0d9ff1ad..02dc4d04 100644 --- a/binding.gyp +++ b/binding.gyp @@ -42,9 +42,11 @@ './src/rcl_timer_bindings.cpp', './src/rcl_utilities.cpp', './src/shadow_node.cpp', + './third_party/ref-napi/src/ref_napi_bindings.cpp', ], 'include_dirs': [ '.', + './third_party/ref-napi/src', '<(ros_include_root)', "= 23', { - 'cflags_cc': [ - '-std=c++20' - ] - } - ], - [ - 'node_major_version < 23', { - 'cflags_cc': [ - '-std=c++17' - ] - } - ] - ], 'include_dirs': [ './src/third_party/dlfcn-win32/', ], @@ -127,6 +113,7 @@ 'msvs_settings': { 'VCCLCompilerTool': { 'ExceptionHandling': '2', # /EHsc + 'AdditionalOptions': ['/std:c++20'] }, 'VCLinkerTool': { 'AdditionalDependencies': ['psapi.lib'], @@ -145,7 +132,7 @@ 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', 'CLANG_CXX_LIBRARY': 'libc++', 'MACOS_DEPLOYMENT_TARGET': '10.12', - 'CLANG_CXX_LANGUAGE_STANDARD': 'c++17' + 'CLANG_CXX_LANGUAGE_STANDARD': 'c++20' } } ], diff --git a/index.js b/index.js index 504981dd..89239be3 100644 --- a/index.js +++ b/index.js @@ -37,7 +37,7 @@ const { } = require('./lib/parameter.js'); const path = require('path'); const QoS = require('./lib/qos.js'); -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./lib/native_loader.js'); const tsdGenerator = require('./rostsd_gen/index.js'); const validator = require('./lib/validator.js'); const Time = require('./lib/time.js'); diff --git a/lib/action/client.js b/lib/action/client.js index aae06f7e..df130e29 100644 --- a/lib/action/client.js +++ b/lib/action/client.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('../native_loader.js'); const ActionInterfaces = require('./interfaces.js'); const ActionUuid = require('./uuid.js'); const ClientGoalHandle = require('./client_goal_handle.js'); diff --git a/lib/action/graph.js b/lib/action/graph.js index cdd491c2..c211a4ad 100644 --- a/lib/action/graph.js +++ b/lib/action/graph.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('../native_loader.js'); /** * Get a list of action names and types for action clients associated with a node. diff --git a/lib/action/server.js b/lib/action/server.js index 32eb3d9a..6896427f 100644 --- a/lib/action/server.js +++ b/lib/action/server.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('../native_loader.js'); const ActionInterfaces = require('./interfaces.js'); const ActionUuid = require('./uuid.js'); const DistroUtils = require('../distro.js'); diff --git a/lib/action/server_goal_handle.js b/lib/action/server_goal_handle.js index eecfab72..ffc74faa 100644 --- a/lib/action/server_goal_handle.js +++ b/lib/action/server_goal_handle.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('../native_loader.js'); const ActionInterfaces = require('./interfaces.js'); const Deferred = require('./deferred.js'); const { GoalEvent } = require('./response.js'); diff --git a/lib/client.js b/lib/client.js index 50b7b2e6..fd7f0f27 100644 --- a/lib/client.js +++ b/lib/client.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); const DistroUtils = require('./distro.js'); const Entity = require('./entity.js'); const debug = require('debug')('rclnodejs:client'); diff --git a/lib/clock.js b/lib/clock.js index f99ce5d3..ff208cae 100644 --- a/lib/clock.js +++ b/lib/clock.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); const Time = require('./time.js'); const ClockType = require('./clock_type.js'); diff --git a/lib/context.js b/lib/context.js index f36acf2a..4033f91a 100644 --- a/lib/context.js +++ b/lib/context.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); let defaultContext = null; diff --git a/lib/duration.js b/lib/duration.js index e499586b..d5acd0d2 100644 --- a/lib/duration.js +++ b/lib/duration.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); const S_TO_NS = 10n ** 9n; /** diff --git a/lib/event_handler.js b/lib/event_handler.js index 585bdf26..b2b506e0 100644 --- a/lib/event_handler.js +++ b/lib/event_handler.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); const DistroUtils = require('./distro.js'); const Entity = require('./entity.js'); diff --git a/lib/guard_condition.js b/lib/guard_condition.js index 68a892a1..70fd3c0e 100644 --- a/lib/guard_condition.js +++ b/lib/guard_condition.js @@ -12,7 +12,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); const Entity = require('./entity.js'); const Context = require('./context.js'); diff --git a/lib/lifecycle.js b/lib/lifecycle.js index 9bdb0176..cee8e8bd 100644 --- a/lib/lifecycle.js +++ b/lib/lifecycle.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); const LifecyclePublisher = require('./lifecycle_publisher.js'); const loader = require('./interface_loader.js'); const Context = require('./context.js'); diff --git a/lib/lifecycle_publisher.js b/lib/lifecycle_publisher.js index b004109c..f22aa280 100644 --- a/lib/lifecycle_publisher.js +++ b/lib/lifecycle_publisher.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); const Logging = require('./logging.js'); const Publisher = require('./publisher.js'); diff --git a/lib/logging.js b/lib/logging.js index 46e26f3a..500f190c 100644 --- a/lib/logging.js +++ b/lib/logging.js @@ -15,7 +15,7 @@ 'use strict'; const path = require('path'); -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); /** * Enum for LoggingSeverity diff --git a/lib/native_loader.js b/lib/native_loader.js new file mode 100644 index 00000000..8c28c299 --- /dev/null +++ b/lib/native_loader.js @@ -0,0 +1,197 @@ +// Copyright (c) 2025, The Robot Web Tools Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const debug = require('debug')('rclnodejs'); + +let nativeModule = null; + +// Simplified loader: only use prebuilt binaries with exact Ubuntu/ROS2/arch match +// Note: Prebuilt binaries are only supported on Linux (Ubuntu) platform +function customFallbackLoader() { + // Prebuilt binaries are only for Linux platform + if (process.platform !== 'linux') { + debug('Prebuilt binaries are only supported on Linux platform'); + return null; + } + + const rosDistro = process.env.ROS_DISTRO; + const arch = process.arch; + let ubuntuCodename = null; + + // Detect Ubuntu codename + try { + const osRelease = fs.readFileSync('/etc/os-release', 'utf8'); + const match = osRelease.match(/^VERSION_CODENAME=(.*)$/m); + if (match) { + ubuntuCodename = match[1].trim(); + } + } catch (e) { + debug('Could not detect Ubuntu codename:', e.message); + } + + // Require all three components for exact match + if (!rosDistro || !ubuntuCodename || !arch) { + debug( + `Missing environment info - ROS: ${rosDistro}, Ubuntu: ${ubuntuCodename}, Arch: ${arch}` + ); + return null; + } + + const prebuildDir = path.join( + __dirname, + '..', + 'prebuilds', + `${process.platform}-${arch}` + ); + + if (!fs.existsSync(prebuildDir)) { + debug('No prebuilds directory found'); + return null; + } + + try { + // Look for exact match binary: {ros_distro}-{ubuntu_codename}-{arch}-rclnodejs.node + const exactMatchFilename = `${rosDistro}-${ubuntuCodename}-${arch}-rclnodejs.node`; + const exactMatchPath = path.join(prebuildDir, exactMatchFilename); + + if (fs.existsSync(exactMatchPath)) { + debug(`Found exact match binary: ${exactMatchFilename}`); + return require(exactMatchPath); + } + + debug(`No exact match found for: ${exactMatchFilename}`); + return null; + } catch (e) { + debug('Error in simplified prebuilt loader:', e.message); + } + + return null; +} + +// Simplified prebuilt binary loader: exact match or build from source +function loadNativeAddon() { + if (nativeModule) { + return nativeModule; + } + + // Environment variable to force building from source + if (process.env.RCLNODEJS_FORCE_BUILD === '1') { + debug('Forcing build from source (RCLNODEJS_FORCE_BUILD=1)'); + + // Trigger actual compilation + const { execSync } = require('child_process'); + try { + debug('Running forced node-gyp rebuild...'); + execSync('npm run rebuild', { + stdio: 'inherit', + cwd: __dirname + '/..', + timeout: 300000, // 5 minute timeout + }); + + // Load the newly built binary + nativeModule = require('bindings')('rclnodejs'); + debug('Successfully force compiled and loaded from source'); + return nativeModule; + } catch (compileError) { + debug('Forced compilation failed:', compileError.message); + throw new Error( + `Failed to force build rclnodejs from source: ${compileError.message}` + ); + } + } + + const rosDistro = process.env.ROS_DISTRO; + const ubuntuCodename = detectUbuntuCodename(); + + debug( + `Platform: ${process.platform}, Arch: ${process.arch}, Ubuntu: ${ubuntuCodename || 'unknown'}, ROS: ${rosDistro || 'unknown'}` + ); + + // Prebuilt binaries are only supported on Linux (Ubuntu) + if (process.platform === 'linux') { + // Try exact match prebuilt binary first + try { + const prebuiltModule = customFallbackLoader(); + if (prebuiltModule) { + nativeModule = prebuiltModule; + return nativeModule; + } + } catch (e) { + debug('Exact match prebuilt loading failed:', e.message); + } + + debug( + 'No exact match prebuilt binary found, falling back to build from source' + ); + } else { + debug( + `Platform ${process.platform} does not support prebuilt binaries, will try existing build or compile from source` + ); + } + + try { + // Try to find existing built binary first (works on all platforms) + // The 'bindings' module will search standard locations like: + // - build/Release/rclnodejs.node + // - build/Debug/rclnodejs.node + // - compiled/{node_version}/{platform}/{arch}/rclnodejs.node + // etc. + nativeModule = require('bindings')('rclnodejs'); + debug('Found and loaded existing built binary'); + return nativeModule; + } catch { + debug('No existing built binary found, triggering compilation...'); + + // Trigger actual compilation + const { execSync } = require('child_process'); + try { + debug('Running node-gyp rebuild...'); + execSync('npm run rebuild', { + stdio: 'inherit', + cwd: __dirname + '/..', + timeout: 300000, // 5 minute timeout + }); + + // Try to load the newly built binary + nativeModule = require('bindings')('rclnodejs'); + debug('Successfully compiled and loaded from source'); + return nativeModule; + } catch (compileError) { + debug('Compilation failed:', compileError.message); + throw new Error( + `Failed to build rclnodejs from source: ${compileError.message}` + ); + } + } +} + +function detectUbuntuCodename() { + if (process.platform !== 'linux') { + return null; + } + + try { + const osRelease = fs.readFileSync('/etc/os-release', 'utf8'); + const match = osRelease.match(/^VERSION_CODENAME=(.*)$/m); + return match ? match[1].trim() : null; + } catch { + return null; + } +} + +module.exports = loadNativeAddon(); diff --git a/lib/node.js b/lib/node.js index e9a344f8..7a85dc01 100644 --- a/lib/node.js +++ b/lib/node.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); const ActionInterfaces = require('./action/interfaces.js'); const Client = require('./client.js'); diff --git a/lib/publisher.js b/lib/publisher.js index b4ae66f8..df7599db 100644 --- a/lib/publisher.js +++ b/lib/publisher.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); const debug = require('debug')('rclnodejs:publisher'); const Entity = require('./entity.js'); diff --git a/lib/serialization.js b/lib/serialization.js index b8462f84..0f676124 100644 --- a/lib/serialization.js +++ b/lib/serialization.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); class Serialization { /** diff --git a/lib/service.js b/lib/service.js index bc70fc0c..ac70982d 100644 --- a/lib/service.js +++ b/lib/service.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); const DistroUtils = require('./distro.js'); const Entity = require('./entity.js'); const debug = require('debug')('rclnodejs:service'); diff --git a/lib/subscription.js b/lib/subscription.js index c73af1d3..74914c5e 100644 --- a/lib/subscription.js +++ b/lib/subscription.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); const Entity = require('./entity.js'); const debug = require('debug')('rclnodejs:subscription'); diff --git a/lib/time.js b/lib/time.js index 176b8ded..a563e837 100644 --- a/lib/time.js +++ b/lib/time.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); const Duration = require('./duration.js'); const ClockType = require('./clock_type.js'); const S_TO_NS = 10n ** 9n; diff --git a/lib/time_source.js b/lib/time_source.js index fc6f5814..9b22d57b 100644 --- a/lib/time_source.js +++ b/lib/time_source.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); const { Clock, ROSClock } = require('./clock.js'); const { ClockType } = Clock; const { Parameter, ParameterType } = require('./parameter.js'); diff --git a/lib/timer.js b/lib/timer.js index 823bb49b..3b09b99b 100644 --- a/lib/timer.js +++ b/lib/timer.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); const DistroUtils = require('./distro.js'); /** diff --git a/lib/type_description_service.js b/lib/type_description_service.js index b2bd30bd..649e4b28 100644 --- a/lib/type_description_service.js +++ b/lib/type_description_service.js @@ -15,7 +15,7 @@ 'use strict'; const loader = require('./interface_loader.js'); -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); const Service = require('./service.js'); const { diff --git a/lib/validator.js b/lib/validator.js index 0144906b..0d8c5a6b 100644 --- a/lib/validator.js +++ b/lib/validator.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('./native_loader.js'); /** * An object - Representing a validator in ROS. diff --git a/package.json b/package.json index 53e535c5..40d9530b 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "generate-messages:dev": "node scripts/generate_messages.js --debug && npx --yes prettier --ignore-path --write generated/**/*.js", "generate-tsd-messages": "node scripts/generate_tsd.js", "clean": "node-gyp clean && npx rimraf ./generated", - "install": "npm run rebuild", + "install": "node scripts/install.js", "postinstall": "npm run generate-messages", "docs": "cd docs && make", "test": "nyc node --expose-gc ./scripts/run_test.js && tsd", @@ -30,7 +30,8 @@ "lint": "eslint && node ./scripts/cpplint.js", "format": "clang-format -i -style=file ./src/*.cpp ./src/*.h && npx --yes prettier --write \"{lib,rosidl_gen,rostsd_gen,rosidl_parser,types,example,test,scripts,benchmark,rostsd_gen}/**/*.{js,md,ts}\" ./*.{js,md,ts}", "prepare": "husky", - "coverage": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" + "coverage": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js", + "prebuild": "prebuildify --napi --strip --target 16.20.2 --target electron@23.0.0 && node scripts/tag_prebuilds.js" }, "bin": { "generate-ros-messages": "./scripts/generate_messages.js" @@ -64,6 +65,7 @@ "lint-staged": "^16.2.0", "mocha": "^11.0.2", "nyc": "^17.1.0", + "prebuildify": "^6.0.1", "rimraf": "^6.0.1", "sinon": "^21.0.0", "tree-kill": "^1.2.2", @@ -72,7 +74,6 @@ }, "dependencies": { "@rclnodejs/ref-array-di": "^1.2.2", - "@rclnodejs/ref-napi": "^4.0.0", "@rclnodejs/ref-struct-di": "^1.1.1", "bindings": "^1.5.0", "compare-versions": "^6.1.1", @@ -81,9 +82,9 @@ "fs-extra": "^11.2.0", "is-close": "^1.3.3", "json-bigint": "^1.0.0", + "node-addon-api": "^8.3.1", "terser": "^5.39.0", - "walk": "^2.3.15", - "node-addon-api": "^8.3.1" + "walk": "^2.3.15" }, "husky": { "hooks": { diff --git a/rosidl_gen/deallocator.js b/rosidl_gen/deallocator.js index 9dca2094..cd44ba68 100644 --- a/rosidl_gen/deallocator.js +++ b/rosidl_gen/deallocator.js @@ -14,7 +14,7 @@ 'use strict'; -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('../lib/native_loader.js'); let deallocator = { delayFreeStructMember(refObj, type, name) { diff --git a/rosidl_gen/primitive_types.js b/rosidl_gen/primitive_types.js index 7099fc6d..514148cc 100644 --- a/rosidl_gen/primitive_types.js +++ b/rosidl_gen/primitive_types.js @@ -14,9 +14,10 @@ 'use strict'; -const ref = require('@rclnodejs/ref-napi'); +const ref = require('../third_party/ref-napi'); const StructType = require('@rclnodejs/ref-struct-di')(ref); -const rclnodejs = require('bindings')('rclnodejs'); +// const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('../lib/native_loader.js'); const StringRefStruct = StructType({ data: ref.types.CString, diff --git a/rosidl_gen/templates/message.dot b/rosidl_gen/templates/message.dot index eff59e08..6f4f33fd 100644 --- a/rosidl_gen/templates/message.dot +++ b/rosidl_gen/templates/message.dot @@ -224,9 +224,9 @@ function extractMemberNames(fields) { }} {{? willUseTypedArray}} -const rclnodejs = require('bindings')('rclnodejs'); +const rclnodejs = require('../../lib/native_loader.js'); {{?}} -const ref = require('@rclnodejs/ref-napi'); +const ref = require('../../third_party/ref-napi'); const StructType = require('@rclnodejs/ref-struct-di')(ref); const ArrayType = require('@rclnodejs/ref-array-di')(ref); const primitiveTypes = require('../../rosidl_gen/primitive_types.js'); diff --git a/scripts/install.js b/scripts/install.js new file mode 100755 index 00000000..0955a88f --- /dev/null +++ b/scripts/install.js @@ -0,0 +1,126 @@ +// Copyright (c) 2025, The Robot Web Tools Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +function detectUbuntuCodename() { + if (process.platform !== 'linux') { + return null; + } + + try { + const osRelease = fs.readFileSync('/etc/os-release', 'utf8'); + const match = osRelease.match(/^VERSION_CODENAME=(.*)$/m); + return match ? match[1].trim() : null; + } catch { + return null; + } +} + +function getRosDistro() { + return process.env.ROS_DISTRO || null; +} + +function checkPrebuiltBinary() { + const platform = process.platform; + const arch = process.arch; + + // Only Linux has prebuilt binaries + if (platform !== 'linux') { + console.log( + `Platform ${platform} does not have prebuilt binaries, will build from source` + ); + return false; + } + + const ubuntuCodename = detectUbuntuCodename(); + const rosDistro = getRosDistro(); + + if (!ubuntuCodename || !rosDistro) { + console.log( + 'Cannot detect Ubuntu codename or ROS distribution, will build from source' + ); + return false; + } + + // Check for the specific prebuilt binary + const prebuildDir = path.join( + __dirname, + '..', + 'prebuilds', + `${platform}-${arch}` + ); + const expectedBinary = `${rosDistro}-${ubuntuCodename}-${arch}-rclnodejs.node`; + const binaryPath = path.join(prebuildDir, expectedBinary); + + if (fs.existsSync(binaryPath)) { + console.log(`✓ Found prebuilt binary: ${expectedBinary}`); + console.log(` Platform: ${platform}, Arch: ${arch}`); + console.log(` Ubuntu: ${ubuntuCodename}, ROS: ${rosDistro}`); + return true; + } + + console.log( + `✗ No prebuilt binary found for ${rosDistro}-${ubuntuCodename}-${arch}` + ); + + // List available binaries for debugging + if (fs.existsSync(prebuildDir)) { + const availableBinaries = fs + .readdirSync(prebuildDir) + .filter((f) => f.endsWith('.node')); + if (availableBinaries.length > 0) { + console.log( + ' Available prebuilt binaries:', + availableBinaries.join(', ') + ); + } + } + + return false; +} + +function buildFromSource() { + console.log('\n=== Building rclnodejs from source ==='); + try { + execSync('npm run rebuild', { + stdio: 'inherit', + cwd: path.join(__dirname, '..'), + timeout: 600000, // 10 minute timeout + }); + console.log('✓ Successfully built from source\n'); + } catch (error) { + console.error('✗ Failed to build from source:', error.message); + process.exit(1); + } +} + +function main() { + console.log('\n=== Installing rclnodejs ===\n'); + + if (checkPrebuiltBinary()) { + console.log('✓ Installation complete - using prebuilt binary\n'); + } else { + console.log('→ Building from source...\n'); + buildFromSource(); + } +} + +if (require.main === module) { + main(); +} + +module.exports = { checkPrebuiltBinary, buildFromSource }; diff --git a/scripts/tag_prebuilds.js b/scripts/tag_prebuilds.js new file mode 100755 index 00000000..9b0198f7 --- /dev/null +++ b/scripts/tag_prebuilds.js @@ -0,0 +1,79 @@ +// Copyright (c) 2025, The Robot Web Tools Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +const fs = require('fs'); +const path = require('path'); + +function detectUbuntuCodename() { + try { + const osRelease = fs.readFileSync('/etc/os-release', 'utf8'); + const match = osRelease.match(/^VERSION_CODENAME=(.*)$/m); + return match ? match[1].trim() : null; + } catch (e) { + return null; + } +} + +function tagPrebuilds() { + const rosDistro = process.env.ROS_DISTRO; + const ubuntuCodename = detectUbuntuCodename(); + const platform = process.platform; + const arch = process.arch; + + console.log( + `Tagging prebuilds with Ubuntu: ${ubuntuCodename || 'unknown'}, ROS2: ${rosDistro || 'unknown'}, Platform: ${platform}, Arch: ${arch}` + ); + + const prebuildDir = path.join( + __dirname, + '..', + 'prebuilds', + `${platform}-${arch}` + ); + + if (!fs.existsSync(prebuildDir)) { + console.log('No prebuilds directory found, skipping tagging'); + return; + } + + const files = fs.readdirSync(prebuildDir).filter((f) => f.endsWith('.node')); + + for (const file of files) { + const filePath = path.join(prebuildDir, file); + + // Create tagged version with format: {ros_distro}-{linux-codename}-{cpu-arch}-rclnodejs.node + if (rosDistro && ubuntuCodename) { + const taggedName = `${rosDistro}-${ubuntuCodename}-${arch}-rclnodejs.node`; + const taggedPath = path.join(prebuildDir, taggedName); + fs.copyFileSync(filePath, taggedPath); + console.log(`Created tagged binary: ${taggedName}`); + + // Remove the original generic binary file if it's the basic rclnodejs.node + if (file === 'rclnodejs.node') { + fs.unlinkSync(filePath); + console.log(`Removed generic binary: ${file}`); + } + } else { + console.log( + `Skipping tagging for ${file} - missing ROS_DISTRO or Ubuntu codename` + ); + } + } +} + +if (require.main === module) { + tagPrebuilds(); +} + +module.exports = { tagPrebuilds }; diff --git a/src/addon.cpp b/src/addon.cpp index b6b5c0cd..d5f2380a 100644 --- a/src/addon.cpp +++ b/src/addon.cpp @@ -40,6 +40,7 @@ #include "rcl_type_description_service_bindings.h" #endif #include "rcl_utilities.h" +#include "ref_napi_bindings.h" #include "shadow_node.h" bool IsRunningInElectronRenderer(const Napi::Env& env) { @@ -93,6 +94,8 @@ Napi::Object InitModule(Napi::Env env, Napi::Object exports) { rclnodejs::ShadowNode::Init(env, exports); rclnodejs::RclHandle::Init(env, exports); + exports.Set("ref", rclnodejs::InitRefNapi(env)); + #ifdef DEBUG_ON int result = rcutils_logging_set_logger_level(PACKAGE_NAME, RCUTILS_LOG_SEVERITY_DEBUG); diff --git a/third_party/ref-napi/index.js b/third_party/ref-napi/index.js new file mode 100644 index 00000000..3ff9e4f6 --- /dev/null +++ b/third_party/ref-napi/index.js @@ -0,0 +1,6 @@ +/** + * Vendored copy of @rclnodejs/ref-napi (MIT License). + * Entry point re-exporting the original module implementation. + */ + +module.exports = require('./lib/ref'); diff --git a/third_party/ref-napi/lib/ref.js b/third_party/ref-napi/lib/ref.js new file mode 100644 index 00000000..b05422d4 --- /dev/null +++ b/third_party/ref-napi/lib/ref.js @@ -0,0 +1,1746 @@ +'use strict'; +const assert = require('assert'); +const inspect = require('util').inspect; +const debug = require('debug')('ref'); +const os = require('os'); +const path = require('path'); +// const addon = require('bindings')({ +// bindings: 'rclnodejs', +// module_root: path.resolve(__dirname, '..', '..', '..'), +// }); +const addon = require('../../../lib/native_loader.js'); + +if (!addon || !addon.ref) { + throw new Error('Failed to load ref bindings from rclnodejs addon'); +} + +exports = module.exports = addon.ref; + +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 written to it, and a trailing NULL byte. + */ + +exports.allocCString = function allocCString(string, encoding) { + if (string == null || 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); + exports._attach(buf, ptr); +}; + +/** + * 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 Buffer.isBuffer(buf) + ? buf['readInt16' + exports.endianness](offset || 0) + : buf.readInt16(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 Buffer.isBuffer(buf) + ? buf['readUInt16' + exports.endianness](offset || 0) + : buf.readUInt16(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 exports.readInt32(buf, offset || 0); + }, + set: function set(buf, offset, val) { + return exports.writeInt32(buf, offset || 0, val); + }, +}; + +/** + * The `uint32` type. + */ + +types.uint32 = { + size: exports.sizeof.uint32, + indirection: 1, + get: function get(buf, offset) { + return Buffer.isBuffer(buf) + ? buf['readUInt32' + exports.endianness](offset || 0) + : buf.readUInt32(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 Buffer.isBuffer(buf) + ? buf['readFloat' + exports.endianness](offset || 0) + : buf.readFloat(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 Buffer.isBuffer(buf) + ? buf['readDouble' + exports.endianness](offset || 0) + : buf.readDouble(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] + ); + } +} + +/** + * ... + */ + +exports.PointerBuffer.prototype.hexAddress = function hexAddress() { + return exports.hexAddress(this, 0); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.ref = function ref() { + return exports.ref(this); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.deref = function deref() { + return exports.deref(this); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.readObject = function readObject(offset) { + return exports.readObject(this, offset); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.writeObject = function writeObject( + obj, + offset +) { + return exports.writeObject(this, offset, obj); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.readPointer = function readPointer( + offset, + size +) { + return exports.readPointer(this, offset, size); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.writePointer = function writePointer( + ptr, + offset +) { + return exports.writePointer(this, offset, ptr); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.readCString = function readCString(offset) { + return exports.readCString(this, offset); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.writeCString = function writeCString( + string, + offset, + encoding +) { + return exports.writeCString(this, offset, string, encoding); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.readInt64BE = function readInt64BE(offset) { + return exports.readInt64BE(this, offset); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.writeInt64BE = function writeInt64BE( + val, + offset +) { + return exports.writeInt64BE(this, offset, val); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.readUInt64BE = function readUInt64BE(offset) { + return exports.readUInt64BE(this, offset); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.writeUInt64BE = function writeUInt64BE( + val, + offset +) { + return exports.writeUInt64BE(this, offset, val); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.readInt64LE = function readInt64LE(offset) { + return exports.readInt64LE(this, offset); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.writeInt64LE = function writeInt64LE( + val, + offset +) { + return exports.writeInt64LE(this, offset, val); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.readUInt64LE = function readUInt64LE(offset) { + return exports.readUInt64LE(this, offset); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.writeUInt64LE = function writeUInt64LE( + val, + offset +) { + return exports.writeUInt64LE(this, offset, val); +}; + +exports.PointerBuffer.prototype.readUInt32 = function readUInt32(offset) { + return exports.readUInt32(this, offset); +}; + +exports.PointerBuffer.prototype.readInt8 = function readInt8(offset) { + return exports.readInt8(this, offset); +}; + +exports.PointerBuffer.prototype.readUInt8 = function readUInt8(offset) { + return exports.readUInt8(this, offset); +}; + +exports.PointerBuffer.prototype.readFloat = function readFloat(offset) { + return exports.readFloat(this, offset); +}; + +exports.PointerBuffer.prototype.readDouble = function readDouble(offset) { + return exports.readDouble(this, offset); +}; + +exports.PointerBuffer.prototype.readInt16 = function readInt16(offset) { + return exports.readInt16(this, offset); +}; + +exports.PointerBuffer.prototype.readUInt16 = function readUInt16(offset) { + return exports.readUInt16(this, offset); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.reinterpret = function reinterpret( + size, + offset +) { + return exports.reinterpret(this, size, offset); +}; + +/** + * ... + */ + +exports.PointerBuffer.prototype.reinterpretUntilZeros = + function reinterpretUntilZeros(size, offset) { + return exports.reinterpretUntilZeros(this, size, offset); + }; + +exports.PointerBuffer.isPointerBuffer = function isPointerBuffer(buffer) { + return buffer instanceof exports.PointerBuffer; +}; + +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()); + }; + } +} diff --git a/third_party/ref-napi/src/ref_napi_bindings.cpp b/third_party/ref-napi/src/ref_napi_bindings.cpp new file mode 100644 index 00000000..351bdc40 --- /dev/null +++ b/third_party/ref-napi/src/ref_napi_bindings.cpp @@ -0,0 +1,729 @@ +// Copyright (c) 2020 The ref-napi Authors. +// +// Licensed under the MIT License. See LICENSE in third_party/ref-napi. +// +// This file adapts the ref-napi addon initialization for integration +// into the rclnodejs native module. + +#include "ref_napi_bindings.h" + +#include +#include +#include +#include + +#include +#include + +#if !defined(NAPI_VERSION) || NAPI_VERSION < 6 +#include +#endif + +namespace RefNapi { + +class Instance { + public: + virtual napi_value WrapPointer(char* ptr, size_t length) = 0; + virtual char* GetBufferData(napi_value val) = 0; +}; + +} // namespace RefNapi + +#ifndef _WIN32 +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif +#include +#else +#define __alignof__ __alignof +#define snprintf(buf, bufSize, format, arg) \ + _snprintf_s(buf, bufSize, _TRUNCATE, format, arg) +#define strtoll _strtoi64 +#define strtoull _strtoui64 +#define PRId64 "lld" +#define PRIu64 "llu" +#endif + +namespace rclnodejs { + +using namespace Napi; + +namespace { + +// used by the Int64 functions to determine whether to return a Number +// or String based on whether or not a Number will lose precision. +// http://stackoverflow.com/q/307179/376773 +#define JS_MAX_INT +9007199254740992LL +#define JS_MIN_INT -9007199254740992LL + +// mirrors deps/v8/src/objects.h. +// we could use `node::Buffer::kMaxLength`, but it's not defined on node v0.6.x +static const size_t kMaxLength = 0x3fffffff; + +// Since Node.js v14.0.0, we have to keep a global list of all ArrayBuffer +// instances that we work with, in order not to create any duplicates. +// Luckily, N-API instance data is available on v14.x and above. +class InstanceData final : public RefNapi::Instance { + public: + explicit InstanceData(Env env) : env(env) {} + Env env; + FunctionReference pointer_ctor; + + napi_value WrapPointer(char* ptr, size_t length) override; + char* GetBufferData(napi_value val) override; + + static InstanceData* Get(Env env) { + return env.GetInstanceData(); + } +}; + +class PointerBuffer : public ObjectWrap { + public: + static Object Init(Napi::Env env, Object exports); + explicit PointerBuffer(Napi::CallbackInfo& info); + Napi::Value IsNull(const Napi::CallbackInfo& info); + Napi::Value Address(const Napi::CallbackInfo& info); + Napi::Value Length(const Napi::CallbackInfo& info); + Napi::Value Get(const Napi::CallbackInfo& info); + Napi::Value ToString(const Napi::CallbackInfo& info); + Napi::Value Copy(const Napi::CallbackInfo& info); + Napi::Value Slice(const Napi::CallbackInfo& info); + char* ptr_; + int length_; +}; + +Object PointerBuffer::Init(Napi::Env env, Object exports) { + Function func = + DefineClass(env, "PointerBuffer", + {InstanceMethod("isNull", &PointerBuffer::IsNull), + InstanceMethod("get", &PointerBuffer::Get), + InstanceMethod("address", &PointerBuffer::Address), + InstanceMethod("toString", &PointerBuffer::ToString), + InstanceMethod("copy", &PointerBuffer::Copy), + InstanceMethod("slice", &PointerBuffer::Slice), + InstanceAccessor<&PointerBuffer::Length>("length")}); + + exports.Set("PointerBuffer", func); + InstanceData* data = InstanceData::Get(env); + data->pointer_ctor = Persistent(func); + return exports; +} + +PointerBuffer::PointerBuffer(Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) { + Napi::Env env = info.Env(); + int length = info.Length(); + if (length <= 0 || !info[0].IsNumber()) { + Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException(); + return; + } + Napi::Number value = info[0].As(); + ptr_ = reinterpret_cast(value.Int64Value()); + length_ = (ptr_ == nullptr ? 0 : info[1].As().Int32Value()); +} + +Napi::Value PointerBuffer::IsNull(const Napi::CallbackInfo& info) { + return Boolean::New(info.Env(), ptr_ == nullptr); +} + +Napi::Value PointerBuffer::Address(const Napi::CallbackInfo& info) { + return Number::New(info.Env(), reinterpret_cast(ptr_)); +} + +Napi::Value PointerBuffer::Length(const Napi::CallbackInfo& info) { + return Number::New(info.Env(), length_); +} + +Napi::Value PointerBuffer::Get(const Napi::CallbackInfo& info) { + int32_t offset = info[0].As().Int32Value(); + return Number::New(info.Env(), ptr_[offset]); +} + +Napi::Value PointerBuffer::ToString(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + int length = info.Length(); + if (length == 1 || info[0].IsString()) { + std::string encoding = info[0].As(); + if (encoding == "utf-8") { + return String::New(info.Env(), ptr_, length_); + } else if (encoding == "ucs2") { + return String::New(info.Env(), reinterpret_cast(ptr_), + length_ / 2); + } else { + Napi::TypeError::New(env, "Unknown encoding argument: " + encoding) + .ThrowAsJavaScriptException(); + } + } + + return String::New(info.Env(), ptr_, length_); +} + +char* ExtractBufferData(Value val); + +Napi::Value PointerBuffer::Copy(const Napi::CallbackInfo& info) { + char* dest = + ExtractBufferData(info[0]) + info[1].As().Int64Value(); + uint64_t length = + info[3].As().Int64Value() - info[2].As().Int64Value(); + std::memcpy(dest, ptr_, length); + return Number::New(info.Env(), length); +} + +Value CreatePointerBuffer(Env env, char* ptr, size_t length); + +Napi::Value PointerBuffer::Slice(const Napi::CallbackInfo& info) { + int64_t offset = info[0].As().Int64Value(); + return CreatePointerBuffer(info.Env(), ptr_ + offset, length_ - offset); +} + +Value CreatePointerBuffer(Env env, char* ptr, size_t length) { + InstanceData* data = InstanceData::Get(env); + return data->pointer_ctor.New( + {Number::New(env, reinterpret_cast(ptr)), + Number::New(env, length)}); +} + +char* ExtractBufferData(Value val) { + if (!val.IsBuffer() && val.IsObject()) { + auto p = PointerBuffer::Unwrap(val.As()); + return p->ptr_; + } + + Buffer buf = val.As>(); + return buf.Data(); +} + +napi_value InstanceData::WrapPointer(char* ptr, size_t length) { + return CreatePointerBuffer(env, ptr, length); +} + +char* InstanceData::GetBufferData(napi_value val) { + return ExtractBufferData(Value(env, val)); +} + +char* AddressForArgs(const CallbackInfo& args, size_t offset_index = 1) { + Value buf = args[0]; + if (!(buf.IsBuffer() || buf.IsObject())) { + throw TypeError::New(args.Env(), + "Buffer or PointerBuffer instance expected"); + } + + int64_t offset = args[offset_index].ToNumber(); + return ExtractBufferData(buf) + offset; +} + +Value Address(const CallbackInfo& args) { + char* ptr = AddressForArgs(args); + uintptr_t intptr = reinterpret_cast(ptr); + + return Number::New(args.Env(), static_cast(intptr)); +} + +Value HexAddress(const CallbackInfo& args) { + char* ptr = AddressForArgs(args); + char strbuf[30]; + snprintf(strbuf, 30, "%p", ptr); + + if (strbuf[0] == '0' && strbuf[1] == 'x') { + ptr = strbuf + 2; + } else { + ptr = strbuf; + } + + return String::New(args.Env(), ptr); +} + +Value IsNull(const CallbackInfo& args) { + Value buf = args[0]; + if (!(buf.IsBuffer() || buf.IsObject())) { + return Boolean::New(args.Env(), false); + } + + char* ptr = AddressForArgs(args); + return Boolean::New(args.Env(), ptr == nullptr); +} + +Value IsAddress(const CallbackInfo& args) { + Value buf = args[0]; + if (!(buf.IsBuffer() || buf.IsObject())) { + return Boolean::New(args.Env(), false); + } + return Boolean::New(args.Env(), true); +} + +Value ReadObject(const CallbackInfo& args) { + char* ptr = AddressForArgs(args); + + if (ptr == nullptr) { + throw Error::New(args.Env(), + "readObject: Cannot read from nullptr pointer"); + } + + Reference* rptr = reinterpret_cast*>(ptr); + return rptr->Value(); +} + +void WriteObject(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args); + + if (ptr == nullptr) { + throw Error::New(env, "readObject: Cannot write to nullptr pointer"); + } + + Reference* rptr = reinterpret_cast*>(ptr); + if (args[2].IsObject()) { + Object val = args[2].As(); + *rptr = std::move(Reference::New(val)); + } else if (args[2].IsNull()) { + rptr->Reset(); + } else { + throw TypeError::New(env, + "WriteObject's 3rd argument needs to be an object"); + } +} + +Value ReadPointer(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args); + + if (ptr == nullptr) { + throw Error::New(env, "readPointer: Cannot read from nullptr pointer"); + } + + int64_t size = args[2].ToNumber(); + + char* val = *reinterpret_cast(ptr); + return CreatePointerBuffer(env, val, size); +} + +void WritePointer(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args); + Value input = args[2]; + + if (!input.IsNull() && !input.IsBuffer() && !input.IsObject()) { + throw TypeError::New( + env, "writePointer: Buffer instance expected as third argument"); + } + + if (input.IsNull()) { + *reinterpret_cast(ptr) = nullptr; + } else { + char* input_ptr = ExtractBufferData(input); + *reinterpret_cast(ptr) = input_ptr; + } +} + +Value ReadInt64(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args); + + if (ptr == nullptr) { + throw TypeError::New(env, "readInt64: Cannot read from nullptr pointer"); + } + + int64_t val = *reinterpret_cast(ptr); + + if (val < JS_MIN_INT || val > JS_MAX_INT) { + char strbuf[128]; + snprintf(strbuf, 128, "%" PRId64, val); + return String::New(env, strbuf); + } else { + return Number::New(env, val); + } +} + +Value ReadInt32(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args); + + if (ptr == nullptr) { + throw TypeError::New(env, "readInt64: Cannot read from nullptr pointer"); + } + + int32_t val = *reinterpret_cast(ptr); + return Number::New(env, val); +} + +void WriteInt64(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args); + + Value in = args[2]; + int64_t val; + if (in.IsNumber()) { + val = in.As(); + } else if (in.IsString()) { + char* endptr; + char* str; + int base = 0; + std::string _str = in.As(); + str = &_str[0]; + + errno = 0; + val = strtoll(str, &endptr, base); + + if (endptr == str) { + throw TypeError::New(env, + "writeInt64: no digits we found in input String"); + } else if (errno == ERANGE && (val == INT64_MAX || val == INT64_MIN)) { + throw TypeError::New( + env, "writeInt64: input String numerical value out of range"); + } else if (errno != 0 && val == 0) { + char errmsg[200]; + snprintf(errmsg, sizeof(errmsg), "writeInt64: %s", strerror(errno)); + throw TypeError::New(env, errmsg); + } + } else { + throw TypeError::New(env, + "writeInt64: Number/String 64-bit value required"); + } + + *reinterpret_cast(ptr) = val; +} + +void WriteInt32(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args); + + Value in = args[2]; + int64_t val; + if (in.IsNumber()) { + val = in.As(); + } else if (in.IsString()) { + char* endptr; + char* str; + int base = 0; + std::string _str = in.As(); + str = &_str[0]; + + errno = 0; + val = strtoll(str, &endptr, base); + + if (endptr == str) { + throw TypeError::New(env, + "writeInt32: no digits we found in input String"); + } else if (errno == ERANGE && (val == INT32_MAX || val == INT32_MIN)) { + throw TypeError::New( + env, "writeInt32: input String numerical value out of range"); + } else if (errno != 0 && val == 0) { + char errmsg[200]; + snprintf(errmsg, sizeof(errmsg), "writeInt32: %s", strerror(errno)); + throw TypeError::New(env, errmsg); + } + } else { + throw TypeError::New(env, + "writeInt32: Number/String 32-bit value required"); + } + + if (val < INT32_MIN || val > INT32_MAX) { + throw TypeError::New(env, "writeInt32: value out of range"); + } + + *reinterpret_cast(ptr) = static_cast(val); +} + +Value ReadUInt32(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args); + + if (ptr == nullptr) { + throw TypeError::New(env, "readUInt32: Cannot read from nullptr pointer"); + } + + uint32_t val = *reinterpret_cast(ptr); + return Number::New(env, val); +} + +Value ReadInt8(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args); + + if (ptr == nullptr) { + throw TypeError::New(env, "readInt8: Cannot read from nullptr pointer"); + } + + int8_t val = *reinterpret_cast(ptr); + return Number::New(env, val); +} + +Value ReadUInt8(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args); + + if (ptr == nullptr) { + throw TypeError::New(env, "readUInt8: Cannot read from nullptr pointer"); + } + + uint8_t val = *reinterpret_cast(ptr); + return Number::New(env, val); +} + +Value ReadFloat(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args); + + if (ptr == nullptr) { + throw TypeError::New(env, "readFloat: Cannot read from nullptr pointer"); + } + + float val = *reinterpret_cast(ptr); + return Number::New(env, val); +} + +Value ReadDouble(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args); + + if (ptr == nullptr) { + throw TypeError::New(env, "readDouble: Cannot read from nullptr pointer"); + } + + double val = *reinterpret_cast(ptr); + return Number::New(env, val); +} + +Value ReadInt16(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args); + + if (ptr == nullptr) { + throw TypeError::New(env, "readInt16: Cannot read from nullptr pointer"); + } + + int16_t val = *reinterpret_cast(ptr); + return Number::New(env, val); +} + +Value ReadUInt16(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args); + + if (ptr == nullptr) { + throw TypeError::New(env, "readUInt16: Cannot read from nullptr pointer"); + } + + uint16_t val = *reinterpret_cast(ptr); + return Number::New(env, val); +} + +Value ReadUInt64(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args); + + if (ptr == nullptr) { + throw TypeError::New(env, "readUInt64: Cannot read from nullptr pointer"); + } + + uint64_t val = *reinterpret_cast(ptr); + + if (val > JS_MAX_INT) { + char strbuf[128]; + snprintf(strbuf, 128, "%" PRIu64, val); + return String::New(env, strbuf); + } else { + return Number::New(env, val); + } +} + +void WriteUInt64(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args); + + Value in = args[2]; + uint64_t val; + if (in.IsNumber()) { + val = static_cast(in.As()); + } else if (in.IsString()) { + char* endptr; + char* str; + int base = 0; + std::string _str = in.As(); + str = &_str[0]; + + errno = 0; + val = strtoull(str, &endptr, base); + + if (endptr == str) { + throw TypeError::New(env, + "writeUInt64: no digits we found in input String"); + } else if (errno == ERANGE && (val == UINT64_MAX)) { + throw TypeError::New( + env, "writeUInt64: input String numerical value out of range"); + } else if (errno != 0 && val == 0) { + char errmsg[200]; + snprintf(errmsg, sizeof(errmsg), "writeUInt64: %s", strerror(errno)); + throw TypeError::New(env, errmsg); + } + } else { + throw TypeError::New(env, + "writeUInt64: Number/String 64-bit value required"); + } + + *reinterpret_cast(ptr) = val; +} + +Value ReadCString(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args); + + if (ptr == nullptr) { + throw Error::New(env, "readCString: Cannot read from nullptr pointer"); + } + + return String::New(env, ptr); +} + +Value ReinterpretBuffer(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args, 2); + + if (ptr == nullptr) { + throw Error::New(env, + "reinterpret: Cannot reinterpret from nullptr pointer"); + } + + int64_t size = args[1].ToNumber(); + + return CreatePointerBuffer(env, ptr, size); +} + +Value ReinterpretBufferUntilZeros(const CallbackInfo& args) { + Env env = args.Env(); + char* ptr = AddressForArgs(args, 2); + + if (ptr == nullptr) { + throw Error::New( + env, "reinterpretUntilZeros: Cannot reinterpret from nullptr pointer"); + } + + uint32_t numZeros = args[1].ToNumber(); + uint32_t i = 0; + size_t size = 0; + bool end = false; + + while (!end && size < kMaxLength) { + end = true; + for (i = 0; i < numZeros; i++) { + if (ptr[size + i] != 0) { + end = false; + break; + } + } + if (!end) { + size += numZeros; + } + } + + return CreatePointerBuffer(env, ptr, size); +} + +} // namespace + +Napi::Object InitRefNapi(Napi::Env env) { + Object exports = Object::New(env); + InstanceData* data = new InstanceData(env); + env.SetInstanceData(data); + + exports["instance"] = External::New(env, data); + + PointerBuffer::Init(env, exports); + + Object smap = Object::New(env); +#define SET_SIZEOF(name, type) smap[#name] = Number::New(env, sizeof(type)); + SET_SIZEOF(int8, int8_t); + SET_SIZEOF(uint8, uint8_t); + SET_SIZEOF(int16, int16_t); + SET_SIZEOF(uint16, uint16_t); + SET_SIZEOF(int32, int32_t); + SET_SIZEOF(uint32, uint32_t); + SET_SIZEOF(int64, int64_t); + SET_SIZEOF(uint64, uint64_t); + SET_SIZEOF(float, float); + SET_SIZEOF(double, double); + SET_SIZEOF(bool, bool); + SET_SIZEOF(byte, unsigned char); + SET_SIZEOF(char, char); + SET_SIZEOF(uchar, unsigned char); + SET_SIZEOF(short, short); + SET_SIZEOF(ushort, unsigned short); + SET_SIZEOF(int, int); + SET_SIZEOF(uint, unsigned int); + SET_SIZEOF(long, long); + SET_SIZEOF(ulong, unsigned long); + SET_SIZEOF(longlong, long long); + SET_SIZEOF(ulonglong, unsigned long long); + SET_SIZEOF(pointer, char*); + SET_SIZEOF(size_t, size_t); + SET_SIZEOF(Object, Reference); +#undef SET_SIZEOF + + Object amap = Object::New(env); +#define SET_ALIGNOF(name, type) \ + struct s_##name { \ + type a; \ + }; \ + amap[#name] = Number::New(env, alignof(struct s_##name)); + SET_ALIGNOF(int8, int8_t); + SET_ALIGNOF(uint8, uint8_t); + SET_ALIGNOF(int16, int16_t); + SET_ALIGNOF(uint16, uint16_t); + SET_ALIGNOF(int32, int32_t); + SET_ALIGNOF(uint32, uint32_t); + SET_ALIGNOF(int64, int64_t); + SET_ALIGNOF(uint64, uint64_t); + SET_ALIGNOF(float, float); + SET_ALIGNOF(double, double); + SET_ALIGNOF(bool, bool); + SET_ALIGNOF(char, char); + SET_ALIGNOF(uchar, unsigned char); + SET_ALIGNOF(short, short); + SET_ALIGNOF(ushort, unsigned short); + SET_ALIGNOF(int, int); + SET_ALIGNOF(uint, unsigned int); + SET_ALIGNOF(long, long); + SET_ALIGNOF(ulong, unsigned long); + SET_ALIGNOF(longlong, long long); + SET_ALIGNOF(ulonglong, unsigned long long); + SET_ALIGNOF(pointer, char*); + SET_ALIGNOF(size_t, size_t); + SET_ALIGNOF(Object, Reference); +#undef SET_ALIGNOF + + exports["sizeof"] = smap; + exports["alignof"] = amap; + exports["nullptr"] = exports["NULL"] = CreatePointerBuffer(env, nullptr, 0); + exports["address"] = Function::New(env, Address); + exports["hexAddress"] = Function::New(env, HexAddress); + exports["isNull"] = Function::New(env, IsNull); + exports["isAddress"] = Function::New(env, IsAddress); + exports["readObject"] = Function::New(env, ReadObject); + exports["_writeObject"] = Function::New(env, WriteObject); + exports["readPointer"] = Function::New(env, ReadPointer); + exports["_writePointer"] = Function::New(env, WritePointer); + exports["readInt64"] = Function::New(env, ReadInt64); + exports["writeInt64"] = Function::New(env, WriteInt64); + exports["readUInt64"] = Function::New(env, ReadUInt64); + exports["writeUInt64"] = Function::New(env, WriteUInt64); + + exports["readInt32"] = Function::New(env, ReadInt32); + exports["writeInt32"] = Function::New(env, WriteInt32); + + exports["readUInt32"] = Function::New(env, ReadUInt32); + exports["readInt8"] = Function::New(env, ReadInt8); + exports["readUInt8"] = Function::New(env, ReadUInt8); + exports["readFloat"] = Function::New(env, ReadFloat); + exports["readDouble"] = Function::New(env, ReadDouble); + exports["readInt16"] = Function::New(env, ReadInt16); + exports["readUInt16"] = Function::New(env, ReadUInt16); + + exports["readCString"] = Function::New(env, ReadCString); + exports["_reinterpret"] = Function::New(env, ReinterpretBuffer); + exports["_reinterpretUntilZeros"] = + Function::New(env, ReinterpretBufferUntilZeros); + + return exports; +} + +} // namespace rclnodejs diff --git a/third_party/ref-napi/src/ref_napi_bindings.h b/third_party/ref-napi/src/ref_napi_bindings.h new file mode 100644 index 00000000..8c3976fc --- /dev/null +++ b/third_party/ref-napi/src/ref_napi_bindings.h @@ -0,0 +1,19 @@ +// Copyright (c) 2020 The ref-napi Authors. +// +// Licensed under the MIT License. See LICENSE in third_party/ref-napi. +// +// This file adapts the ref-napi addon initialization for integration +// into the rclnodejs native module. + +#ifndef THIRD_PARTY_REF_NAPI_SRC_REF_NAPI_BINDINGS_H_ +#define THIRD_PARTY_REF_NAPI_SRC_REF_NAPI_BINDINGS_H_ + +#include + +namespace rclnodejs { + +Napi::Object InitRefNapi(Napi::Env env); + +} // namespace rclnodejs + +#endif // THIRD_PARTY_REF_NAPI_SRC_REF_NAPI_BINDINGS_H_