├── index.js ├── .npmignore ├── bin └── download-prebuilds.js ├── set-optional-deps.cjs ├── .circleci └── config.yml ├── .travis.yml ├── README.md ├── LICENSE ├── .gitignore ├── binding.gyp ├── package.json ├── .github └── workflows │ └── prebuild.yml └── src └── extract.cpp /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('node-gyp-build-optional-packages')(__dirname); -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | tests/samples 4 | .vs 5 | build/ 6 | .DS_Store -------------------------------------------------------------------------------- /bin/download-prebuilds.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { dirname } = require('path'); 4 | const { fileURLToPath } = require('url'); 5 | const { exec } = require('child_process'); 6 | 7 | process.chdir(dirname(__dirname)); 8 | exec('prebuildify-ci download', (error, stdout, stderr) => { 9 | console.error(stderr); 10 | console.log(stdout); 11 | if (error?.code) 12 | process.exit(error.code); 13 | }); 14 | -------------------------------------------------------------------------------- /set-optional-deps.cjs: -------------------------------------------------------------------------------- 1 | let fs = require('fs'); 2 | packageData = JSON.parse(fs.readFileSync('package.json')); 3 | let prebuilds = fs.readdirSync('prebuilds'); 4 | let platformDeps = packageData.optionalDependencies = {}; 5 | let packageName = packageData.name; 6 | let version = packageData.version; 7 | for (let prebuild of prebuilds) { 8 | platformDeps['@' + packageName + '/' + packageName + '-' + prebuild] = version; 9 | } 10 | fs.writeFileSync('package.json', JSON.stringify(packageData, null, 2)); 11 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | name: Test and Prebuild 2 | jobs: 3 | build: 4 | executor: my-ubuntu-exec 5 | steps: 6 | - uses: actions/checkout@v2 7 | - name: Setup node 8 | uses: actions/setup-node@v2 9 | with: 10 | node-version: 12 11 | - run: npm install 12 | - run: npm run prebuild 13 | - run: zip -r prebuild-darwin.zip prebuilds 14 | - run: hexdump "prebuilds/darwin-x64/node.abi93.node" | head -10 15 | - name: Prebuild 16 | uses: softprops/action-gh-release@v1 17 | if: startsWith(github.ref, 'refs/tags/') 18 | with: 19 | files: prebuild-darwin.zip 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - node 5 | addons: 6 | apt: 7 | sources: 8 | - ubuntu-toolchain-r-test 9 | arch: 10 | - amd64 11 | - arm64 12 | os: 13 | - osx 14 | - linux 15 | osx_image: xcode12.5 16 | before_deploy: 17 | - ARCHIVE_NAME="${TRAVIS_TAG:-latest}-$TRAVIS_OS_NAME-`uname -m`.tar" 18 | - npm run prebuild 19 | - tar --create --verbose --file="$ARCHIVE_NAME" --directory "$TRAVIS_BUILD_DIR/prebuilds" 20 | . 21 | deploy: 22 | provider: releases 23 | draft: false 24 | prerelease: true 25 | file: "$ARCHIVE_NAME" 26 | skip_cleanup: true 27 | on: 28 | tags: true 29 | node: node 30 | api_key: '$GITHUB_API_KEY' 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | This module is designed to do fast and efficient native/C-level extraction of strings from MessagePack binary data. This works by calling `extractStrings(buffer, start, end)`, and it will extract strings by doing partial MessagePack parsing, and scanning to find the string data in the range specified in the buffer. It will return an array of strings that it finds. When it finds strings that can be represented with latin-1/one-byte strings (and important V8 optimization), it will attempt return a continuous string of MessagePack data that contains multiple sub-strings, so the decoder can slice off strings by offset. When a string contains non-latin characters, and must be represented as a two-byte string, this will always be returned as the string alone without combination with any other strings. The extractor will return an array of a maximum of 256 strings. The decoder can call the extractStrings again, with a new offset to continue extracting more strings as necessary. 3 | 4 | ## License 5 | MIT -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Kris Zyp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | dist 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | yarn.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (http://nodejs.org/api/addons.html) 35 | build/Release 36 | prebuilds 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | package-lock.json 43 | # Typescript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | tests/samples 64 | 65 | # Visual Studio Code directory 66 | .vscode 67 | .vs 68 | 69 | .DS_Store 70 | build -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "os_linux_compiler%": "gcc", 4 | "enable_v8%": "true", 5 | "enable_pointer_compression%": "false", 6 | "build_v8_with_gn": "false" 7 | }, 8 | "conditions": [ 9 | ['OS=="win"', { 10 | "variables": { 11 | "enable_v8%": "=7", { 39 | "cflags": [ 40 | "-Wimplicit-fallthrough=2", 41 | ], 42 | }], 43 | ], 44 | "ldflags": [ 45 | "-fPIC", 46 | "-fvisibility=hidden" 47 | ], 48 | "cflags": [ 49 | "-fPIC", 50 | "-fvisibility=hidden", 51 | "-O3" 52 | ], 53 | }], 54 | ["enable_v8!='false'", { 55 | "defines": ["ENABLE_V8_API=1"] 56 | }], 57 | ["enable_pointer_compression=='true'", { 58 | "defines": ["V8_COMPRESS_POINTERS", "V8_COMPRESS_POINTERS_IN_ISOLATE_CAGE"], 59 | }], 60 | ], 61 | } 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "msgpackr-extract", 3 | "author": "Kris Zyp", 4 | "version": "3.0.3", 5 | "description": "Node addon for string extraction for msgpackr", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "http://github.com/kriszyp/msgpackr-extract" 10 | }, 11 | "scripts": { 12 | "install": "node-gyp-build-optional-packages", 13 | "recompile": "node-gyp rebuild", 14 | "before-publish": "prebuildify-ci download && node set-optional-deps.cjs", 15 | "prebuild": "prebuildify-platform-packages --target 20.14.0", 16 | "prebuild-win32": "prebuildify-platform-packages --target 20.14.0 && set ENABLE_V8_FUNCTIONS=false&& prebuildify-platform-packages --platform-packages --napi --target 20.14.0", 17 | "prebuild-libc": "prebuildify-platform-packages --tag-libc --target 20.14.0 && prebuildify-platform-packages --platform-packages --napi --tag-libc --target 22.2.0 && ENABLE_V8_FUNCTIONS=false prebuildify-platform-packages --platform-packages --napi --tag-libc --target 20.14.0", 18 | "prebuild-libc-napi": "ENABLE_V8_FUNCTIONS=false prebuildify-platform-packages --platform-packages --napi --tag-libc --target 20.14.0", 19 | "prebuild-libc-alpine": "prebuildify-cross --image alpine --tag-libc --target 20.14.0", 20 | "publish-all": "cd prebuilds/win32-x64 && npm publish --access public && cd ../darwin-x64 && npm publish --access public && cd ../darwin-arm64 && npm publish --access public && cd ../linux-x64 && npm publish --access public && cd ../linux-arm64 && npm publish --access public && cd ../linux-arm && npm publish --access public && cd ../.. && npm publish --access public", 21 | "test": "node ./index.js" 22 | }, 23 | "main": "./index.js", 24 | "gypfile": true, 25 | "dependencies": { 26 | "node-gyp-build-optional-packages": "5.2.2" 27 | }, 28 | "files": [ 29 | "index.js", 30 | "/src", 31 | "/*.gyp", 32 | "/bin" 33 | ], 34 | "bin": { 35 | "download-msgpackr-prebuilds": "./bin/download-prebuilds.js" 36 | }, 37 | "optionalDependencies": { 38 | }, 39 | "devDependencies": { 40 | "prebuildify-platform-packages": "5.0.4", 41 | "prebuildify-ci": "^1.0.5", 42 | "prebuildify-cross": "5.0.0" 43 | } 44 | } -------------------------------------------------------------------------------- /.github/workflows/prebuild.yml: -------------------------------------------------------------------------------- 1 | name: Prebuild 2 | on: [push] 3 | jobs: 4 | build-test-macos: 5 | if: startsWith(github.ref, 'refs/tags/') 6 | runs-on: macos-12 7 | steps: 8 | - uses: actions/checkout@v2 9 | - name: Setup node 10 | uses: actions/setup-node@v2 11 | with: 12 | node-version: 16 13 | - run: python3 -m pip install setuptools 14 | - run: npm install 15 | - run: npm test 16 | - run: npm run prebuild-libc-napi 17 | if: startsWith(github.ref, 'refs/tags/') 18 | - run: npm run prebuild-libc-napi 19 | if: startsWith(github.ref, 'refs/tags/') 20 | env: 21 | PREBUILD_ARCH: arm64 22 | - run: tar --create --format ustar --verbose --file=prebuild-darwin.tar -C prebuilds . 23 | if: startsWith(github.ref, 'refs/tags/') 24 | - name: Prebuild 25 | uses: softprops/action-gh-release@v1 26 | if: startsWith(github.ref, 'refs/tags/') 27 | with: 28 | files: prebuild-darwin.tar 29 | build-test-win32: 30 | if: startsWith(github.ref, 'refs/tags/') 31 | runs-on: windows-latest 32 | steps: 33 | - uses: actions/checkout@v2 34 | - name: Setup node 35 | uses: actions/setup-node@v2 36 | with: 37 | node-version: 16 38 | - run: npm install 39 | - run: npm run prebuild-win32 40 | - run: tar --create --verbose --file=prebuild-win32.tar -C prebuilds . 41 | #if: startsWith(github.ref, 'refs/tags/') 42 | - name: Prebuild 43 | uses: softprops/action-gh-release@v1 44 | if: startsWith(github.ref, 'refs/tags/') 45 | with: 46 | files: prebuild-win32.tar 47 | build-centos-7: 48 | #if: startsWith(github.ref, 'refs/tags/') 49 | runs-on: ubuntu-20.04 50 | container: quay.io/pypa/manylinux2014_x86_64 51 | steps: 52 | #- run: ldd --version ldd 53 | #- uses: actions/checkout@v2 54 | # with: 55 | # repository: 'kriszyp/musl-bins' 56 | #- run: tar -xf aarch64-linux-musl-cross.tgz && pwd && ls 57 | - uses: actions/checkout@v2 58 | - name: Setup node 59 | uses: actions/setup-node@v2 60 | with: 61 | node-version: 16 62 | - run: yum update -y && yum install -y python3 63 | - run: curl https://raw.githubusercontent.com/kriszyp/musl-bins/main/aarch64-linux-musl-cross.tgz --output aarch64-linux-musl-cross.tgz 64 | - run: tar -xf aarch64-linux-musl-cross.tgz && pwd && ls 65 | - run: curl https://raw.githubusercontent.com/kriszyp/musl-bins/main/armv7l-linux-musleabihf-cross.tgz --output armv7l-linux-musleabihf-cross.tgz 66 | - run: tar -xf armv7l-linux-musleabihf-cross.tgz && pwd && ls 67 | - run: curl https://raw.githubusercontent.com/kriszyp/musl-bins/main/x86_64-linux-musl-native.tgz --output x86_64-linux-musl-native.tgz 68 | - run: tar -xf x86_64-linux-musl-native.tgz && pwd && ls 69 | - run: npm install 70 | - run: npm run prebuild-libc 71 | if: startsWith(github.ref, 'refs/tags/') 72 | env: 73 | PREBUILD_LIBC: musl 74 | PREBUILD_ARCH: arm64 75 | CC: ${PWD}/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc 76 | CXX: ${PWD}/aarch64-linux-musl-cross/bin/aarch64-linux-musl-g++ 77 | - run: npm run prebuild-libc 78 | if: startsWith(github.ref, 'refs/tags/') 79 | env: 80 | PREBUILD_LIBC: musl 81 | PREBUILD_ARCH: arm 82 | PREBUILD_ARMV: 7 83 | CC: ${PWD}/armv7l-linux-musleabihf-cross/bin/armv7l-linux-musleabihf-gcc 84 | CXX: ${PWD}/armv7l-linux-musleabihf-cross/bin/armv7l-linux-musleabihf-g++ 85 | - run: npm run prebuild-libc 86 | if: startsWith(github.ref, 'refs/tags/') 87 | env: 88 | PREBUILD_LIBC: musl 89 | PREBUILD_ARCH: x64 90 | CC: ${PWD}/x86_64-linux-musl-native/bin/x86_64-linux-musl-gcc 91 | CXX: ${PWD}/x86_64-linux-musl-native/bin/x86_64-linux-musl-g++ 92 | - run: npm run prebuild-libc 93 | - run: npm test 94 | - run: tar --create --verbose --file=prebuild-linux.tar -C prebuilds . 95 | - name: Prebuild 96 | if: startsWith(github.ref, 'refs/tags/') 97 | uses: softprops/action-gh-release@v1 98 | with: 99 | files: prebuild-linux.tar 100 | -------------------------------------------------------------------------------- /src/extract.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This is responsible for extracting the strings, in bulk, from a MessagePack buffer. Creating strings from buffers can 3 | be one of the biggest performance bottlenecks of parsing, but creating an array of extracting strings all at once 4 | provides much better performance. This will parse and produce up to 256 strings at once .The JS parser can call this multiple 5 | times as necessary to get more strings. This must be partially capable of parsing MessagePack so it can know where to 6 | find the string tokens and determine their position and length. All strings are decoded as UTF-8. 7 | */ 8 | #include 9 | #if ENABLE_V8_API 10 | #include 11 | #endif 12 | 13 | #ifndef thread_local 14 | #ifdef __GNUC__ 15 | # define thread_local __thread 16 | #elif __STDC_VERSION__ >= 201112L 17 | # define thread_local _Thread_local 18 | #elif defined(_MSC_VER) 19 | # define thread_local __declspec(thread) 20 | #else 21 | # define thread_local 22 | #endif 23 | #endif 24 | 25 | const int MAX_TARGET_SIZE = 255; 26 | const uint32_t UNEXPECTED_END = 0xffffffff; 27 | typedef uint32_t (*token_handler)(uint8_t* source, uint32_t position, uint32_t size); 28 | token_handler tokenTable[256] = {}; 29 | napi_value unexpectedEnd(napi_env env) { 30 | napi_value returnValue; 31 | napi_get_undefined(env, &returnValue); 32 | napi_throw_type_error(env, NULL, "Unexpected end of buffer reading string"); 33 | return returnValue; 34 | } 35 | 36 | class Extractor { 37 | public: 38 | // napi_ref targetArray; // could consider reenabling this optimization for napi 39 | bool hasTargetArray = false; 40 | uint8_t* source; 41 | uint32_t position = 0; 42 | uint32_t writePosition = 0; 43 | uint32_t stringStart = 0; 44 | uint32_t lastStringEnd = 0; 45 | 46 | void readString(napi_env env, uint32_t length, bool allowStringBlocks, napi_value* target) { 47 | uint32_t start = position; 48 | uint32_t end = position + length; 49 | if (allowStringBlocks) { // for larger strings, we don't bother to check every character for being latin, and just go right to creating a new string 50 | while(position < end) { 51 | if (source[position] < 0x80) // ensure we character is latin and can be decoded as one byte 52 | position++; 53 | else { 54 | break; 55 | } 56 | } 57 | } 58 | if (position < end) { 59 | // non-latin character 60 | if (lastStringEnd) { 61 | napi_value value; 62 | napi_create_string_latin1(env, (const char*) source + stringStart, lastStringEnd - stringStart, &value); 63 | target[writePosition++] = value; 64 | lastStringEnd = 0; 65 | } 66 | // use standard utf-8 conversion 67 | napi_value value; 68 | napi_create_string_utf8(env, (const char*) source + start, (int) length, &value); 69 | target[writePosition++] = value; 70 | position = end; 71 | return; 72 | } 73 | 74 | if (lastStringEnd) { 75 | if (start - lastStringEnd > 40 || end - stringStart > 6000) { 76 | napi_value value; 77 | napi_create_string_latin1(env, (const char*) source + stringStart, lastStringEnd - stringStart, &value); 78 | target[writePosition++] = value; 79 | stringStart = start; 80 | } 81 | } else { 82 | stringStart = start; 83 | } 84 | lastStringEnd = end; 85 | } 86 | 87 | napi_value extractStrings(napi_env env, uint32_t startingPosition, uint32_t size, uint8_t* inputSource) { 88 | napi_value target[MAX_TARGET_SIZE + 1]; // leave one for the queued string 89 | writePosition = 0; 90 | lastStringEnd = 0; 91 | position = startingPosition; 92 | source = inputSource; 93 | while (position < size) { 94 | uint8_t token = source[position++]; 95 | if (token < 0xa0) { 96 | // all one byte tokens 97 | } else if (token < 0xc0) { 98 | // fixstr, we want to convert this 99 | token -= 0xa0; 100 | if (token + position > size) { 101 | return unexpectedEnd(env); 102 | } 103 | readString(env, token, true, target); 104 | if (writePosition >= MAX_TARGET_SIZE) 105 | break; 106 | } else if (token <= 0xdb && token >= 0xd9) { 107 | if (token == 0xd9) { //str 8 108 | if (position >= size) { 109 | return unexpectedEnd(env); 110 | } 111 | uint32_t length = source[position++]; 112 | if (length + position > size) { 113 | return unexpectedEnd(env); 114 | } 115 | readString(env,length, true, target); 116 | } else if (token == 0xda) { //str 16 117 | if (2 + position > size) { 118 | return unexpectedEnd(env); 119 | } 120 | uint32_t length = source[position++] << 8; 121 | length += source[position++]; 122 | if (length + position > size) { 123 | return unexpectedEnd(env); 124 | } 125 | readString(env,length, false, target); 126 | } else { //str 32 127 | if (4 + position > size) { 128 | return unexpectedEnd(env); 129 | } 130 | uint32_t length = source[position++] << 24; 131 | length += source[position++] << 16; 132 | length += source[position++] << 8; 133 | length += source[position++]; 134 | if (length + position > size) { 135 | return unexpectedEnd(env); 136 | } 137 | readString(env, length, false, target); 138 | } 139 | if (writePosition >= MAX_TARGET_SIZE) 140 | break; 141 | } else { 142 | auto handle = tokenTable[token]; 143 | if ((size_t ) handle < 20) { 144 | position += (size_t ) handle; 145 | } else { 146 | position = tokenTable[token](source, position, size); 147 | if (position == UNEXPECTED_END) { 148 | return unexpectedEnd(env); 149 | } 150 | } 151 | } 152 | } 153 | 154 | if (lastStringEnd) { 155 | napi_value value; 156 | napi_create_string_latin1(env, (const char*) source + stringStart, lastStringEnd - stringStart, &value); 157 | if (writePosition == 0) { 158 | return value; 159 | } 160 | target[writePosition++] = value; 161 | } else if (writePosition == 1) { 162 | return target[0]; 163 | } 164 | napi_value array; 165 | #if ENABLE_V8_API 166 | v8::Local v8Array = v8::Array::New(v8::Isolate::GetCurrent(), (v8::Local*) target, writePosition); 167 | memcpy(&array, &v8Array, sizeof(array)); 168 | #else 169 | napi_create_array_with_length(env, writePosition, &array); 170 | for (uint32_t i = 0; i < writePosition; i++) { 171 | napi_set_element(env, array, i, target[i]); 172 | } 173 | #endif 174 | return array; 175 | } 176 | }; 177 | 178 | void setupTokenTable() { 179 | for (int i = 0; i < 256; i++) { 180 | tokenTable[i] = nullptr; 181 | } 182 | // uint8, int8 183 | tokenTable[0xcc] = tokenTable[0xd0] = (token_handler) 1; 184 | // uint16, int16, array 16, map 16, fixext 1 185 | tokenTable[0xcd] = tokenTable[0xd1] = tokenTable[0xdc] = tokenTable[0xde] = tokenTable[0xd4] = (token_handler) 2; 186 | // fixext 16 187 | tokenTable[0xd5] = (token_handler) 3; 188 | // uint32, int32, float32, array 32, map 32 189 | tokenTable[0xce] = tokenTable[0xd2] = tokenTable[0xca] = tokenTable[0xdd] = tokenTable[0xdf] = (token_handler) 4; 190 | // fixext 4 191 | tokenTable[0xd6] = (token_handler) 5; 192 | // uint64, int64, float64, fixext 8 193 | tokenTable[0xcf] = tokenTable[0xd3] = tokenTable[0xcb] = (token_handler) 8; 194 | // fixext 8 195 | tokenTable[0xd7] = (token_handler) 9; 196 | // fixext 16 197 | tokenTable[0xd8] = (token_handler) 17; 198 | // bin 8 199 | tokenTable[0xc4] = ([](uint8_t* source, uint32_t position, uint32_t size) -> uint32_t { 200 | if (position >= size) { 201 | return UNEXPECTED_END; 202 | } 203 | uint32_t length = source[position++]; 204 | return position + length; 205 | }); 206 | // bin 16 207 | tokenTable[0xc5] = ([](uint8_t* source, uint32_t position, uint32_t size) -> uint32_t { 208 | if (position + 2 > size) { 209 | return UNEXPECTED_END; 210 | } 211 | uint32_t length = source[position++] << 8; 212 | length += source[position++]; 213 | return position + length; 214 | }); 215 | // bin 32 216 | tokenTable[0xc6] = ([](uint8_t* source, uint32_t position, uint32_t size) -> uint32_t { 217 | if (position + 4 > size) 218 | return UNEXPECTED_END; 219 | uint32_t length = source[position++] << 24; 220 | length += source[position++] << 16; 221 | length += source[position++] << 8; 222 | length += source[position++]; 223 | return position + length; 224 | }); 225 | // ext 8 226 | tokenTable[0xc7] = ([](uint8_t* source, uint32_t position, uint32_t size) -> uint32_t { 227 | if (position >= size) 228 | return UNEXPECTED_END; 229 | uint32_t length = source[position++]; 230 | position++; 231 | return position + length; 232 | }); 233 | // ext 16 234 | tokenTable[0xc8] = ([](uint8_t* source, uint32_t position, uint32_t size) -> uint32_t { 235 | if (position + 2 > size) 236 | return UNEXPECTED_END; 237 | uint32_t length = source[position++] << 8; 238 | length += source[position++]; 239 | position++; 240 | return position + length; 241 | }); 242 | // ext 32 243 | tokenTable[0xc9] = ([](uint8_t* source, uint32_t position, uint32_t size) -> uint32_t { 244 | if (position + 4 > size) 245 | return UNEXPECTED_END; 246 | uint32_t length = source[position++] << 24; 247 | length += source[position++] << 16; 248 | length += source[position++] << 8; 249 | length += source[position++]; 250 | position++; 251 | return position + length; 252 | }); 253 | } 254 | static thread_local Extractor* extractor; 255 | napi_value extractStrings(napi_env env, napi_callback_info info) { 256 | size_t argc = 3; 257 | napi_value args[3]; 258 | napi_get_cb_info(env, info, &argc, args, NULL, NULL); 259 | uint32_t position; 260 | uint32_t size; 261 | napi_get_value_uint32(env, args[0], &position); 262 | napi_get_value_uint32(env, args[1], &size); 263 | uint8_t* source; 264 | size_t buffer_size; 265 | napi_get_arraybuffer_info(env, args[2], (void**) &source, &buffer_size); 266 | return extractor->extractStrings(env, position, size, source); 267 | } 268 | #define EXPORT_NAPI_FUNCTION(name, func) { napi_property_descriptor desc = { name, 0, func, 0, 0, 0, (napi_property_attributes) (napi_writable | napi_configurable), 0 }; napi_define_properties(env, exports, 1, &desc); } 269 | NAPI_MODULE_INIT() { 270 | extractor = new Extractor(); // create our thread-local extractor 271 | setupTokenTable(); 272 | EXPORT_NAPI_FUNCTION("extractStrings", extractStrings); 273 | return exports; 274 | } 275 | --------------------------------------------------------------------------------