├── .eslintrc.json ├── .github └── workflows │ ├── codeql.yml │ └── node.js.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── lib ├── callback.js ├── firebird.msg ├── firebird.msg.json ├── gdscodes.d.ts ├── gdscodes.js ├── index.d.ts ├── index.js ├── messages.js ├── pool.js ├── srp.js ├── unix-crypt.js ├── utils.js └── wire │ ├── connection.js │ ├── const.js │ ├── database.js │ ├── eventConnection.js │ ├── fbEventManager.js │ ├── serialize.js │ ├── service.js │ ├── socket.js │ ├── statement.js │ ├── transaction.js │ └── xsqlvar.js ├── package-lock.json ├── package.json └── test ├── config.js ├── fbtrace-2.conf ├── fbtrace-3.conf ├── image.png ├── index.js ├── service.js └── srp.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module", 5 | "ecmaFeatures": { 6 | "jsx": true 7 | } 8 | }, 9 | "rules": { 10 | "semi": "error" 11 | } 12 | } -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '26 2 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Use only 'java' to analyze code written in Java, Kotlin or both 38 | # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both 39 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 40 | 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v3 44 | 45 | # Initializes the CodeQL tools for scanning. 46 | - name: Initialize CodeQL 47 | uses: github/codeql-action/init@v2 48 | with: 49 | languages: ${{ matrix.language }} 50 | # If you wish to specify custom queries, you can do so here or in a config file. 51 | # By default, queries listed here will override any specified in a config file. 52 | # Prefix the list here with "+" to use these queries and those in the config file. 53 | 54 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 55 | # queries: security-extended,security-and-quality 56 | 57 | 58 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 59 | # If this step fails, then you should remove it and run the build manually (see below) 60 | - name: Autobuild 61 | uses: github/codeql-action/autobuild@v2 62 | 63 | # ℹ️ Command-line programs to run using the OS shell. 64 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 65 | 66 | # If the Autobuild fails above, remove it and uncomment the following three lines. 67 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 68 | 69 | # - run: | 70 | # echo "Run, Build Application using script" 71 | # ./location_of_script_within_repo/buildscript.sh 72 | 73 | - name: Perform CodeQL Analysis 74 | uses: github/codeql-action/analyze@v2 75 | with: 76 | category: "/language:${{matrix.language}}" 77 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | node: [18, 20] 12 | firebird-version: ['v3'] 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 10 18 | 19 | - name: Setup FirebirdSQL container 20 | uses: juarezr/firebirdsql-github-action@v1.2.0 21 | with: 22 | version: ${{ matrix.firebird-version }} 23 | isc_password: "masterkey" 24 | enable_legacy_client_auth: "true" 25 | 26 | - name: Use Node.js ${{ matrix.node }} 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: ${{ matrix.node }} 30 | 31 | 32 | - name: Build 33 | shell: bash 34 | run: | 35 | npm ci 36 | 37 | - name: Test (Linux) 38 | run: | 39 | export FIREBIRD_DATA=/firebird/data 40 | npx nyc npm test 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project ignores 2 | node_modules/ 3 | test/*.fdb 4 | test/*.fbk 5 | *.fdb 6 | .nyc_output 7 | .vscode 8 | coverage 9 | 10 | # Jetbrains IDE 11 | .idea 12 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pure JavaScript and Asynchronous Firebird client for Node.js 2 | 3 | ![Firebird Logo](https://firebirdsql.org/file/about/firebird-logo-90.png) 4 | 5 | [![NPM version][npm-version-image]][npm-url] [![NPM downloads][npm-downloads-image]][npm-url] [![Mozilla License][license-image]][license-url] 6 | 7 | [![NPM](https://nodei.co/npm/node-firebird.png?downloads=true&downloadRank=true)](https://nodei.co/npm/node-firebird/) 8 | 9 | [Firebird forum](https://groups.google.com/forum/#!forum/node-firebird) on Google Groups. 10 | 11 | ## Firebird database on social networks 12 | 13 | - [Firebird on Twitter](https://twitter.com/firebirdsql/) 14 | - [Firebird on Facebook](https://www.facebook.com/FirebirdSQL) 15 | 16 | ## Changelog for version v0.2.x 17 | 18 | - added auto-reconnect 19 | - added [sequentially selects](https://github.com/hgourvest/node-firebird/wiki/What-is-sequentially-selects) 20 | - events for connection (attach, detach, row, result, transaction, commit, rollback, error, etc.) 21 | - performance improvements 22 | - supports inserting/updating buffers and streams 23 | - reading blobs (sequentially) 24 | - pooling 25 | - `database.detach()` waits for last command 26 | - better unit-test 27 | 28 | --- 29 | 30 | - [Firebird documentation](https://firebirdsql.org/en/documentation/) 31 | - [Firebird limits and data types](https://firebirdsql.org/en/firebird-technical-specifications/) 32 | 33 | ## Installation 34 | 35 | ```bash 36 | npm install node-firebird 37 | ``` 38 | 39 | ## Usage 40 | 41 | ```js 42 | var Firebird = require('node-firebird'); 43 | ``` 44 | 45 | ### Methods 46 | 47 | - `Firebird.escape(value) -> return {String}` - prevent for SQL Injections 48 | - `Firebird.attach(options, function(err, db))` attach a database 49 | - `Firebird.create(options, function(err, db))` create a database 50 | - `Firebird.attachOrCreate(options, function(err, db))` attach or create database 51 | - `Firebird.pool(max, options) -> return {Object}` create a connection pooling 52 | 53 | ## Connection types 54 | 55 | ### Connection options 56 | 57 | ```js 58 | var options = {}; 59 | 60 | options.host = '127.0.0.1'; 61 | options.port = 3050; 62 | options.database = 'database.fdb'; 63 | options.user = 'SYSDBA'; 64 | options.password = 'masterkey'; 65 | options.lowercase_keys = false; // set to true to lowercase keys 66 | options.role = null; // default 67 | options.pageSize = 4096; // default when creating database 68 | options.retryConnectionInterval = 1000; // reconnect interval in case of connection drop 69 | options.blobAsText = false; // set to true to get blob as text, only affects blob subtype 1 70 | options.encoding = 'UTF8'; // default encoding for connection is UTF-8 71 | options.wireCompression = false; // set to true for enable firebird compression on the wire (Work only on FB >= 3 and compression is enable on server (WireCompression = true in firebird.conf)) 72 | ``` 73 | 74 | ### Classic 75 | 76 | ```js 77 | Firebird.attach(options, function (err, db) { 78 | if (err) throw err; 79 | 80 | // db = DATABASE 81 | db.query('SELECT * FROM TABLE', function (err, result) { 82 | // IMPORTANT: close the connection 83 | db.detach(); 84 | }); 85 | }); 86 | ``` 87 | 88 | ### Pooling 89 | 90 | ```js 91 | // 5 = the number is count of opened sockets 92 | var pool = Firebird.pool(5, options); 93 | 94 | // Get a free pool 95 | pool.get(function (err, db) { 96 | if (err) throw err; 97 | 98 | // db = DATABASE 99 | db.query('SELECT * FROM TABLE', function (err, result) { 100 | // IMPORTANT: release the pool connection 101 | db.detach(); 102 | }); 103 | }); 104 | 105 | // Destroy pool 106 | pool.destroy(); 107 | ``` 108 | 109 | ## Database object (db) 110 | 111 | ### Database Methods 112 | 113 | - `db.query(query, [params], function(err, result))` - classic query, returns Array of Object 114 | - `db.execute(query, [params], function(err, result))` - classic query, returns Array of Array 115 | - `db.sequentially(query, [params], function(row, index), function(err))` - sequentially query 116 | - `db.detach(function(err))` detach a database 117 | - `db.transaction(options, function(err, transaction))` create transaction 118 | 119 | ### Transaction options 120 | 121 | ```js 122 | const options = { 123 | autoCommit: false, 124 | autoUndo: true, 125 | isolation: Firebird.ISOLATION_READ_COMMITTED, 126 | ignoreLimbo: false, 127 | readOnly: false, 128 | wait: true, 129 | waitTimeout: 0, 130 | }; 131 | ``` 132 | 133 | ### Transaction methods 134 | 135 | - `transaction.query(query, [params], function(err, result))` - classic query, returns Array of Object 136 | - `transaction.execute(query, [params], function(err, result))` - classic query, returns Array of Array 137 | - `transaction.commit(function(err))` commit current transaction 138 | - `transaction.rollback(function(err))` rollback current transaction 139 | 140 | ## Examples 141 | 142 | ### Parametrized Queries 143 | 144 | ### Parameters 145 | 146 | ```js 147 | Firebird.attach(options, function (err, db) { 148 | if (err) throw err; 149 | 150 | // db = DATABASE 151 | db.query( 152 | 'INSERT INTO USERS (ID, ALIAS, CREATED) VALUES(?, ?, ?) RETURNING ID', 153 | [1, "Pe'ter", new Date()], 154 | function (err, result) { 155 | console.log(result[0].id); 156 | db.query( 157 | 'SELECT * FROM USERS WHERE Alias=?', 158 | ['Peter'], 159 | function (err, result) { 160 | console.log(result); 161 | db.detach(); 162 | } 163 | ); 164 | } 165 | ); 166 | }); 167 | ``` 168 | 169 | ### BLOB (stream) 170 | 171 | ```js 172 | Firebird.attach(options, function (err, db) { 173 | if (err) throw err; 174 | 175 | // db = DATABASE 176 | // INSERT STREAM as BLOB 177 | db.query( 178 | 'INSERT INTO USERS (ID, ALIAS, FILE) VALUES(?, ?, ?)', 179 | [1, 'Peter', fs.createReadStream('/users/image.jpg')], 180 | function (err, result) { 181 | // IMPORTANT: close the connection 182 | db.detach(); 183 | } 184 | ); 185 | }); 186 | ``` 187 | 188 | ### BLOB (buffer) 189 | 190 | ```js 191 | Firebird.attach(options, function (err, db) { 192 | if (err) throw err; 193 | 194 | // db = DATABASE 195 | // INSERT BUFFER as BLOB 196 | db.query( 197 | 'INSERT INTO USERS (ID, ALIAS, FILE) VALUES(?, ?, ?)', 198 | [1, 'Peter', fs.readFileSync('/users/image.jpg')], 199 | function (err, result) { 200 | // IMPORTANT: close the connection 201 | db.detach(); 202 | } 203 | ); 204 | }); 205 | ``` 206 | 207 | ### Reading Blobs (Asynchronous) 208 | 209 | ```js 210 | Firebird.attach(options, function (err, db) { 211 | if (err) throw err; 212 | 213 | // db = DATABASE 214 | db.query('SELECT ID, ALIAS, USERPICTURE FROM USER', function (err, rows) { 215 | if (err) throw err; 216 | 217 | // first row 218 | rows[0].userpicture(function (err, name, e) { 219 | if (err) throw err; 220 | 221 | // +v0.2.4 222 | // e.pipe(writeStream/Response); 223 | 224 | // e === EventEmitter 225 | e.on('data', function (chunk) { 226 | // reading data 227 | }); 228 | 229 | e.on('end', function () { 230 | // end reading 231 | // IMPORTANT: close the connection 232 | db.detach(); 233 | }); 234 | }); 235 | }); 236 | }); 237 | ``` 238 | 239 | ### Reading Multiples Blobs (Asynchronous) 240 | 241 | ```js 242 | Firebird.attach(options, (err, db) => { 243 | if (err) throw err; 244 | 245 | db.transaction(Firebird.ISOLATION_READ_COMMITTED, (err, transaction) => { 246 | if (err) { 247 | throw err; 248 | } 249 | 250 | transaction.query('SELECT FIRST 10 * FROM JOB', (err, result) => { 251 | if (err) { 252 | transaction.rollback(); 253 | return; 254 | } 255 | 256 | const arrBlob = []; 257 | for (const item of result) { 258 | const fields = Object.keys(item); 259 | for (const key of fields) { 260 | if (typeof item[key] === 'function') { 261 | item[key] = new Promise((resolve, reject) => { 262 | // the same transaction is used (better performance) 263 | // this is optional 264 | item[key](transaction, (error, name, event, row) => { 265 | if (error) { 266 | return reject(error); 267 | } 268 | 269 | // reading data 270 | let value = ''; 271 | event.on('data', (chunk) => { 272 | value += chunk.toString('binary'); 273 | }); 274 | event.on('end', () => { 275 | resolve({ value, column: name, row }); 276 | }); 277 | }); 278 | }); 279 | arrBlob.push(item[key]); 280 | } 281 | } 282 | } 283 | 284 | Promise.all(arrBlob) 285 | .then((blobs) => { 286 | for (const blob of blobs) { 287 | result[blob.row][blob.column] = blob.value; 288 | } 289 | 290 | transaction.commit((err) => { 291 | if (err) { 292 | transaction.rollback(); 293 | return; 294 | } 295 | 296 | db.detach(); 297 | console.log(result); 298 | }); 299 | }) 300 | .catch((err) => { 301 | transaction.rollback(); 302 | }); 303 | }); 304 | }); 305 | }); 306 | ``` 307 | 308 | ### Streaming a big data 309 | 310 | ```js 311 | Firebird.attach(options, function (err, db) { 312 | if (err) throw err; 313 | 314 | // db = DATABASE 315 | db.sequentially( 316 | 'SELECT * FROM BIGTABLE', 317 | function (row, index) { 318 | // EXAMPLE 319 | stream.write(JSON.stringify(row)); 320 | }, 321 | function (err) { 322 | // END 323 | // IMPORTANT: close the connection 324 | db.detach(); 325 | } 326 | ); 327 | }); 328 | ``` 329 | 330 | ### Transactions 331 | 332 | **Transaction types:** 333 | 334 | - `Firebird.ISOLATION_READ_UNCOMMITTED` 335 | - `Firebird.ISOLATION_READ_COMMITTED` 336 | - `Firebird.ISOLATION_REPEATABLE_READ` 337 | - `Firebird.ISOLATION_SERIALIZABLE` 338 | - `Firebird.ISOLATION_READ_COMMITTED_READ_ONLY` 339 | 340 | ```js 341 | Firebird.attach(options, function (err, db) { 342 | if (err) throw err; 343 | 344 | // db = DATABASE 345 | db.transaction( 346 | Firebird.ISOLATION_READ_COMMITTED, 347 | function (err, transaction) { 348 | transaction.query( 349 | 'INSERT INTO users VALUE(?,?)', 350 | [1, 'Janko'], 351 | function (err, result) { 352 | if (err) { 353 | transaction.rollback(); 354 | return; 355 | } 356 | 357 | transaction.commit(function (err) { 358 | if (err) transaction.rollback(); 359 | else db.detach(); 360 | }); 361 | } 362 | ); 363 | } 364 | ); 365 | }); 366 | ``` 367 | 368 | ### Events 369 | 370 | ```js 371 | Firebird.attach(options, function (err, db) { 372 | if (err) throw err; 373 | 374 | db.on('row', function (row, index, isObject) { 375 | // index === Number 376 | // isObject === is row object or array? 377 | }); 378 | 379 | db.on('result', function (result) { 380 | // result === Array 381 | }); 382 | 383 | db.on('attach', function () {}); 384 | 385 | db.on('detach', function (isPoolConnection) { 386 | // isPoolConnection == Boolean 387 | }); 388 | 389 | db.on('reconnect', function () {}); 390 | 391 | db.on('error', function (err) {}); 392 | 393 | db.on('transaction', function (isolation) { 394 | // isolation === Number 395 | }); 396 | 397 | db.on('commit', function () {}); 398 | 399 | db.on('rollback', function () {}); 400 | 401 | db.detach(); 402 | }); 403 | ``` 404 | 405 | ### Escaping Query values 406 | 407 | ```js 408 | var sql1 = 'SELECT * FROM TBL_USER WHERE ID>' + Firebird.escape(1); 409 | var sql2 = 'SELECT * FROM TBL_USER WHERE NAME=' + Firebird.escape("Pe'er"); 410 | var sql3 = 411 | 'SELECT * FROM TBL_USER WHERE CREATED<=' + Firebird.escape(new Date()); 412 | var sql4 = 'SELECT * FROM TBL_USER WHERE NEWSLETTER=' + Firebird.escape(true); 413 | 414 | // or db.escape() 415 | 416 | console.log(sql1); 417 | console.log(sql2); 418 | console.log(sql3); 419 | console.log(sql4); 420 | ``` 421 | 422 | ### Using GDS codes 423 | 424 | ```js 425 | var { GDSCode } = require('node-firebird/lib/gdscodes'); 426 | /*...*/ 427 | db.query( 428 | 'insert into my_table(id, name) values (?, ?)', 429 | [1, 'John Doe'], 430 | function (err) { 431 | if (err.gdscode == GDSCode.UNIQUE_KEY_VIOLATION) { 432 | console.log('constraint name:' + err.gdsparams[0]); 433 | console.log('table name:' + err.gdsparams[0]); 434 | /*...*/ 435 | } 436 | /*...*/ 437 | } 438 | ); 439 | ``` 440 | 441 | ### Service Manager functions 442 | 443 | - backup 444 | - restore 445 | - fixproperties 446 | - serverinfo 447 | - database validation 448 | - commit transaction 449 | - rollback transaction 450 | - recover transaction 451 | - database stats 452 | - users infos 453 | - user actions (add modify remove) 454 | - get firebird file log 455 | - tracing 456 | 457 | ```js 458 | // each row : fctname : [params], typeofreturn 459 | var fbsvc = { 460 | "backup" : { [ "options"], "stream" }, 461 | "nbackup" : { [ "options"], "stream" }, 462 | "restore" : { [ "options"], "stream" }, 463 | "nrestore" : { [ "options"], "stream" }, 464 | "setDialect": { [ "database","dialect"], "stream" }, 465 | "setSweepinterval": { [ "database","sweepinterval"], "stream" }, 466 | "setCachebuffer" : { [ "database","nbpagebuffers"], "stream" }, 467 | "BringOnline" : { [ "database"], "stream" }, 468 | "Shutdown" : { [ "database","shutdown","shutdowndelay","shutdownmode"], "stream" }, 469 | "setShadow" : { [ "database","activateshadow"], "stream" }, 470 | "setForcewrite" : { [ "database","forcewrite"], "stream" }, 471 | "setReservespace" : { [ "database","reservespace"], "stream" }, 472 | "setReadonlyMode" : { [ "database"], "stream" }, 473 | "setReadwriteMode" : { [ "database"], "stream" }, 474 | "validate" : { [ "options"], "stream" }, 475 | "commit" : { [ "database", "transactid"], "stream" }, 476 | "rollback" : { [ "database", "transactid"], "stream" }, 477 | "recover" : { [ "database", "transactid"], "stream" }, 478 | "getStats" : { [ "options"], "stream" }, 479 | "getLog" : { [ "options"], "stream" }, 480 | "getUsers" : { [ "username"], "object" }, 481 | "addUser" : { [ "username", "password", "options"], "stream" }, 482 | "editUser" : { [ "username", "options"], "stream" }, 483 | "removeUser" : { [ "username","rolename"], "stream" }, 484 | "getFbserverInfos" : { [ "options", "options"], "object" }, 485 | "startTrace" : { [ "options"], "stream" }, 486 | "suspendTrace" : { [ "options"], "stream" }, 487 | "resumeTrace" : { [ "options"], "stream" }, 488 | "stopTrace" : { [ "options"], "stream" }, 489 | "getTraceList" : { [ "options"], "stream" }, 490 | "hasActionRunning" : { [ "options"], "object"} 491 | } 492 | 493 | ``` 494 | 495 | ### Backup Service example 496 | 497 | ```js 498 | const options = {...}; // Classic configuration with manager = true 499 | Firebird.attach(options, function(err, svc) { 500 | if (err) 501 | return; 502 | svc.backup( 503 | { 504 | database:'/DB/MYDB.FDB', 505 | files: [ 506 | { 507 | filename:'/DB/MYDB.FBK', 508 | sizefile:'0' 509 | } 510 | ] 511 | }, 512 | function(err, data) { 513 | data.on('data', line => console.log(line)); 514 | data.on('end', () => svc.detach()); 515 | } 516 | ); 517 | }); 518 | ``` 519 | 520 | ### Restore Service example 521 | 522 | ```js 523 | const config = {...}; // Classic configuration with manager = true 524 | const RESTORE_OPTS = { 525 | database: 'database.fdb', 526 | files: ['backup.fbk'] 527 | }; 528 | 529 | Firebird.attach(config, (err, srv) => { 530 | srv.restore(RESTORE_OPTS, (err, data) => { 531 | data.on('data', () => {}); 532 | data.on('end', () =>{ 533 | srv.detach();}) 534 | }); 535 | }); 536 | ``` 537 | 538 | ### getLog and getFbserverInfos Service examples with use of stream and object return 539 | 540 | ```js 541 | fb.attach(_connection, function (err, svc) { 542 | if (err) return; 543 | // all function that return a stream take two optional parameter 544 | // optread => byline or buffer byline use isc_info_svc_line and buffer use isc_info_svc_to_eof 545 | // buffersize => is the buffer for service manager it can't exceed 8ko (i'm not sure) 546 | 547 | svc.getLog({ optread: 'buffer', buffersize: 2048 }, function (err, data) { 548 | // data is a readablestream that contain the firebird.log file 549 | console.log(err); 550 | data.on('data', function (data) { 551 | console.log(data.toString()); 552 | }); 553 | data.on('end', function () { 554 | console.log('finish'); 555 | }); 556 | }); 557 | 558 | // an other exemple to use function that return object 559 | svc.getFbserverInfos( 560 | { 561 | dbinfo: true, 562 | fbconfig: true, 563 | svcversion: true, 564 | fbversion: true, 565 | fbimplementation: true, 566 | fbcapatibilities: true, 567 | pathsecuritydb: true, 568 | fbenv: true, 569 | fbenvlock: true, 570 | fbenvmsg: true, 571 | }, 572 | {}, 573 | function (err, data) { 574 | console.log(err); 575 | console.log(data); 576 | } 577 | ); 578 | }); 579 | ``` 580 | 581 | ### Charset for database connection is always UTF-8 582 | 583 | Node Firebird uses UTF-8 as the default charset. If you want a different one, such as Latin1, you will need to go into the library and modify the default_encoding in the index.js file 584 | 585 | ```js 586 | const default_encoding = 'latin1'; 587 | ``` 588 | 589 | This is why you should use **Firebird 2.5** server at least. 590 | 591 | ### Firebird 3.0 Support 592 | 593 | Firebird new wire protocol is not supported yet so 594 | for Firebird 3.0 you need to add the following in firebird.conf according to Firebird 3 release notes 595 | 596 | 597 | ```bash 598 | AuthServer = Srp, Legacy_Auth 599 | WireCrypt = Enabled 600 | UserManager = Legacy_UserManager 601 | ``` 602 | 603 | Firebird 4 wire protocol is not supported yet so 604 | for Firebird 4.0 you need to add the following in firebird.conf according to Firebird release notes 605 | 606 | 607 | ```bash 608 | AuthServer = Srp256, Srp, Legacy_Auth 609 | WireCrypt = Enabled 610 | UserManager = Legacy_UserManager 611 | ``` 612 | 613 | Please read also Authorization with Firebird 2.5 client library from Firebird 4 migration guide 614 | 615 | 616 | Firebird 5 wire protocol is not supported yet so 617 | for Firebird 5.0 you need to add the following in firebird.conf according to Firebird release notes 618 | 619 | 620 | ```bash 621 | AuthServer = Srp256, Srp, Legacy_Auth 622 | WireCrypt = Enabled 623 | UserManager = Legacy_UserManager 624 | ``` 625 | 626 | Please read also Authorization with Firebird 2.5 client library from Firebird 5 migration guide 627 | 628 | 629 | 630 | ## Contributors 631 | 632 | - Henri Gourvest, 633 | - Popa Marius Adrian, 634 | - Peter Širka, 635 | 636 | [license-image]: http://img.shields.io/badge/license-MOZILLA-blue.svg?style=flat 637 | [license-url]: LICENSE 638 | [npm-url]: https://npmjs.org/package/node-firebird 639 | [npm-version-image]: http://img.shields.io/npm/v/node-firebird.svg?style=flat 640 | [npm-downloads-image]: http://img.shields.io/npm/dm/node-firebird.svg?style=flat 641 | -------------------------------------------------------------------------------- /lib/callback.js: -------------------------------------------------------------------------------- 1 | function doError(obj, callback) { 2 | if (callback) 3 | callback(obj) 4 | } 5 | 6 | function isError(obj) { 7 | return Boolean( 8 | obj != null && typeof obj === "object" && !Array.isArray(obj) && obj.status 9 | ); 10 | } 11 | 12 | function doCallback(obj, callback) { 13 | 14 | if (!callback) 15 | return; 16 | 17 | if (obj instanceof Error) { 18 | callback(obj); 19 | return; 20 | } 21 | 22 | if (isError(obj)) { 23 | var error = new Error(obj.message); 24 | var status = obj.status && obj.status.length && obj.status[0] || {}; 25 | error.gdscode = status.gdscode; // main error gds code 26 | error.gdsparams = status.params; // parameters (constraint name, table, etc.) 27 | callback(error); 28 | return; 29 | } 30 | 31 | callback(undefined, obj); 32 | 33 | } 34 | 35 | module.exports = { 36 | doError, 37 | doCallback 38 | } 39 | -------------------------------------------------------------------------------- /lib/firebird.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hgourvest/node-firebird/e1c4dd963bb7bea0568a083b9f1013fc70b1776f/lib/firebird.msg -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for node-firebird 2 | // Project: node-firebird 3 | // Definitions by: Marco Warm 4 | 5 | declare module 'node-firebird' { 6 | type DatabaseCallback = (err: any, db: Database) => void; 7 | type TransactionCallback = (err: any, transaction: Transaction) => void; 8 | type QueryCallback = (err: any, result: any[]) => void; 9 | type SimpleCallback = (err: any) => void; 10 | type SequentialCallback = (row: any, index: number) => void; 11 | 12 | export const AUTH_PLUGIN_LEGACY: string; 13 | export const AUTH_PLUGIN_SRP: string; 14 | export const AUTH_PLUGIN_SRP256: string; 15 | 16 | export const WIRE_CRYPT_ENABLE: number; 17 | export const WIRE_CRYPT_DISABLE: number; 18 | 19 | /** A transaction sees changes done by uncommitted transactions. */ 20 | export const ISOLATION_READ_UNCOMMITTED: number[]; 21 | /** A transaction sees only data committed before the statement has been executed. */ 22 | export const ISOLATION_READ_COMMITTED: number[]; 23 | /** A transaction sees during its lifetime only data committed before the transaction has been started. */ 24 | export const ISOLATION_REPEATABLE_READ: number[]; 25 | /** 26 | * This is the strictest isolation level, which enforces transaction serialization. 27 | * Data accessed in the context of a serializable transaction cannot be accessed by any other transaction. 28 | */ 29 | export const ISOLATION_SERIALIZABLE: number[]; 30 | export const ISOLATION_READ_COMMITTED_READ_ONLY: number[]; 31 | 32 | export type Isolation = number[]; 33 | 34 | export type TransactionOptions = { 35 | autoCommit?: boolean; 36 | autoUndo?: boolean; 37 | isolation?: Isolation; 38 | ignoreLimbo?: boolean; 39 | readOnly?: boolean; 40 | wait?: boolean; 41 | waitTimeout?: number; 42 | }; 43 | 44 | export interface Database { 45 | detach(callback?: SimpleCallback): Database; 46 | transaction(options: TransactionOptions|Isolation|TransactionCallback, callback?: TransactionCallback): Database; 47 | query(query: string, params: any[], callback: QueryCallback): Database; 48 | execute(query: string, params: any[], callback: QueryCallback): Database; 49 | sequentially(query: string, params: any[], rowCallback: SequentialCallback, callback: SimpleCallback, asArray?: boolean): Database; 50 | drop(callback: SimpleCallback): void; 51 | escape(value: any): string; 52 | attachEvent(callback: any): this; 53 | } 54 | 55 | export interface Transaction { 56 | query(query: string, params: any[], callback: QueryCallback): void; 57 | execute(query: string, params: any[], callback: QueryCallback): void; 58 | sequentially(query: string, params: any[], rowCallback: SequentialCallback, callback: SimpleCallback, asArray?: boolean): Database; 59 | commit(callback?: SimpleCallback): void; 60 | commitRetaining(callback?: SimpleCallback): void; 61 | rollback(callback?: SimpleCallback): void; 62 | rollbackRetaining(callback?: SimpleCallback): void; 63 | } 64 | 65 | export type SupportedCharacterSet = | 66 | 'NONE' | 67 | 'CP943C' | 68 | 'DOS737' | 69 | 'DOS775' | 70 | 'DOS858' | 71 | 'DOS862' | 72 | 'DOS864' | 73 | 'DOS866' | 74 | 'DOS869' | 75 | 'GB18030' | 76 | 'GBK' | 77 | 'ISO8859_1' | 78 | 'ISO8859_2' | 79 | 'ISO8859_3' | 80 | 'ISO8859_4' | 81 | 'ISO8859_5' | 82 | 'ISO8859_6' | 83 | 'ISO8859_7' | 84 | 'ISO8859_8' | 85 | 'ISO8859_9' | 86 | 'ISO8859_13' | 87 | 'KOI8R' | 88 | 'KOI8U' | 89 | 'TIS620' | 90 | 'UTF8' | 91 | 'WIN1251' | 92 | 'WIN1252' | 93 | 'WIN1253' | 94 | 'WIN1254' | 95 | 'WIN1255' | 96 | 'WIN1256' | 97 | 'WIN1257' | 98 | 'WIN1258' | 99 | 'WIN_1258'; 100 | 101 | export interface Options { 102 | host?: string; 103 | port?: number; 104 | database?: string; 105 | user?: string; 106 | password?: string; 107 | lowercase_keys?: boolean; 108 | role?: string; 109 | pageSize?: number; 110 | retryConnectionInterval?: number; 111 | encoding?: SupportedCharacterSet; 112 | blobAsText?: boolean; // only affects for blob subtype 1 113 | } 114 | 115 | export interface SvcMgrOptions extends Options { 116 | manager: true; // Attach to ServiceManager 117 | } 118 | 119 | export interface ConnectionPool { 120 | get(callback: DatabaseCallback): void; 121 | destroy(callback?: SimpleCallback): void; 122 | } 123 | 124 | export function attach(options: Options, callback: DatabaseCallback): void; 125 | export function attach(options: SvcMgrOptions, callback: ServiceManagerCallback): void; 126 | export function escape(value: any, protocolVersion?: number /*PROTOCOL_VERSION13*/): string; 127 | export function create(options: Options, callback: DatabaseCallback): void; 128 | export function attachOrCreate(options: Options, callback: DatabaseCallback): void; 129 | export function pool(max: number, options: Options): ConnectionPool; 130 | export function drop(options: Options, callback: SimpleCallback): void; 131 | 132 | interface ReadableOptions { 133 | optread?: 'byline' | 'buffer'; // default 'byline' 134 | buffersize?: number; // default 'byline': 2048, 'buffer': 8192 135 | timeout?: number; 136 | } 137 | 138 | export interface BackupOptions extends ReadableOptions { 139 | database?: string; 140 | files: string | { filename: string, sizefile: string }[]; 141 | factor?: number; // If backing up to a physical tape device, this switch lets you specify the tape's blocking factor 142 | verbose?: boolean; 143 | ignorechecksums?: boolean; 144 | ignorelimbo?: boolean; 145 | metadataonly?: boolean; 146 | nogarbasecollect?: boolean; 147 | olddescriptions?: boolean; 148 | nontransportable?: boolean; 149 | convert?: boolean; 150 | expand?: boolean; 151 | notriggers?: boolean; 152 | } 153 | 154 | export interface NBackupOptions extends ReadableOptions { 155 | database?: string; 156 | file: string; 157 | level?: number; // nb day for incremental 158 | notriggers?: boolean; 159 | direct?: 'on' | 'off'; // default 'on' 160 | } 161 | 162 | export interface RestoreOptions extends ReadableOptions { 163 | database?: string; 164 | files: string | string[]; 165 | verbose?: boolean; 166 | cachebuffers?: number; // default 2048, gbak -buffers 167 | pagesize?: boolean; // default 4096 168 | readonly?: boolean; // default false 169 | deactivateindexes?: boolean; // default false 170 | noshadow?: boolean; // default false 171 | novalidity?: boolean; // default false 172 | individualcommit?: boolean; // default true 173 | replace?: boolean; // default false 174 | create?: boolean; // default true 175 | useallspace?: boolean; // default false 176 | metadataonly?: boolean; // default false 177 | fixfssdata?: string; // default null 178 | fixfssmetadata?: string; // default null 179 | } 180 | 181 | export interface NRestoreOptions extends ReadableOptions { 182 | database?: string; 183 | files: string | string[]; 184 | } 185 | 186 | export interface ValidateOptions extends ReadableOptions { 187 | database?: string; 188 | checkdb?: boolean; 189 | ignorechecksums?: boolean; 190 | killshadows?: boolean; 191 | mend?: boolean; 192 | validate?: boolean; 193 | full?: boolean; 194 | sweep?: boolean; 195 | listlimbo?: boolean; 196 | icu?: boolean; 197 | } 198 | 199 | export interface StatsOptions extends ReadableOptions { 200 | database?: string; 201 | record?: boolean; 202 | nocreation?: boolean; 203 | tables?: boolean; 204 | pages?: boolean; 205 | header?: boolean; 206 | indexes?: boolean; 207 | tablesystem?: boolean; 208 | encryption?: boolean; 209 | objects?: string; // space-separated list of object index,table,systemtable 210 | } 211 | 212 | interface UserInfo { 213 | userid: number; 214 | groupid: number; 215 | username: string; 216 | firstname: string; 217 | middlename: string; 218 | lastname: string 219 | admin: number; 220 | rolename?: string; 221 | groupname?: string; 222 | } 223 | 224 | export interface ServerInfo { 225 | result: number; 226 | dbinfo?: { database: any[], nbattachment: number, nbdatabase: number }; 227 | fbconfig?: any; 228 | svcversion?: number; 229 | fbversion?: string; 230 | fbimplementation?: string; 231 | fbcapatibilities: string[]; 232 | pathsecuritydb?: string; 233 | fbenv?: string; 234 | fbenvlock?: string; 235 | fbenvmsg?: string; 236 | limbotrans?: number[]; 237 | fbusers?: UserInfo[] 238 | } 239 | 240 | export interface ServerInfoReq { 241 | dbinfo?: boolean; 242 | fbconfig?: boolean; 243 | svcversion?: boolean; 244 | fbversion?: boolean; 245 | fbimplementation?: boolean; 246 | fbcapatibilities?: boolean; 247 | pathsecuritydb?: boolean; 248 | fbenv?: boolean; 249 | fbenvlock?: boolean; 250 | fbenvmsg?: boolean; 251 | limbotrans?: boolean; 252 | } 253 | 254 | export interface TraceOptions extends ReadableOptions { 255 | configfile?: string; // startTrace uses it 256 | tracename?: string; // startTrace uses it 257 | traceid?: number; // suspendTrace, stopTrace, and resumeTrace use it 258 | } 259 | 260 | type ServiceManagerCallback = (err: any, svc: ServiceManager) => void; 261 | // @ts-ignore 262 | type ReadableCallback = (err: any, reader: NodeJS.ReadableStream) => void; 263 | type InfoCallback = (err: any, info: ServerInfo) => void; 264 | type LineCallback = (err: any, data: { result: number, line: string }) => void; 265 | 266 | export enum ShutdownMode { NORMAL = 0, MULTI = 1, SINGLE = 2, FULL = 3 } 267 | export enum ShutdownKind { FORCED = 0, DENY_TRANSACTION = 1, DENY_ATTACHMENT = 2 } 268 | 269 | export interface ServiceManager { 270 | detach(callback?: SimpleCallback, force?: boolean): void; 271 | backup(options: BackupOptions, callback: ReadableCallback): void; 272 | nbackup(options: BackupOptions, callback: ReadableCallback): void; 273 | restore(options: NRestoreOptions, callback: ReadableCallback): void; 274 | nrestore(options: any, callback: Function): void; 275 | setDialect(db: string, dialect: 1 | 3, callback: ReadableCallback): void; 276 | setSweepinterval(db: string, interval: number, callback: Function): void; // gfix -h INTERVAL 277 | setCachebuffer(db: string, nbpages: any, callback: ReadableCallback): void; // gfix -b NBPAGES 278 | BringOnline(db: string, callback: ReadableCallback): void; // gfix -o 279 | Shutdown(db: string, kind: ShutdownKind, delay: number, mode: ShutdownMode, callback: ReadableCallback): void; // server version >= 2.0 280 | Shutdown(db: string, kind: ShutdownKind, delay: number, callback: ReadableCallback): void; // server version < 2.0 281 | setShadow(db: string, val: boolean, callback: ReadableCallback): void; 282 | setForcewrite(db: string, val: boolean, callback: ReadableCallback): void; // gfix -write 283 | setReservespace(db: string, val: boolean, callback: ReadableCallback): void; // true: gfix -use reserve, false: gfix -use full 284 | setReadonlyMode(db: string, callback: ReadableCallback): void; // gfix -mode read_only 285 | setReadwriteMode(db: string, callback: ReadableCallback): void; // gfix -mode read_write 286 | validate(options: ValidateOptions, callback: ReadableCallback): void; // gfix -validate 287 | commit(db: string, transactid: number, callback: ReadableCallback): void; // gfix -commit 288 | rollback(db: string, transactid: number, callback: ReadableCallback): void; 289 | recover(db: string, transactid: number, callback: ReadableCallback): void; 290 | getStats(options: StatsOptions, callback: ReadableCallback): void; 291 | getLog(options: ReadableOptions, callback: ReadableCallback): void; 292 | getUsers(username: string | null, callback: InfoCallback): void; 293 | addUser(username: string, password: string, info: UserInfo, callback: ReadableCallback): void; 294 | editUser(username: string, info: UserInfo, callback: ReadableCallback): void; 295 | removeUser(username: string, rolename: string | null, callback: ReadableCallback): void; 296 | getFbserverInfos(infos: ServerInfoReq, options: { buffersize?: number, timeout?: number }, callback: InfoCallback): void; // if infos is empty all options are asked to the service 297 | startTrace(options: TraceOptions, callback: ReadableCallback): void; 298 | suspendTrace(options: TraceOptions, callback: ReadableCallback): void; 299 | resumeTrace(options: TraceOptions, callback: ReadableCallback): void; 300 | stopTrace(options: TraceOptions, callback: ReadableCallback): void; 301 | getTraceList(options: ReadableOptions, callback: ReadableCallback): void; 302 | readline(options: ReadableOptions, callback: LineCallback): void; 303 | readeof(options: ReadableOptions, callback: LineCallback): void; 304 | hasRunningAction(options: ReadableOptions, callback: ReadableCallback): void; 305 | readusers(options: ReadableOptions, callback: ReadableCallback): void; 306 | readlimbo(options: ReadableOptions, callback: ReadableCallback): void; 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const Const = require('./wire/const'); 2 | const {doError, doCallback} = require('./callback'); 3 | const Connection = require('./wire/connection'); 4 | const Pool = require('./pool'); 5 | const {escape} = require('./utils'); 6 | 7 | if (typeof(setImmediate) === 'undefined') { 8 | global.setImmediate = function(cb) { 9 | process.nextTick(cb); 10 | }; 11 | } 12 | 13 | exports.AUTH_PLUGIN_LEGACY = Const.AUTH_PLUGIN_LEGACY; 14 | exports.AUTH_PLUGIN_SRP = Const.AUTH_PLUGIN_SRP; 15 | // exports.AUTH_PLUGIN_SRP256 = Const.AUTH_PLUGIN_SRP256; 16 | 17 | exports.WIRE_CRYPT_DISABLE = Const.WIRE_CRYPT_DISABLE; 18 | exports.WIRE_CRYPT_ENABLE = Const.WIRE_CRYPT_ENABLE; 19 | 20 | exports.ISOLATION_READ_UNCOMMITTED = Const.ISOLATION_READ_UNCOMMITTED; 21 | exports.ISOLATION_READ_COMMITTED = Const.ISOLATION_READ_COMMITTED; 22 | exports.ISOLATION_REPEATABLE_READ = Const.ISOLATION_REPEATABLE_READ; 23 | exports.ISOLATION_SERIALIZABLE = Const.ISOLATION_SERIALIZABLE; 24 | exports.ISOLATION_READ_COMMITTED_READ_ONLY = Const.ISOLATION_READ_COMMITTED_READ_ONLY; 25 | 26 | if (!String.prototype.padLeft) { 27 | String.prototype.padLeft = function(max, c) { 28 | var self = this; 29 | return new Array(Math.max(0, max - self.length + 1)).join(c || ' ') + self; 30 | }; 31 | } 32 | 33 | exports.escape = escape; 34 | 35 | exports.attach = function(options, callback) { 36 | var host = options.host || Const.DEFAULT_HOST; 37 | var port = options.port || Const.DEFAULT_PORT; 38 | var manager = options.manager || false; 39 | var cnx = this.connection = new Connection(host, port, function(err) { 40 | 41 | if (err) { 42 | doError(err, callback); 43 | return; 44 | } 45 | 46 | cnx.connect(options, function(err) { 47 | if (err) { 48 | doError(err, callback); 49 | } else { 50 | if (manager) 51 | cnx.svcattach(options, callback); 52 | else 53 | cnx.attach(options, callback); 54 | } 55 | }); 56 | 57 | }, options); 58 | }; 59 | 60 | exports.drop = function(options, callback) { 61 | exports.attach(options, function(err, db) { 62 | if (err) { 63 | callback({ error: err, message: "Drop error" }); 64 | return; 65 | } 66 | 67 | db.drop(callback); 68 | }); 69 | }; 70 | 71 | exports.create = function(options, callback) { 72 | var host = options.host || Const.DEFAULT_HOST; 73 | var port = options.port || Const.DEFAULT_PORT; 74 | var cnx = this.connection = new Connection(host, port, function(err) { 75 | 76 | var self = cnx; 77 | 78 | if (err) { 79 | callback({ error: err, message: "Connect error" }); 80 | return; 81 | } 82 | 83 | cnx.connect(options, function(err) { 84 | if (err) { 85 | self.db.emit('error', err); 86 | doError(err, callback); 87 | return; 88 | } 89 | 90 | cnx.createDatabase(options, callback); 91 | }); 92 | }, options); 93 | }; 94 | 95 | exports.attachOrCreate = function(options, callback) { 96 | 97 | var host = options.host || Const.DEFAULT_HOST; 98 | var port = options.port || Const.DEFAULT_PORT; 99 | 100 | var cnx = this.connection = new Connection(host, port, function(err) { 101 | 102 | var self = cnx; 103 | 104 | if (err) { 105 | callback({ error: err, message: "Connect error" }); 106 | return; 107 | } 108 | 109 | cnx.connect(options, function(err) { 110 | 111 | if (err) { 112 | doError(err, callback); 113 | return; 114 | } 115 | 116 | cnx.attach(options, function(err, ret) { 117 | 118 | if (!err) { 119 | if (self.db) 120 | self.db.emit('connect', ret); 121 | doCallback(ret, callback); 122 | return; 123 | } 124 | 125 | cnx.createDatabase(options, callback); 126 | }); 127 | }); 128 | 129 | }, options); 130 | }; 131 | 132 | // Pooling 133 | exports.pool = function(max, options) { 134 | return new Pool(exports.attach, max, Object.assign({}, options, { isPool: true })); 135 | }; 136 | -------------------------------------------------------------------------------- /lib/messages.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | const 4 | //ISC_MASK = 0x14000000, // Defines the code as a valid ISC code 5 | FAC_MASK = 0x00FF0000, // Specifies the facility where the code is located 6 | CODE_MASK = 0x0000FFFF, // Specifies the code in the message file 7 | CLASS_MASK = 0xF0000000; // Defines the code as warning, error, info, or other 8 | 9 | var msgNumber = exports.msgNumber = function(facility, code) { 10 | return (facility * 10000 + code); 11 | }; 12 | 13 | var getCode = exports.getCode = function(code) { 14 | return (code & CODE_MASK) 15 | }; 16 | 17 | var getFacility = exports.getFacility = function(code) { 18 | return (code & FAC_MASK) >> 16; 19 | }; 20 | 21 | exports.getClass = function(code) { 22 | return (code & CLASS_MASK) >> 28; 23 | }; 24 | 25 | exports.lookupMessages = function(status, messageFile, callback){ 26 | 27 | var handle; 28 | var bucket_size; 29 | var top_tree; 30 | var levels; 31 | var buffer; 32 | 33 | function lookup(item, callback) { 34 | 35 | var code = msgNumber(getFacility(item.gdscode), getCode(item.gdscode)); 36 | 37 | function readIndex(stackSize, position) { 38 | 39 | function readNode(from) { 40 | var ret = {}; 41 | ret.code = buffer.readUInt32LE(from); 42 | ret.seek = buffer.readUInt32LE(from + 4); 43 | return ret; 44 | } 45 | 46 | fs.read(handle, buffer, 0, bucket_size, position, function(err, bufferSize) { 47 | 48 | if (bufferSize <= 0) { 49 | callback(); 50 | return; 51 | } 52 | 53 | if (stackSize === levels) { 54 | search(); 55 | return; 56 | } 57 | 58 | var from = 0; 59 | var node = readNode(from); 60 | 61 | while (true) { 62 | 63 | if (node.code >= code) 64 | { 65 | readIndex(stackSize + 1, node.seek); 66 | break; 67 | } 68 | 69 | from += 8; 70 | if (from >= bufferSize) 71 | { 72 | callback(); 73 | break; 74 | } 75 | 76 | node = readNode(from); 77 | } 78 | }); 79 | } 80 | 81 | function search() { 82 | 83 | function readRec(from) { 84 | 85 | function align(v) { 86 | return (v + 3) & ~3; 87 | } 88 | 89 | var ret = {}; 90 | ret.code = buffer.readUInt32LE(from); 91 | ret.length = buffer.readUInt16LE(from + 4); 92 | 93 | if (ret.code == code){ 94 | from += 8; 95 | ret.text = buffer.toString(undefined, from, from + ret.length); 96 | } else 97 | ret.seek = from + align(8 + ret.length, 4); 98 | 99 | return ret; 100 | } 101 | 102 | var rec = readRec(0); 103 | 104 | while (rec.seek) { 105 | if (rec.seek >= buffer.length) 106 | break; 107 | else 108 | rec = readRec(rec.seek); 109 | } 110 | 111 | var str = rec.text; 112 | if (item.params) { 113 | for (var i = 0; i < item.params.length; i++) 114 | str = str.replace('@' + String(i+1), item.params[i]); 115 | } 116 | 117 | callback(str); 118 | } 119 | 120 | readIndex(1, top_tree); 121 | } 122 | 123 | fs.open(messageFile, 'r', function(err, h) { 124 | 125 | if (!h) { 126 | callback(); 127 | return; 128 | } 129 | 130 | buffer = Buffer.alloc(14); 131 | fs.read(h, buffer, 0, 14, 0, function(){ 132 | 133 | handle = h; 134 | bucket_size = buffer.readUInt16LE(2); 135 | top_tree = buffer.readUInt32LE(4); 136 | levels = buffer.readUInt16LE(12); 137 | buffer = Buffer.alloc(bucket_size); 138 | 139 | var i = 0; 140 | var text; 141 | 142 | function loop() { 143 | lookup(status[i], function(line) { 144 | if (text) 145 | text = text + ', ' + line 146 | else 147 | text = line; 148 | 149 | if (i === status.length - 1) { 150 | fs.closeSync(handle); 151 | callback(text); 152 | } else { 153 | i++; 154 | loop(); 155 | } 156 | }); 157 | } 158 | 159 | loop(0); 160 | }); 161 | }); 162 | }; 163 | -------------------------------------------------------------------------------- /lib/pool.js: -------------------------------------------------------------------------------- 1 | /*************************************** 2 | * 3 | * Simple Pooling 4 | * 5 | ***************************************/ 6 | 7 | function Pool(attach, max, options) { 8 | this.attach = attach; 9 | this.internaldb = []; // connection created by the pool (for destroy) 10 | this.pooldb = []; // available connection in the pool 11 | this.dbinuse = 0; // connection currently in use into the pool 12 | this.max = max || 4; 13 | this.pending = []; 14 | this.options = options; 15 | } 16 | 17 | Pool.prototype.get = function(callback) { 18 | var self = this; 19 | self.pending.push(callback); 20 | self.check(); 21 | return self; 22 | }; 23 | 24 | Pool.prototype.check = function() { 25 | 26 | var self = this; 27 | if (self.dbinuse >= self.max) 28 | return self; 29 | 30 | var cb = self.pending.shift(); 31 | if (!cb) 32 | return self; 33 | self.dbinuse++; 34 | if (self.pooldb.length) { 35 | cb(null, self.pooldb.shift()); 36 | } else { 37 | this.attach(self.options, function (err, db) { 38 | if (!err) { 39 | self.internaldb.push(db); 40 | db.on('detach', function () { 41 | // also in pool (could be a twice call to detach) 42 | if (self.pooldb.indexOf(db) !== -1 || self.internaldb.indexOf(db) === -1) 43 | return; 44 | // if not usable don't put in again in the pool and remove reference on it 45 | if (db.connection._isClosed || db.connection._isDetach || db.connection._pooled === false) 46 | self.internaldb.splice(self.internaldb.indexOf(db), 1); 47 | else 48 | self.pooldb.push(db); 49 | 50 | if (db.connection._pooled) 51 | self.dbinuse--; 52 | self.check(); 53 | }); 54 | } else { 55 | // attach fail so not in the pool 56 | self.dbinuse--; 57 | } 58 | 59 | cb(err, db); 60 | }); 61 | } 62 | setImmediate(function() { 63 | self.check(); 64 | }); 65 | 66 | return self; 67 | }; 68 | 69 | Pool.prototype.destroy = function(callback) { 70 | var self = this; 71 | 72 | var connectionCount = this.internaldb.length; 73 | 74 | if (connectionCount === 0 && callback) { 75 | callback(); 76 | } 77 | 78 | function detachCallback(err) { 79 | if (err) { 80 | if (callback) { 81 | callback(err); 82 | } 83 | return; 84 | } 85 | 86 | connectionCount--; 87 | if (connectionCount === 0 && callback) { 88 | callback(); 89 | } 90 | } 91 | 92 | this.internaldb.forEach(function(db) { 93 | if (db.connection._pooled === false) { 94 | detachCallback(); 95 | return; 96 | } 97 | // check if the db is not free into the pool otherwise user should manual detach it 98 | var _db_in_pool = self.pooldb.indexOf(db); 99 | if (_db_in_pool !== -1) { 100 | self.pooldb.splice(_db_in_pool, 1); 101 | db.connection._pooled = false; 102 | db.detach(detachCallback); 103 | } 104 | }); 105 | }; 106 | 107 | module.exports = Pool; 108 | -------------------------------------------------------------------------------- /lib/srp.js: -------------------------------------------------------------------------------- 1 | var BigInt = require('big-integer'), 2 | crypto = require('crypto'); 3 | 4 | const SRP_KEY_SIZE = 128, 5 | SRP_KEY_MAX = BigInt('340282366920938463463374607431768211456'), // 1 << SRP_KEY_SIZE 6 | SRP_SALT_SIZE = 32; 7 | 8 | const DEBUG = false; 9 | const DEBUG_PRIVATE_KEY = BigInt('84316857F47914F838918D5C12CE3A3E7A9B2D7C9486346809E9EEFCE8DE7CD4259D8BE4FD0BCC2D259553769E078FA61EE2977025E4DA42F7FD97914D8A33723DFAFBC00770B7DA0C2E3778A05790F0C0F33C32A19ED88A12928567749021B3FD45DCD1CE259C45325067E3DDC972F87867349BA82C303CCCAA9B207218007B', 16); 10 | 11 | /** 12 | * Prime values. 13 | * 14 | * @type {{g: (bigInt.BigInteger), k: (bigInt.BigInteger), N: (bigInt.BigInteger)}} 15 | */ 16 | const PRIME = { 17 | N: BigInt('E67D2E994B2F900C3F41F08F5BB2627ED0D49EE1FE767A52EFCD565CD6E768812C3E1E9CE8F0A8BEA6CB13CD29DDEBF7A96D4A93B55D488DF099A15C89DCB0640738EB2CBDD9A8F7BAB561AB1B0DC1C6CDABF303264A08D1BCA932D1F1EE428B619D970F342ABA9A65793B8B2F041AE5364350C16F735F56ECBCA87BD57B29E7', 16), 18 | g: BigInt(2), 19 | k: BigInt('1277432915985975349439481660349303019122249719989') 20 | }; 21 | 22 | /** 23 | * Generate a client key pair. 24 | * 25 | * @param a bigInt.BigInteger Client private key. 26 | * @returns {{private: bigInt.BigInteger, public: bigInt.BigInteger}} 27 | */ 28 | exports.clientSeed = function(a = toBigInt(crypto.randomBytes(SRP_KEY_SIZE))) { 29 | var A = PRIME.g.modPow(a, PRIME.N); 30 | 31 | dump('a', a); 32 | dump('A', A); 33 | 34 | return { 35 | public: A, 36 | private: a 37 | }; 38 | } 39 | 40 | /** 41 | * Generate a server key pair. 42 | * 43 | * @param user string Connection username. 44 | * @param password string Connection password. 45 | * @param salt bigInt.BigInteger Connection salt. 46 | * @param b bigInt.BigInteger Server private key. 47 | * @returns {{private: bigInt.BigInteger, public: bigInt.BigInteger}} 48 | */ 49 | exports.serverSeed = function(user, password, salt, b = toBigInt(crypto.randomBytes(SRP_KEY_SIZE))) { 50 | var v = getVerifier(user, password, salt); 51 | var gb = PRIME.g.modPow(b, PRIME.N); 52 | var kv = PRIME.k.multiply(v).mod(PRIME.N); 53 | var B = kv.add(gb).mod(PRIME.N); 54 | 55 | dump('v', v); 56 | dump('b', b); 57 | dump('gb', b); 58 | dump('kv', v); 59 | dump('B', B); 60 | 61 | return { 62 | public: B, 63 | private: b 64 | }; 65 | } 66 | 67 | /** 68 | * Server session secret. 69 | * 70 | * @param user string Connection username. 71 | * @param password string Connection password. 72 | * @param salt bigInt.BigInteger Connection salt. 73 | * @param A bigInt.BigInteger Client public key. 74 | * @param B bigInt.BigInteger Server public key. 75 | * @param b bigInt.BigInteger Server private key. 76 | * @returns {bigInt.BigInteger} 77 | */ 78 | exports.serverSession = function(user, password, salt, A, B, b) { 79 | var u = getScramble(A, B); 80 | var v = getVerifier(user, password, salt); 81 | var vu = v.modPow(u, PRIME.N); 82 | var Avu = A.multiply(vu).mod(PRIME.N); 83 | var sessionSecret = Avu.modPow(b, PRIME.N); 84 | var K = getHash('sha1', toBuffer(sessionSecret)); 85 | 86 | dump('server sessionSecret', sessionSecret); 87 | dump('server K', K); 88 | 89 | return BigInt(K, 16); 90 | }; 91 | 92 | /** 93 | * M = H(H(N) xor H(g), H(I), s, A, B, K) 94 | */ 95 | exports.clientProof = function(user, password, salt, A, B, a, hashAlgo) { 96 | var K = clientSession(user, password, salt, A, B, a); 97 | var n1, n2; 98 | 99 | n1 = toBigInt(getHash('sha1', toBuffer(PRIME.N))); 100 | n2 = toBigInt(getHash('sha1', toBuffer(PRIME.g))); 101 | 102 | dump('n1', n1); 103 | dump('n2', n2); 104 | 105 | n1 = n1.modPow(n2, PRIME.N); 106 | n2 = toBigInt(getHash('sha1', user)); 107 | var M = toBigInt(getHash(hashAlgo, toBuffer(n1), toBuffer(n2), salt, toBuffer(A), toBuffer(B), toBuffer(K))); 108 | 109 | dump('n1-2', n1); 110 | dump('n2-2', n2); 111 | dump('proof:M', M); 112 | 113 | return { 114 | clientSessionKey: K, 115 | authData: M, 116 | }; 117 | } 118 | 119 | /** 120 | * Pad hex string. 121 | */ 122 | function hexPad(hex) { 123 | if (hex.length % 2 !== 0) { 124 | hex = '0' + hex; 125 | } 126 | 127 | return hex; 128 | } 129 | exports.hexPad = hexPad; 130 | 131 | /** 132 | * Pad key with SRP_KEY_SIZE. 133 | * 134 | * @param n BigInt Key to pad. 135 | * @returns Buffer 136 | */ 137 | function pad(n) { 138 | var buff = Buffer.from(hexPad(n.toString(16)), 'hex'); 139 | 140 | if (buff.length > SRP_KEY_SIZE) { 141 | buff = buff.slice(buff.length - SRP_KEY_SIZE, buff.length); 142 | } 143 | 144 | return buff; 145 | } 146 | 147 | /** 148 | * Scramble keys. 149 | * 150 | * @param A bigInt.BigInteger Client public key. 151 | * @param B bigInt.BigInteger Server public key. 152 | * @returns {bigInt.BigInteger} 153 | */ 154 | function getScramble(A, B) { 155 | return BigInt(getHash('sha1', pad(A), pad(B)), 16); 156 | } 157 | 158 | /** 159 | * Client session secret. 160 | * 161 | * Both: u = H(A, B) 162 | * User: x = H(s, p) (user enters password) 163 | * User: S = (B - kg^x) ^ (a + ux) (computes session key) 164 | * User: K = H(S) 165 | * 166 | * @param user string Connection username. 167 | * @param password string Connection password. 168 | * @param salt bigInt.BigInteger Connection salt. 169 | * @param A bigInt.BigInteger Client public key. 170 | * @param B bigInt.BigInteger Server public key. 171 | * @param a bigInt.BigInteger Client private key. 172 | */ 173 | function clientSession(user, password, salt, A, B, a) { 174 | var u = getScramble(A, B); 175 | var x = getUserHash(user, salt, password); 176 | var gx = PRIME.g.modPow(x, PRIME.N); 177 | var kgx = PRIME.k.multiply(gx).mod(PRIME.N); 178 | var diff = B.subtract(kgx).mod(PRIME.N); 179 | 180 | if (diff.lesser(0)) { 181 | diff = diff.add(PRIME.N); 182 | } 183 | 184 | var ux = u.multiply(x).mod(PRIME.N); 185 | var aux = a.add(ux).mod(PRIME.N); 186 | var sessionSecret = diff.modPow(aux, PRIME.N); 187 | var K = toBigInt(getHash('sha1', toBuffer(sessionSecret))); 188 | 189 | dump('B', B); 190 | dump('u', u); 191 | dump('x', x); 192 | dump('gx', gx); 193 | dump('kgx', kgx); 194 | dump('diff', diff); 195 | dump('ux', ux); 196 | dump('aux', aux); 197 | dump('sessionSecret', sessionSecret); 198 | dump('sessionKey(K)', K); 199 | 200 | return K; 201 | } 202 | 203 | /** 204 | * Compute user hash. 205 | * 206 | * @param user string Connection username. 207 | * @param salt bigInt.BigInteger Connection salt. 208 | * @param password string Connection password. 209 | * @returns {bigInt.BigInteger} 210 | */ 211 | function getUserHash(user, salt, password) { 212 | var hash1 = getHash('sha1', user.toUpperCase(), ':', password); 213 | var hash2 = getHash('sha1', salt, toBuffer(hash1)); 214 | 215 | return toBigInt(hash2); 216 | } 217 | 218 | /** 219 | * Verifier of user hash. 220 | * 221 | * @param user string Connection username. 222 | * @param password string Connection password. 223 | * @param salt bigInt.BigInteger Connection salt. 224 | * @returns {bigInt.BigInteger} 225 | */ 226 | function getVerifier(user, password, salt) { 227 | return PRIME.g.modPow(getUserHash(user, salt, password), PRIME.N); 228 | } 229 | 230 | /** 231 | * Hash data and return hex string. 232 | * 233 | * @param algo string Algorithm to use. 234 | * @param data any[] Data to hash. 235 | * @returns {string} 236 | */ 237 | function getHash(algo, ...data) { 238 | var hash = crypto.createHash(algo); 239 | 240 | for (var d of data) { 241 | hash.update(d); 242 | } 243 | 244 | return hash.digest('hex'); 245 | } 246 | 247 | /** 248 | * Convert BigInt to buffer. 249 | * 250 | * @param bigInt 251 | * @returns {*} 252 | */ 253 | function toBuffer(bigInt) { 254 | return Buffer.from(BigInt.isInstance(bigInt) ? hexPad(bigInt.toString(16)) : bigInt, 'hex'); 255 | } 256 | 257 | /** 258 | * Convert hex buffer or string to BigInt. 259 | * 260 | * @param hex 261 | * @returns {bigInt.BigInteger} 262 | */ 263 | function toBigInt(hex) { 264 | return BigInt(Buffer.isBuffer(hex) ? hex.toString('hex') : hex, 16); 265 | } 266 | 267 | /** 268 | * Dump value in debug mode. 269 | * 270 | * @param key 271 | * @param value 272 | */ 273 | function dump(key, value) { 274 | if (DEBUG) { 275 | if (BigInt.isInstance(value)) { 276 | value = value.toString(16); 277 | } 278 | 279 | console.log(key + '=' + value); 280 | } 281 | } -------------------------------------------------------------------------------- /lib/unix-crypt.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one or more 3 | contributor license agreements. See the NOTICE file distributed with 4 | this work for additional information regarding copyright ownership. 5 | The ASF licenses this file to You under the Apache License, Version 2.0 6 | (the "License"); you may not use this file except in compliance with 7 | the License. You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | This code was originally made available in the Apache Codec API. 18 | 19 | http://commons.apache.org/proper/commons-codec/ 20 | 21 | */ 22 | 23 | var CON_SALT = [ 24 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 5, 6, 26 | 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 27 | 34, 35, 36, 37, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 28 | 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 0, 0, 0, 0, 0 29 | ]; 30 | 31 | var COV2CHAR = [ 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 32 | 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 33 | 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122 34 | ]; 35 | 36 | var SHIFT2 = [ false, false, true, true, true, true, true, true, false, true, true, true, true, true, true, false ]; 37 | 38 | var SKB = [ 39 | [ 0, 16, 0x20000000, 0x20000010, 0x10000, 0x10010, 0x20010000, 0x20010010, 2048, 2064, 0x20000800, 40 | 0x20000810, 0x10800, 0x10810, 0x20010800, 0x20010810, 32, 48, 0x20000020, 0x20000030, 0x10020, 41 | 0x10030, 0x20010020, 0x20010030, 2080, 2096, 0x20000820, 0x20000830, 0x10820, 0x10830, 0x20010820, 42 | 0x20010830, 0x80000, 0x80010, 0x20080000, 0x20080010, 0x90000, 0x90010, 0x20090000, 0x20090010, 43 | 0x80800, 0x80810, 0x20080800, 0x20080810, 0x90800, 0x90810, 0x20090800, 0x20090810, 0x80020, 44 | 0x80030, 0x20080020, 0x20080030, 0x90020, 0x90030, 0x20090020, 0x20090030, 0x80820, 0x80830, 45 | 0x20080820, 0x20080830, 0x90820, 0x90830, 0x20090820, 0x20090830 ], 46 | [ 0, 0x2000000, 8192, 0x2002000, 0x200000, 0x2200000, 0x202000, 0x2202000, 4, 0x2000004, 8196, 0x2002004, 47 | 0x200004, 0x2200004, 0x202004, 0x2202004, 1024, 0x2000400, 9216, 0x2002400, 0x200400, 0x2200400, 48 | 0x202400, 0x2202400, 1028, 0x2000404, 9220, 0x2002404, 0x200404, 0x2200404, 0x202404, 0x2202404, 49 | 0x10000000, 0x12000000, 0x10002000, 0x12002000, 0x10200000, 0x12200000, 0x10202000, 0x12202000, 50 | 0x10000004, 0x12000004, 0x10002004, 0x12002004, 0x10200004, 0x12200004, 0x10202004, 0x12202004, 51 | 0x10000400, 0x12000400, 0x10002400, 0x12002400, 0x10200400, 0x12200400, 0x10202400, 0x12202400, 52 | 0x10000404, 0x12000404, 0x10002404, 0x12002404, 0x10200404, 0x12200404, 0x10202404, 0x12202404 ], 53 | [ 0, 1, 0x40000, 0x40001, 0x1000000, 0x1000001, 0x1040000, 0x1040001, 2, 3, 0x40002, 0x40003, 0x1000002, 54 | 0x1000003, 0x1040002, 0x1040003, 512, 513, 0x40200, 0x40201, 0x1000200, 0x1000201, 0x1040200, 55 | 0x1040201, 514, 515, 0x40202, 0x40203, 0x1000202, 0x1000203, 0x1040202, 0x1040203, 0x8000000, 56 | 0x8000001, 0x8040000, 0x8040001, 0x9000000, 0x9000001, 0x9040000, 0x9040001, 0x8000002, 0x8000003, 57 | 0x8040002, 0x8040003, 0x9000002, 0x9000003, 0x9040002, 0x9040003, 0x8000200, 0x8000201, 0x8040200, 58 | 0x8040201, 0x9000200, 0x9000201, 0x9040200, 0x9040201, 0x8000202, 0x8000203, 0x8040202, 0x8040203, 59 | 0x9000202, 0x9000203, 0x9040202, 0x9040203 ], 60 | [ 0, 0x100000, 256, 0x100100, 8, 0x100008, 264, 0x100108, 4096, 0x101000, 4352, 0x101100, 4104, 0x101008, 61 | 4360, 0x101108, 0x4000000, 0x4100000, 0x4000100, 0x4100100, 0x4000008, 0x4100008, 0x4000108, 62 | 0x4100108, 0x4001000, 0x4101000, 0x4001100, 0x4101100, 0x4001008, 0x4101008, 0x4001108, 0x4101108, 63 | 0x20000, 0x120000, 0x20100, 0x120100, 0x20008, 0x120008, 0x20108, 0x120108, 0x21000, 0x121000, 64 | 0x21100, 0x121100, 0x21008, 0x121008, 0x21108, 0x121108, 0x4020000, 0x4120000, 0x4020100, 65 | 0x4120100, 0x4020008, 0x4120008, 0x4020108, 0x4120108, 0x4021000, 0x4121000, 0x4021100, 0x4121100, 66 | 0x4021008, 0x4121008, 0x4021108, 0x4121108 ], 67 | [ 0, 0x10000000, 0x10000, 0x10010000, 4, 0x10000004, 0x10004, 0x10010004, 0x20000000, 0x30000000, 68 | 0x20010000, 0x30010000, 0x20000004, 0x30000004, 0x20010004, 0x30010004, 0x100000, 0x10100000, 69 | 0x110000, 0x10110000, 0x100004, 0x10100004, 0x110004, 0x10110004, 0x20100000, 0x30100000, 70 | 0x20110000, 0x30110000, 0x20100004, 0x30100004, 0x20110004, 0x30110004, 4096, 0x10001000, 0x11000, 71 | 0x10011000, 4100, 0x10001004, 0x11004, 0x10011004, 0x20001000, 0x30001000, 0x20011000, 0x30011000, 72 | 0x20001004, 0x30001004, 0x20011004, 0x30011004, 0x101000, 0x10101000, 0x111000, 0x10111000, 73 | 0x101004, 0x10101004, 0x111004, 0x10111004, 0x20101000, 0x30101000, 0x20111000, 0x30111000, 74 | 0x20101004, 0x30101004, 0x20111004, 0x30111004 ], 75 | [ 0, 0x8000000, 8, 0x8000008, 1024, 0x8000400, 1032, 0x8000408, 0x20000, 0x8020000, 0x20008, 0x8020008, 76 | 0x20400, 0x8020400, 0x20408, 0x8020408, 1, 0x8000001, 9, 0x8000009, 1025, 0x8000401, 1033, 77 | 0x8000409, 0x20001, 0x8020001, 0x20009, 0x8020009, 0x20401, 0x8020401, 0x20409, 0x8020409, 78 | 0x2000000, 0xa000000, 0x2000008, 0xa000008, 0x2000400, 0xa000400, 0x2000408, 0xa000408, 0x2020000, 79 | 0xa020000, 0x2020008, 0xa020008, 0x2020400, 0xa020400, 0x2020408, 0xa020408, 0x2000001, 0xa000001, 80 | 0x2000009, 0xa000009, 0x2000401, 0xa000401, 0x2000409, 0xa000409, 0x2020001, 0xa020001, 0x2020009, 81 | 0xa020009, 0x2020401, 0xa020401, 0x2020409, 0xa020409 ], 82 | [ 0, 256, 0x80000, 0x80100, 0x1000000, 0x1000100, 0x1080000, 0x1080100, 16, 272, 0x80010, 0x80110, 83 | 0x1000010, 0x1000110, 0x1080010, 0x1080110, 0x200000, 0x200100, 0x280000, 0x280100, 0x1200000, 84 | 0x1200100, 0x1280000, 0x1280100, 0x200010, 0x200110, 0x280010, 0x280110, 0x1200010, 0x1200110, 85 | 0x1280010, 0x1280110, 512, 768, 0x80200, 0x80300, 0x1000200, 0x1000300, 0x1080200, 0x1080300, 528, 86 | 784, 0x80210, 0x80310, 0x1000210, 0x1000310, 0x1080210, 0x1080310, 0x200200, 0x200300, 0x280200, 87 | 0x280300, 0x1200200, 0x1200300, 0x1280200, 0x1280300, 0x200210, 0x200310, 0x280210, 0x280310, 88 | 0x1200210, 0x1200310, 0x1280210, 0x1280310 ], 89 | [ 0, 0x4000000, 0x40000, 0x4040000, 2, 0x4000002, 0x40002, 0x4040002, 8192, 0x4002000, 0x42000, 0x4042000, 90 | 8194, 0x4002002, 0x42002, 0x4042002, 32, 0x4000020, 0x40020, 0x4040020, 34, 0x4000022, 0x40022, 91 | 0x4040022, 8224, 0x4002020, 0x42020, 0x4042020, 8226, 0x4002022, 0x42022, 0x4042022, 2048, 92 | 0x4000800, 0x40800, 0x4040800, 2050, 0x4000802, 0x40802, 0x4040802, 10240, 0x4002800, 0x42800, 93 | 0x4042800, 10242, 0x4002802, 0x42802, 0x4042802, 2080, 0x4000820, 0x40820, 0x4040820, 2082, 94 | 0x4000822, 0x40822, 0x4040822, 10272, 0x4002820, 0x42820, 0x4042820, 10274, 0x4002822, 0x42822, 95 | 0x4042822 ] 96 | ]; 97 | 98 | var SPTRANS= 99 | [ 100 | [ 0x820200, 0x20000, 0x80800000, 0x80820200, 0x800000, 0x80020200, 0x80020000, 0x80800000, 0x80020200, 101 | 0x820200, 0x820000, 0x80000200, 0x80800200, 0x800000, 0, 0x80020000, 0x20000, 0x80000000, 102 | 0x800200, 0x20200, 0x80820200, 0x820000, 0x80000200, 0x800200, 0x80000000, 512, 0x20200, 103 | 0x80820000, 512, 0x80800200, 0x80820000, 0, 0, 0x80820200, 0x800200, 0x80020000, 0x820200, 104 | 0x20000, 0x80000200, 0x800200, 0x80820000, 512, 0x20200, 0x80800000, 0x80020200, 0x80000000, 105 | 0x80800000, 0x820000, 0x80820200, 0x20200, 0x820000, 0x80800200, 0x800000, 0x80000200, 0x80020000, 106 | 0, 0x20000, 0x800000, 0x80800200, 0x820200, 0x80000000, 0x80820000, 512, 0x80020200 ], 107 | [ 0x10042004, 0, 0x42000, 0x10040000, 0x10000004, 8196, 0x10002000, 0x42000, 8192, 0x10040004, 4, 108 | 0x10002000, 0x40004, 0x10042000, 0x10040000, 4, 0x40000, 0x10002004, 0x10040004, 8192, 0x42004, 109 | 0x10000000, 0, 0x40004, 0x10002004, 0x42004, 0x10042000, 0x10000004, 0x10000000, 0x40000, 8196, 110 | 0x10042004, 0x40004, 0x10042000, 0x10002000, 0x42004, 0x10042004, 0x40004, 0x10000004, 0, 111 | 0x10000000, 8196, 0x40000, 0x10040004, 8192, 0x10000000, 0x42004, 0x10002004, 0x10042000, 8192, 0, 112 | 0x10000004, 4, 0x10042004, 0x42000, 0x10040000, 0x10040004, 0x40000, 8196, 0x10002000, 0x10002004, 113 | 4, 0x10040000, 0x42000 ], 114 | [ 0x41000000, 0x1010040, 64, 0x41000040, 0x40010000, 0x1000000, 0x41000040, 0x10040, 0x1000040, 0x10000, 115 | 0x1010000, 0x40000000, 0x41010040, 0x40000040, 0x40000000, 0x41010000, 0, 0x40010000, 0x1010040, 116 | 64, 0x40000040, 0x41010040, 0x10000, 0x41000000, 0x41010000, 0x1000040, 0x40010040, 0x1010000, 117 | 0x10040, 0, 0x1000000, 0x40010040, 0x1010040, 64, 0x40000000, 0x10000, 0x40000040, 0x40010000, 118 | 0x1010000, 0x41000040, 0, 0x1010040, 0x10040, 0x41010000, 0x40010000, 0x1000000, 0x41010040, 119 | 0x40000000, 0x40010040, 0x41000000, 0x1000000, 0x41010040, 0x10000, 0x1000040, 0x41000040, 120 | 0x10040, 0x1000040, 0, 0x41010000, 0x40000040, 0x41000000, 0x40010040, 64, 0x1010000 ], 121 | [ 0x100402, 0x4000400, 2, 0x4100402, 0, 0x4100000, 0x4000402, 0x100002, 0x4100400, 0x4000002, 0x4000000, 122 | 1026, 0x4000002, 0x100402, 0x100000, 0x4000000, 0x4100002, 0x100400, 1024, 2, 0x100400, 0x4000402, 123 | 0x4100000, 1024, 1026, 0, 0x100002, 0x4100400, 0x4000400, 0x4100002, 0x4100402, 0x100000, 124 | 0x4100002, 1026, 0x100000, 0x4000002, 0x100400, 0x4000400, 2, 0x4100000, 0x4000402, 0, 1024, 125 | 0x100002, 0, 0x4100002, 0x4100400, 1024, 0x4000000, 0x4100402, 0x100402, 0x100000, 0x4100402, 2, 126 | 0x4000400, 0x100402, 0x100002, 0x100400, 0x4100000, 0x4000402, 1026, 0x4000000, 0x4000002, 127 | 0x4100400 ], 128 | [ 0x2000000, 16384, 256, 0x2004108, 0x2004008, 0x2000100, 16648, 0x2004000, 16384, 8, 0x2000008, 16640, 129 | 0x2000108, 0x2004008, 0x2004100, 0, 16640, 0x2000000, 16392, 264, 0x2000100, 16648, 0, 0x2000008, 130 | 8, 0x2000108, 0x2004108, 16392, 0x2004000, 256, 264, 0x2004100, 0x2004100, 0x2000108, 16392, 131 | 0x2004000, 16384, 8, 0x2000008, 0x2000100, 0x2000000, 16640, 0x2004108, 0, 16648, 0x2000000, 256, 132 | 16392, 0x2000108, 256, 0, 0x2004108, 0x2004008, 0x2004100, 264, 16384, 16640, 0x2004008, 133 | 0x2000100, 264, 8, 16648, 0x2004000, 0x2000008 ], 134 | [ 0x20000010, 0x80010, 0, 0x20080800, 0x80010, 2048, 0x20000810, 0x80000, 2064, 0x20080810, 0x80800, 135 | 0x20000000, 0x20000800, 0x20000010, 0x20080000, 0x80810, 0x80000, 0x20000810, 0x20080010, 0, 2048, 136 | 16, 0x20080800, 0x20080010, 0x20080810, 0x20080000, 0x20000000, 2064, 16, 0x80800, 0x80810, 137 | 0x20000800, 2064, 0x20000000, 0x20000800, 0x80810, 0x20080800, 0x80010, 0, 0x20000800, 0x20000000, 138 | 2048, 0x20080010, 0x80000, 0x80010, 0x20080810, 0x80800, 16, 0x20080810, 0x80800, 0x80000, 139 | 0x20000810, 0x20000010, 0x20080000, 0x80810, 0, 2048, 0x20000010, 0x20000810, 0x20080800, 140 | 0x20080000, 2064, 16, 0x20080010 ], 141 | [ 4096, 128, 0x400080, 0x400001, 0x401081, 4097, 4224, 0, 0x400000, 0x400081, 129, 0x401000, 1, 0x401080, 142 | 0x401000, 129, 0x400081, 4096, 4097, 0x401081, 0, 0x400080, 0x400001, 4224, 0x401001, 4225, 143 | 0x401080, 1, 4225, 0x401001, 128, 0x400000, 4225, 0x401000, 0x401001, 129, 4096, 128, 0x400000, 144 | 0x401001, 0x400081, 4225, 4224, 0, 128, 0x400001, 1, 0x400080, 0, 0x400081, 0x400080, 4224, 129, 145 | 4096, 0x401081, 0x400000, 0x401080, 1, 4097, 0x401081, 0x400001, 0x401080, 0x401000, 4097 ], 146 | [ 0x8200020, 0x8208000, 32800, 0, 0x8008000, 0x200020, 0x8200000, 0x8208020, 32, 0x8000000, 0x208000, 147 | 32800, 0x208020, 0x8008020, 0x8000020, 0x8200000, 32768, 0x208020, 0x200020, 0x8008000, 0x8208020, 148 | 0x8000020, 0, 0x208000, 0x8000000, 0x200000, 0x8008020, 0x8200020, 0x200000, 32768, 0x8208000, 32, 149 | 0x200000, 32768, 0x8000020, 0x8208020, 32800, 0x8000000, 0, 0x208000, 0x8200020, 0x8008020, 150 | 0x8008000, 0x200020, 0x8208000, 32, 0x200020, 0x8008000, 0x8208020, 0x200000, 0x8200000, 151 | 0x8000020, 0x208000, 32800, 0x8008020, 0x8200000, 32, 0x8208000, 0x208020, 0, 0x8000000, 152 | 0x8200020, 32768, 0x208020 ] 153 | ]; 154 | 155 | function hPermOp(a, n, m) { 156 | var t = (a << 16 - n ^ a) & m; 157 | a = a ^ t ^ t >>> 16 - n; 158 | return a; 159 | } 160 | 161 | function intToFourBytes(iValue, b, offset) { 162 | b[offset++] = iValue & 0xff; 163 | b[offset++] = iValue >>> 8 & 0xff; 164 | b[offset++] = iValue >>> 16 & 0xff; 165 | b[offset++] = iValue >>> 24 & 0xff; 166 | } 167 | 168 | function byteToUnsigned(b) { 169 | var value = b; 170 | return value < 0 ? value + 256 : value; 171 | } 172 | 173 | function fourBytesToInt(b, offset) { 174 | var value = byteToUnsigned(b[offset++]); 175 | value |= byteToUnsigned(b[offset++]) << 8; 176 | value |= byteToUnsigned(b[offset++]) << 16; 177 | value |= byteToUnsigned(b[offset++]) << 24; 178 | return value; 179 | } 180 | 181 | function permOp(a, b, n, m, results) { 182 | var t = (a >>> n ^ b) & m; 183 | a ^= t << n; 184 | b ^= t; 185 | results[0] = a; 186 | results[1] = b; 187 | } 188 | 189 | function desSetKey(key) { 190 | var schedule = []; 191 | var c = fourBytesToInt(key, 0); 192 | var d = fourBytesToInt(key, 4); 193 | var results = [0, 0]; 194 | permOp(d, c, 4, 0xf0f0f0f, results); 195 | d = results[0]; 196 | c = results[1]; 197 | c = hPermOp(c, -2, 0xcccc0000); 198 | d = hPermOp(d, -2, 0xcccc0000); 199 | permOp(d, c, 1, 0x55555555, results); 200 | d = results[0]; 201 | c = results[1]; 202 | permOp(c, d, 8, 0xff00ff, results); 203 | c = results[0]; 204 | d = results[1]; 205 | permOp(d, c, 1, 0x55555555, results); 206 | d = results[0]; 207 | c = results[1]; 208 | d = (d & 0xff) << 16 | d & 0xff00 | (d & 0xff0000) >>> 16 | (c & 0xf0000000) >>> 4; 209 | c &= 0xfffffff; 210 | var j = 0; 211 | for (var i = 0; i < 16; i++) { 212 | if (SHIFT2[i]) { 213 | c = c >>> 2 | c << 26; 214 | d = d >>> 2 | d << 26; 215 | } else { 216 | c = c >>> 1 | c << 27; 217 | d = d >>> 1 | d << 27; 218 | } 219 | c &= 0xfffffff; 220 | d &= 0xfffffff; 221 | var s = SKB[0][c & 0x3f] | SKB[1][c >>> 6 & 0x3 | c >>> 7 & 0x3c] | 222 | SKB[2][c >>> 13 & 0xf | c >>> 14 & 0x30] | 223 | SKB[3][c >>> 20 & 0x1 | c >>> 21 & 0x6 | c >>> 22 & 0x38]; 224 | var t = SKB[4][d & 0x3f] | SKB[5][d >>> 7 & 0x3 | d >>> 8 & 0x3c] | SKB[6][d >>> 15 & 0x3f] | 225 | SKB[7][d >>> 21 & 0xf | d >>> 22 & 0x30]; 226 | schedule[j++] = (t << 16 | s & 0xffff); 227 | s = s >>> 16 | t & 0xffff0000; 228 | s = s << 4 | s >>> 28; 229 | schedule[j++] = s; 230 | } 231 | 232 | return schedule; 233 | } 234 | 235 | function dEncrypt(el, r, s, e0, e1, sArr) { 236 | var v = r ^ r >>> 16; 237 | var u = v & e0; 238 | v &= e1; 239 | u = u ^ u << 16 ^ r ^ sArr[s]; 240 | var t = v ^ v << 16 ^ r ^ sArr[s + 1]; 241 | t = t >>> 4 | t << 28; 242 | el ^= SPTRANS[1][t & 0x3f] | SPTRANS[3][t >>> 8 & 0x3f] | SPTRANS[5][t >>> 16 & 0x3f] | 243 | SPTRANS[7][t >>> 24 & 0x3f] | SPTRANS[0][u & 0x3f] | SPTRANS[2][u >>> 8 & 0x3f] | 244 | SPTRANS[4][u >>> 16 & 0x3f] | SPTRANS[6][u >>> 24 & 0x3f]; 245 | return el; 246 | } 247 | 248 | function body(schedule, eSwap0, eSwap1) { 249 | var left = 0; 250 | var right = 0; 251 | var t = 0; 252 | for (var j = 0; j < 25; j++) { 253 | for (var i = 0; i < 32; i += 4) { 254 | left = dEncrypt(left, right, i, eSwap0, eSwap1, schedule); 255 | right = dEncrypt(right, left, i + 2, eSwap0, eSwap1, schedule); 256 | } 257 | 258 | t = left; 259 | left = right; 260 | right = t; 261 | } 262 | 263 | t = right; 264 | right = left >>> 1 | left << 31; 265 | left = t >>> 1 | t << 31; 266 | var results = [0, 0]; 267 | permOp(right, left, 1, 0x55555555, results); 268 | right = results[0]; 269 | left = results[1]; 270 | permOp(left, right, 8, 0xff00ff, results); 271 | left = results[0]; 272 | right = results[1]; 273 | permOp(right, left, 2, 0x33333333, results); 274 | right = results[0]; 275 | left = results[1]; 276 | permOp(left, right, 16, 65535, results); 277 | left = results[0]; 278 | right = results[1]; 279 | permOp(right, left, 4, 0xf0f0f0f, results); 280 | right = results[0]; 281 | left = results[1]; 282 | var out = [0, 0]; 283 | out[0] = left; 284 | out[1] = right; 285 | return out; 286 | } 287 | 288 | function crypt(original, salt) { 289 | if (!(original instanceof Buffer)) { 290 | original = Buffer.from(original); 291 | } 292 | 293 | if (!salt) { 294 | throw new Error("Invalid salt value: " + salt); 295 | } 296 | 297 | var buffer = Buffer.alloc(13); 298 | var charZero = salt[0].charCodeAt(); 299 | var charOne = salt[1].charCodeAt(); 300 | buffer[0] = charZero; 301 | buffer[1] = charOne; 302 | var eSwap0 = CON_SALT[charZero]; 303 | var eSwap1 = CON_SALT[charOne] << 4; 304 | 305 | var key = Buffer.alloc(8); 306 | for (var i = 0; i < key.length && i < original.length; i++) { 307 | var iChar = original[i]; 308 | key[i] = iChar << 1; 309 | } 310 | 311 | var schedule = desSetKey(key); 312 | var out = body(schedule, eSwap0, eSwap1); 313 | var b = Buffer.alloc(9); 314 | intToFourBytes(out[0], b, 0); 315 | intToFourBytes(out[1], b, 4); 316 | 317 | b[8] = 0; 318 | var i = 2; 319 | var y = 0; 320 | var u = 128; 321 | for (; i < 13; i++) { 322 | var j = 0; 323 | var c = 0; 324 | for (; j < 6; j++) { 325 | c <<= 1; 326 | if ((b[y] & u) != 0) { 327 | c |= 0x1; 328 | } 329 | u >>>= 1; 330 | if (u == 0) { 331 | y++; 332 | u = 128; 333 | } 334 | buffer[i] = COV2CHAR[c]; 335 | } 336 | } 337 | 338 | return buffer.toString('ascii'); 339 | } 340 | 341 | module.exports = { 342 | crypt : crypt 343 | } 344 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | const MessagesError = require('./firebird.msg.json'); 2 | const Const = require('./wire/const'); 3 | 4 | /** 5 | * Parse date from string 6 | * @param {String} str 7 | * @return {Date} 8 | */ 9 | const parseDate = (str) => { 10 | const self = str.trim(); 11 | const arr = self.indexOf(' ') === -1 ? self.split('T') : self.split(' '); 12 | let index = arr[0].indexOf(':'); 13 | const length = arr[0].length; 14 | 15 | if (index !== -1) { 16 | const tmp = arr[1]; 17 | arr[1] = arr[0]; 18 | arr[0] = tmp; 19 | } 20 | 21 | if (arr[0] === undefined) { 22 | arr[0] = ''; 23 | } 24 | 25 | const noTime = arr[1] === undefined || arr[1].length === 0; 26 | 27 | for (let i = 0; i < length; i++) { 28 | const c = arr[0].charCodeAt(i); 29 | if (c > 47 && c < 58) { 30 | continue; 31 | } 32 | if (c === 45 || c === 46) { 33 | continue; 34 | } 35 | if (noTime) { 36 | return new Date(self); 37 | } 38 | } 39 | 40 | if (arr[1] === undefined) { 41 | arr[1] = '00:00:00'; 42 | } 43 | 44 | const firstDay = arr[0].indexOf('-') === -1; 45 | 46 | const date = (arr[0] || '').split(firstDay ? '.' : '-'); 47 | const time = (arr[1] || '').split(':'); 48 | 49 | if (date.length < 4 && time.length < 2) { 50 | return new Date(self); 51 | } 52 | 53 | index = (time[2] || '').indexOf('.'); 54 | 55 | // milliseconds 56 | if (index !== -1) { 57 | time[3] = time[2].substring(index + 1); 58 | time[2] = time[2].substring(0, index); 59 | } else { 60 | time[3] = '0'; 61 | } 62 | 63 | const parsed = [ 64 | parseInt(date[firstDay ? 2 : 0], 10), // year 65 | parseInt(date[1], 10), // month 66 | parseInt(date[firstDay ? 0 : 2], 10), // day 67 | parseInt(time[0], 10), // hours 68 | parseInt(time[1], 10), // minutes 69 | parseInt(time[2], 10), // seconds 70 | parseInt(time[3], 10) // miliseconds 71 | ]; 72 | 73 | const def = new Date(); 74 | 75 | for (let i = 0; i < parsed.length; i++) { 76 | if (isNaN(parsed[i])) { 77 | parsed[i] = 0; 78 | } 79 | 80 | const value = parsed[i]; 81 | if (value !== 0) { 82 | continue; 83 | } 84 | 85 | switch (i) { 86 | case 0: 87 | if (value <= 0) { 88 | parsed[i] = def.getFullYear(); 89 | } 90 | break; 91 | case 1: 92 | if (value <= 0) { 93 | parsed[i] = def.getMonth() + 1; 94 | } 95 | break; 96 | case 2: 97 | if (value <= 0) { 98 | parsed[i] = def.getDate(); 99 | } 100 | break; 101 | } 102 | } 103 | 104 | return new Date(parsed[0], parsed[1] - 1, parsed[2], parsed[3], parsed[4], parsed[5]); 105 | } 106 | 107 | /** 108 | * Get Error Message per gdscode 109 | * @param {{gdscode: Number, params: Any[]}[]} status 110 | * @returns {String} - Error message 111 | */ 112 | const lookupMessages = (status) => { 113 | const messages = status.map((item) => { 114 | let text = MessagesError[item.gdscode]; 115 | if (text === undefined) { 116 | return 'Unknow error'; 117 | } 118 | if (item.params !== undefined) { 119 | item.params.forEach((param, i) => { 120 | text = text.replace('@' + (i + 1), param); 121 | }); 122 | } 123 | return text; 124 | }); 125 | return messages.join(', '); 126 | } 127 | 128 | /** 129 | * Escape value 130 | * @param {Object} value 131 | * @param {Number} protocolVersion (optional, default: PROTOCOL_VERSION13) 132 | * @return {String} 133 | */ 134 | const escape = function(value, protocolVersion) { 135 | 136 | if (value === null || value === undefined) 137 | return 'NULL'; 138 | 139 | switch (typeof(value)) { 140 | case 'boolean': 141 | if ((protocolVersion || Const.PROTOCOL_VERSION13) >= Const.PROTOCOL_VERSION13) 142 | return value ? 'true' : 'false'; 143 | else 144 | return value ? '1' : '0'; 145 | case 'number': 146 | return value.toString(); 147 | case 'string': 148 | return "'" + value.replace(/'/g, "''").replace(/\\/g, '\\\\') + "'"; 149 | } 150 | 151 | if (value instanceof Date) 152 | return "'" + value.getFullYear() + '-' + (value.getMonth()+1).toString().padLeft(2, '0') + '-' + value.getDate().toString().padLeft(2, '0') + ' ' + value.getHours().toString().padLeft(2, '0') + ':' + value.getMinutes().toString().padLeft(2, '0') + ':' + value.getSeconds().toString().padLeft(2, '0') + '.' + value.getMilliseconds().toString().padLeft(3, '0') + "'"; 153 | 154 | throw new Error('Escape supports only primitive values.'); 155 | }; 156 | 157 | function noop() {} 158 | 159 | module.exports = { 160 | escape, 161 | lookupMessages, 162 | noop, 163 | parseDate, 164 | }; 165 | -------------------------------------------------------------------------------- /lib/wire/const.js: -------------------------------------------------------------------------------- 1 | /*************************************** 2 | * 3 | * Constantes 4 | * 5 | ***************************************/ 6 | 7 | const defaultOptions = { 8 | DEFAULT_HOST : '127.0.0.1', 9 | DEFAULT_PORT : 3050, 10 | DEFAULT_USER : 'SYSDBA', 11 | DEFAULT_PASSWORD : 'masterkey', 12 | DEFAULT_LOWERCASE_KEYS : false, 13 | DEFAULT_PAGE_SIZE : 4096, 14 | DEFAULT_SVC_NAME : 'service_mgr', 15 | DEFAULT_ENCODING : 'UTF8', 16 | DEFAULT_FETCHSIZE : 200, 17 | }; 18 | 19 | const buffer= { 20 | MAX_BUFFER_SIZE : 8192, 21 | }; 22 | 23 | const int = { 24 | MAX_INT : Math.pow(2, 31) - 1, 25 | MIN_INT : -Math.pow(2, 31), 26 | }; 27 | 28 | const op = { 29 | op_void : 0, // Packet has been voided 30 | op_connect : 1, // Connect to remote server 31 | op_exit : 2, // Remote end has exitted 32 | op_accept : 3, // Server accepts connection 33 | op_reject : 4, // Server rejects connection 34 | op_disconnect : 6, // Connect is going away 35 | op_response : 9, // Generic response block 36 | 37 | // Full context server operations 38 | 39 | op_attach : 19, // Attach database 40 | op_create : 20, // Create database 41 | op_detach : 21, // Detach database 42 | op_compile : 22, // Request based operations 43 | op_start : 23, 44 | op_start_and_send : 24, 45 | op_send : 25, 46 | op_receive : 26, 47 | op_unwind : 27, // apparently unused, see protocol.cpp's case op_unwind 48 | op_release : 28, 49 | 50 | op_transaction : 29, // Transaction operations 51 | op_commit : 30, 52 | op_rollback : 31, 53 | op_prepare : 32, 54 | op_reconnect : 33, 55 | 56 | op_create_blob : 34, // Blob operations 57 | op_open_blob : 35, 58 | op_get_segment : 36, 59 | op_put_segment : 37, 60 | op_cancel_blob : 38, 61 | op_close_blob : 39, 62 | 63 | op_info_database : 40, // Information services 64 | op_info_request : 41, 65 | op_info_transaction : 42, 66 | op_info_blob : 43, 67 | 68 | op_batch_segments : 44, // Put a bunch of blob segments 69 | 70 | op_que_events : 48, // Que event notification request 71 | op_cancel_events : 49, // Cancel event notification request 72 | op_commit_retaining : 50, // Commit retaining (what else) 73 | op_prepare2 : 51, // Message form of prepare 74 | op_event : 52, // Completed event request (asynchronous) 75 | op_connect_request : 53, // Request to establish connection 76 | op_aux_connect : 54, // Establish auxiliary connection 77 | op_ddl : 55, // DDL call 78 | op_open_blob2 : 56, 79 | op_create_blob2 : 57, 80 | op_get_slice : 58, 81 | op_put_slice : 59, 82 | op_slice : 60, // Successful response to op_get_slice 83 | op_seek_blob : 61, // Blob seek operation 84 | 85 | // DSQL operations 86 | 87 | op_allocate_statement : 62, // allocate a statment handle 88 | op_execute : 63, // execute a prepared statement 89 | op_exec_immediate : 64, // execute a statement 90 | op_fetch : 65, // fetch a record 91 | op_fetch_response : 66, // response for record fetch 92 | op_free_statement : 67, // free a statement 93 | op_prepare_statement : 68, // prepare a statement 94 | op_set_cursor : 69, // set a cursor name 95 | op_info_sql : 70, 96 | 97 | op_dummy : 71, // dummy packet to detect loss of client 98 | op_response_piggyback : 72, // response block for piggybacked messages 99 | op_start_and_receive : 73, 100 | op_start_send_and_receive : 74, 101 | op_exec_immediate2 : 75, // execute an immediate statement with msgs 102 | op_execute2 : 76, // execute a statement with msgs 103 | op_insert : 77, 104 | op_sql_response : 78, // response from execute, exec immed, insert 105 | op_transact : 79, 106 | op_transact_response : 80, 107 | op_drop_database : 81, 108 | op_service_attach : 82, 109 | op_service_detach : 83, 110 | op_service_info : 84, 111 | op_service_start : 85, 112 | op_rollback_retaining : 86, 113 | op_partial : 89, // packet is not complete - delay processing 114 | op_trusted_auth : 90, 115 | op_cancel : 91, 116 | op_cont_auth : 92, 117 | op_ping : 93, 118 | op_accept_data : 94, // Server accepts connection and returns some data to client 119 | op_abort_aux_connection : 95, // Async operation - stop waiting for async connection to arrive 120 | op_crypt : 96, 121 | op_crypt_key_callback : 97, 122 | op_cond_accept : 98, // Server accepts connection, returns some data to client 123 | // and asks client to continue authentication before attach call 124 | }; 125 | 126 | const dsql = { 127 | DSQL_close : 1, 128 | DSQL_drop : 2, 129 | DSQL_unprepare : 4, // >: 2.5 130 | }; 131 | 132 | /***********************/ 133 | /* ISC Error Codes */ 134 | /***********************/ 135 | const iscError = { 136 | isc_sqlerr: 335544436, 137 | isc_arg_end : 0, // end of argument list 138 | isc_arg_gds : 1, // generic DSRI status value 139 | isc_arg_string : 2, // string argument 140 | isc_arg_cstring : 3, // count & string argument 141 | isc_arg_number : 4, // numeric argument (long) 142 | isc_arg_interpreted : 5, // interpreted status code (string) 143 | isc_arg_unix : 7, // UNIX error code 144 | isc_arg_next_mach : 15, // NeXT/Mach error code 145 | isc_arg_win32 : 17, // Win32 error code 146 | isc_arg_warning : 18, // warning argument 147 | isc_arg_sql_state : 19, // SQLSTATE 148 | }; 149 | 150 | const connect = { 151 | CONNECT_VERSION2 : 2, 152 | CONNECT_VERSION3 : 3, 153 | ARCHITECTURE_GENERIC : 1, 154 | }; 155 | 156 | /*******************/ 157 | /* Protocols */ 158 | /*******************/ 159 | const FB_PROTOCOL_FLAG = 0x8000; 160 | const protocol = { 161 | // Protocol 10 includes support for warnings and removes the requirement for 162 | // encoding and decoding status codes 163 | PROTOCOL_VERSION10 : 10, 164 | 165 | // Since protocol 11 we must be separated from Borland Interbase. 166 | // Therefore always set highmost bit in protocol version to 1. 167 | // For unsigned protocol version this does not break version's compare. 168 | FB_PROTOCOL_FLAG : FB_PROTOCOL_FLAG, 169 | FB_PROTOCOL_MASK : ~FB_PROTOCOL_FLAG & 0xFFFF, 170 | 171 | // Protocol 11 has support for user authentication related 172 | // operations (op_update_account_info, op_authenticate_user and 173 | // op_trusted_auth). When specific operation is not supported, 174 | // we say "sorry". 175 | PROTOCOL_VERSION11 : (FB_PROTOCOL_FLAG | 11), 176 | 177 | // Protocol 12 has support for asynchronous call op_cancel. 178 | // Currently implemented asynchronously only for TCP/IP. 179 | PROTOCOL_VERSION12 : (FB_PROTOCOL_FLAG | 12), 180 | 181 | // Protocol 13 has support for authentication plugins (op_cont_auth). 182 | PROTOCOL_VERSION13 : (FB_PROTOCOL_FLAG | 13), 183 | }; 184 | 185 | // Protocols types (accept_type) 186 | const acceptType = { 187 | ptype_rpc : 2, // Simple remote procedure call 188 | ptype_batch_send : 3, // Batch sends, no asynchrony 189 | ptype_out_of_band : 4, // Batch sends w/ out of band notification 190 | ptype_lazy_send : 5, // Deferred packets delivery; 191 | ptype_mask : 0xFF, // Mask - up to 255 types of protocol 192 | pflag_compress : 0x100 // Turn on compression if possible 193 | }; 194 | 195 | const SUPPORTED_PROTOCOL = [ 196 | [protocol.PROTOCOL_VERSION10, connect.ARCHITECTURE_GENERIC, acceptType.ptype_rpc, acceptType.ptype_batch_send, 1], 197 | [protocol.PROTOCOL_VERSION11, connect.ARCHITECTURE_GENERIC, acceptType.ptype_lazy_send, acceptType.ptype_lazy_send, 2], 198 | [protocol.PROTOCOL_VERSION12, connect.ARCHITECTURE_GENERIC, acceptType.ptype_lazy_send, acceptType.ptype_lazy_send, 3], 199 | [protocol.PROTOCOL_VERSION13, connect.ARCHITECTURE_GENERIC, acceptType.ptype_lazy_send, acceptType.ptype_lazy_send, 4], 200 | ]; 201 | 202 | const authPlugin = { 203 | AUTH_PLUGIN_LEGACY : 'Legacy_Auth', 204 | AUTH_PLUGIN_SRP : 'Srp', 205 | // AUTH_PLUGIN_SRP256 : 'Srp256', 206 | }; 207 | 208 | const authOptions = { 209 | // AUTH_PLUGIN_LIST : [authPlugin.AUTH_PLUGIN_SRP256, authPlugin.AUTH_PLUGIN_SRP, authPlugin.AUTH_PLUGIN_LEGACY], 210 | AUTH_PLUGIN_LIST : [authPlugin.AUTH_PLUGIN_SRP, authPlugin.AUTH_PLUGIN_LEGACY], 211 | // AUTH_PLUGIN_SRP_LIST : [authPlugin.AUTH_PLUGIN_SRP256, authPlugin.AUTH_PLUGIN_SRP], 212 | AUTH_PLUGIN_SRP_LIST : [authPlugin.AUTH_PLUGIN_SRP], 213 | LEGACY_AUTH_SALT : '9z', 214 | WIRE_CRYPT_DISABLE : 0, 215 | WIRE_CRYPT_ENABLE : 1, 216 | }; 217 | 218 | /*******************/ 219 | /* SQL Type */ 220 | /*******************/ 221 | const sqlType = { 222 | SQL_TEXT : 452, // Array of char 223 | SQL_VARYING : 448, 224 | SQL_SHORT : 500, 225 | SQL_LONG : 496, 226 | SQL_FLOAT : 482, 227 | SQL_DOUBLE : 480, 228 | SQL_D_FLOAT : 530, 229 | SQL_TIMESTAMP : 510, 230 | SQL_BLOB : 520, 231 | SQL_ARRAY : 540, 232 | SQL_QUAD : 550, 233 | SQL_TYPE_TIME : 560, 234 | SQL_TYPE_DATE : 570, 235 | SQL_INT64 : 580, 236 | SQL_INT128: 32752, // >= 4.0 237 | SQL_BOOLEAN : 32764, // >: 3.0 238 | SQL_NULL : 32766, // >= 2.5 239 | }; 240 | 241 | const blobType = { 242 | isc_blob_text : 1, 243 | }; 244 | 245 | /*******************/ 246 | /* Blr definitions */ 247 | /*******************/ 248 | const blr = { 249 | blr_text : 14, 250 | blr_text2 : 15, 251 | blr_short : 7, 252 | blr_long : 8, 253 | blr_quad : 9, 254 | blr_float : 10, 255 | blr_double : 27, 256 | blr_d_float : 11, 257 | blr_timestamp : 35, 258 | blr_varying : 37, 259 | blr_varying2 : 38, 260 | blr_blob : 261, 261 | blr_cstring : 40, 262 | blr_cstring2 : 41, 263 | blr_blob_id : 45, 264 | blr_sql_date : 12, 265 | blr_sql_time : 13, 266 | blr_int64 : 16, 267 | blr_int128 : 26, // >: 4.0 268 | blr_blob2 : 17, // >: 2.0 269 | blr_domain_name : 18, // >: 2.1 270 | blr_domain_name2 : 19, // >: 2.1 271 | blr_not_nullable : 20, // >: 2.1 272 | blr_column_name : 21, // >: 2.5 273 | blr_column_name2 : 22, // >: 2.5 274 | blr_bool : 23, // >: 3.0 275 | 276 | blr_version4 : 4, 277 | blr_version5 : 5, // dialect 3 278 | blr_eoc : 76, 279 | blr_end : 255, 280 | 281 | blr_assignment : 1, 282 | blr_begin : 2, 283 | blr_dcl_variable : 3, 284 | blr_message : 4, 285 | }; 286 | 287 | /**********************************/ 288 | /* Database parameter block stuff */ 289 | /**********************************/ 290 | const dpb = { 291 | isc_dpb_version1 : 1, 292 | isc_dpb_version2 : 2, // >: FB30 293 | isc_dpb_cdd_pathname : 1, 294 | isc_dpb_allocation : 2, 295 | isc_dpb_journal : 3, 296 | isc_dpb_page_size : 4, 297 | isc_dpb_num_buffers : 5, 298 | isc_dpb_buffer_length : 6, 299 | isc_dpb_debug : 7, 300 | isc_dpb_garbage_collect : 8, 301 | isc_dpb_verify : 9, 302 | isc_dpb_sweep : 10, 303 | isc_dpb_enable_journal : 11, 304 | isc_dpb_disable_journal : 12, 305 | isc_dpb_dbkey_scope : 13, 306 | isc_dpb_number_of_users : 14, 307 | isc_dpb_trace : 15, 308 | isc_dpb_no_garbage_collect : 16, 309 | isc_dpb_damaged : 17, 310 | isc_dpb_license : 18, 311 | isc_dpb_sys_user_name : 19, 312 | isc_dpb_encrypt_key : 20, 313 | isc_dpb_activate_shadow : 21, 314 | isc_dpb_sweep_interval : 22, 315 | isc_dpb_delete_shadow : 23, 316 | isc_dpb_force_write : 24, 317 | isc_dpb_begin_log : 25, 318 | isc_dpb_quit_log : 26, 319 | isc_dpb_no_reserve : 27, 320 | isc_dpb_user_name : 28, 321 | isc_dpb_password : 29, 322 | isc_dpb_password_enc : 30, 323 | isc_dpb_sys_user_name_enc : 31, 324 | isc_dpb_interp : 32, 325 | isc_dpb_online_dump : 33, 326 | isc_dpb_old_file_size : 34, 327 | isc_dpb_old_num_files : 35, 328 | isc_dpb_old_file : 36, 329 | isc_dpb_old_start_page : 37, 330 | isc_dpb_old_start_seqno : 38, 331 | isc_dpb_old_start_file : 39, 332 | isc_dpb_old_dump_id : 41, 333 | isc_dpb_lc_messages : 47, 334 | isc_dpb_lc_ctype : 48, 335 | isc_dpb_cache_manager : 49, 336 | isc_dpb_shutdown : 50, 337 | isc_dpb_online : 51, 338 | isc_dpb_shutdown_delay : 52, 339 | isc_dpb_reserved : 53, 340 | isc_dpb_overwrite : 54, 341 | isc_dpb_sec_attach : 55, 342 | isc_dpb_connect_timeout : 57, 343 | isc_dpb_dummy_packet_interval : 58, 344 | isc_dpb_gbak_attach : 59, 345 | isc_dpb_sql_role_name : 60, 346 | isc_dpb_set_page_buffers : 61, 347 | isc_dpb_working_directory : 62, 348 | isc_dpb_sql_dialect : 63, 349 | isc_dpb_set_db_readonly : 64, 350 | isc_dpb_set_db_sql_dialect : 65, 351 | isc_dpb_gfix_attach : 66, 352 | isc_dpb_gstat_attach : 67, 353 | isc_dpb_set_db_charset : 68, 354 | isc_dpb_gsec_attach : 69, 355 | isc_dpb_address_path : 70, 356 | isc_dpb_process_id : 71, 357 | isc_dpb_no_db_triggers : 72, 358 | isc_dpb_trusted_auth : 73, 359 | isc_dpb_process_name : 74, 360 | isc_dpb_trusted_role : 75, 361 | isc_dpb_org_filename : 76, 362 | isc_dpb_utf8_filename : 77, 363 | isc_dpb_ext_call_depth : 78, 364 | isc_dpb_auth_block : 79, 365 | isc_dpb_client_version : 80, 366 | isc_dpb_remote_protocol : 81, 367 | isc_dpb_host_name : 82, 368 | isc_dpb_os_user : 83, 369 | isc_dpb_specific_auth_data : 84, 370 | isc_dpb_auth_plugin_list : 85, 371 | isc_dpb_auth_plugin_name : 86, 372 | isc_dpb_config : 87, 373 | isc_dpb_nolinger : 88, 374 | isc_dpb_reset_icu : 89, 375 | isc_dpb_map_attach : 90, 376 | isc_dpb_session_time_zone : 91, 377 | }; 378 | 379 | const cnct = { 380 | CNCT_user : 1, // User name 381 | CNCT_passwd : 2, 382 | // CNCT_ppo : 3, // Apollo person, project, organization. OBSOLETE. 383 | CNCT_host : 4, 384 | CNCT_group : 5, // Effective Unix group id 385 | CNCT_user_verification : 6, // Attach/create using this connection will use user verification 386 | CNCT_specific_data : 7, // Some data, needed for user verification on server 387 | CNCT_plugin_name : 8, // Name of plugin, which generated that data 388 | CNCT_login : 9, // Same data as isc_dpb_user_name 389 | CNCT_plugin_list : 10, // List of plugins, available on client 390 | CNCT_client_crypt : 11, // Client encyption level (DISABLED/ENABLED/REQUIRED) 391 | WIRE_CRYPT_DISABLED : 0, 392 | WIRE_CRYPT_ENABLED : 1, 393 | WIRE_CRYPT_REQUIRED : 2, 394 | }; 395 | 396 | /****************************/ 397 | /* Common, structural codes */ 398 | /****************************/ 399 | const common = { 400 | isc_info_end : 1, 401 | isc_info_truncated : 2, 402 | isc_info_error : 3, 403 | isc_info_data_not_ready : 4, 404 | isc_info_length : 126, 405 | isc_info_flag_end : 127, 406 | }; 407 | 408 | /*************************************/ 409 | /* Transaction parameter block stuff */ 410 | /*************************************/ 411 | const tpb = { 412 | isc_tpb_version1 : 1, 413 | isc_tpb_version3 : 3, 414 | isc_tpb_consistency : 1, 415 | isc_tpb_concurrency : 2, 416 | isc_tpb_shared : 3, // < FB21 417 | isc_tpb_protected : 4, // < FB21 418 | isc_tpb_exclusive : 5, // < FB21 419 | isc_tpb_wait : 6, 420 | isc_tpb_nowait : 7, 421 | isc_tpb_read : 8, 422 | isc_tpb_write : 9, 423 | isc_tpb_lock_read : 10, 424 | isc_tpb_lock_write : 11, 425 | isc_tpb_verb_time : 12, 426 | isc_tpb_commit_time : 13, 427 | isc_tpb_ignore_limbo : 14, 428 | isc_tpb_read_committed : 15, 429 | isc_tpb_autocommit : 16, 430 | isc_tpb_rec_version : 17, 431 | isc_tpb_no_rec_version : 18, 432 | isc_tpb_restart_requests : 19, 433 | isc_tpb_no_auto_undo : 20, 434 | isc_tpb_lock_timeout : 21, // >= FB20 435 | }; 436 | 437 | const transactionIsolation = { 438 | ISOLATION_READ_UNCOMMITTED : [tpb.isc_tpb_read_committed, tpb.isc_tpb_rec_version], 439 | ISOLATION_READ_COMMITTED : [tpb.isc_tpb_read_committed, tpb.isc_tpb_no_rec_version], 440 | ISOLATION_REPEATABLE_READ : [tpb.isc_tpb_concurrency], 441 | ISOLATION_SERIALIZABLE : [tpb.isc_tpb_consistency], 442 | ISOLATION_READ_COMMITTED_READ_ONLY : [tpb.isc_tpb_read_committed, tpb.isc_tpb_no_rec_version], 443 | }; 444 | 445 | /*************************/ 446 | /* SQL information items */ 447 | /*************************/ 448 | const sqlInfo = { 449 | isc_info_sql_select : 4, 450 | isc_info_sql_bind : 5, 451 | isc_info_sql_num_variables : 6, 452 | isc_info_sql_describe_vars : 7, 453 | isc_info_sql_describe_end : 8, 454 | isc_info_sql_sqlda_seq : 9, 455 | isc_info_sql_message_seq : 10, 456 | isc_info_sql_type : 11, 457 | isc_info_sql_sub_type : 12, 458 | isc_info_sql_scale : 13, 459 | isc_info_sql_length : 14, 460 | isc_info_sql_null_ind : 15, 461 | isc_info_sql_field : 16, 462 | isc_info_sql_relation : 17, 463 | isc_info_sql_owner : 18, 464 | isc_info_sql_alias : 19, 465 | isc_info_sql_sqlda_start : 20, 466 | isc_info_sql_stmt_type : 21, 467 | isc_info_sql_get_plan : 22, 468 | isc_info_sql_records : 23, 469 | isc_info_sql_batch_fetch : 24, 470 | isc_info_sql_relation_alias : 25, // >: 2.0 471 | isc_info_sql_explain_plan : 26, // >= 3.0 472 | }; 473 | 474 | const statementInfo = { 475 | isc_info_sql_stmt_select : 1, 476 | isc_info_sql_stmt_insert : 2, 477 | isc_info_sql_stmt_update : 3, 478 | isc_info_sql_stmt_delete : 4, 479 | isc_info_sql_stmt_ddl : 5, 480 | isc_info_sql_stmt_get_segment : 6, 481 | isc_info_sql_stmt_put_segment : 7, 482 | isc_info_sql_stmt_exec_procedure : 8, 483 | isc_info_sql_stmt_start_trans : 9, 484 | isc_info_sql_stmt_commit : 10, 485 | isc_info_sql_stmt_rollback : 11, 486 | isc_info_sql_stmt_select_for_upd : 12, 487 | isc_info_sql_stmt_set_generator : 13, 488 | isc_info_sql_stmt_savepoint : 14, 489 | }; 490 | 491 | const DESCRIBE = [ 492 | sqlInfo.isc_info_sql_stmt_type, 493 | sqlInfo.isc_info_sql_select, 494 | sqlInfo.isc_info_sql_describe_vars, 495 | sqlInfo.isc_info_sql_sqlda_seq, 496 | sqlInfo.isc_info_sql_type, 497 | sqlInfo.isc_info_sql_sub_type, 498 | sqlInfo.isc_info_sql_scale, 499 | sqlInfo.isc_info_sql_length, 500 | sqlInfo.isc_info_sql_field, 501 | sqlInfo.isc_info_sql_relation, 502 | //isc_info_sql_owner, 503 | sqlInfo.isc_info_sql_alias, 504 | sqlInfo.isc_info_sql_describe_end, 505 | sqlInfo.isc_info_sql_bind, 506 | sqlInfo.isc_info_sql_describe_vars, 507 | sqlInfo.isc_info_sql_sqlda_seq, 508 | sqlInfo.isc_info_sql_type, 509 | sqlInfo.isc_info_sql_sub_type, 510 | sqlInfo.isc_info_sql_scale, 511 | sqlInfo.isc_info_sql_length, 512 | sqlInfo.isc_info_sql_describe_end 513 | ]; 514 | 515 | /***********************/ 516 | /* ISC Services */ 517 | /***********************/ 518 | const iscAction = { 519 | isc_action_svc_backup : 1, /* Starts database backup process on the server */ 520 | isc_action_svc_restore : 2, /* Starts database restore process on the server */ 521 | isc_action_svc_repair : 3, /* Starts database repair process on the server */ 522 | isc_action_svc_add_user : 4, /* Adds a new user to the security database */ 523 | isc_action_svc_delete_user : 5, /* Deletes a user record from the security database */ 524 | isc_action_svc_modify_user : 6, /* Modifies a user record in the security database */ 525 | isc_action_svc_display_user : 7, /* Displays a user record from the security database */ 526 | isc_action_svc_properties : 8, /* Sets database properties */ 527 | isc_action_svc_add_license : 9, /* Adds a license to the license file */ 528 | isc_action_svc_remove_license : 10, /* Removes a license from the license file */ 529 | isc_action_svc_db_stats : 11, /* Retrieves database statistics */ 530 | isc_action_svc_get_ib_log : 12, /* Retrieves the InterBase log file from the server */ 531 | isc_action_svc_get_fb_log : 12, // isc_action_svc_get_ib_log, /* Retrieves the Firebird log file from the server */ 532 | isc_action_svc_nbak : 20, /* start nbackup */ 533 | isc_action_svc_nrest : 21, /* start nrestore */ 534 | isc_action_svc_trace_start : 22, 535 | isc_action_svc_trace_stop : 23, 536 | isc_action_svc_trace_suspend : 24, 537 | isc_action_svc_trace_resume : 25, 538 | isc_action_svc_trace_list : 26, 539 | }; 540 | 541 | /* Services Properties */ 542 | const service = { 543 | isc_spb_prp_page_buffers : 5, 544 | isc_spb_prp_sweep_interval : 6, 545 | isc_spb_prp_shutdown_db : 7, 546 | isc_spb_prp_deny_new_attachments : 9, 547 | isc_spb_prp_deny_new_transactions : 10, 548 | isc_spb_prp_reserve_space : 11, 549 | isc_spb_prp_write_mode : 12, 550 | isc_spb_prp_access_mode : 13, 551 | isc_spb_prp_set_sql_dialect : 14, 552 | isc_spb_num_att : 5, 553 | isc_spb_num_db : 6, 554 | // SHUTDOWN OPTION FOR 2.0 555 | isc_spb_prp_force_shutdown : 41, 556 | isc_spb_prp_attachments_shutdown : 42, 557 | isc_spb_prp_transactions_shutdown : 43, 558 | isc_spb_prp_shutdown_mode : 44, 559 | isc_spb_prp_online_mode : 45, 560 | 561 | isc_spb_prp_sm_normal : 0, 562 | isc_spb_prp_sm_multi : 1, 563 | isc_spb_prp_sm_single : 2, 564 | isc_spb_prp_sm_full : 3, 565 | 566 | // WRITE_MODE_PARAMETERS 567 | isc_spb_prp_wm_async : 37, 568 | isc_spb_prp_wm_sync : 38, 569 | 570 | // ACCESS_MODE_PARAMETERS 571 | isc_spb_prp_am_readonly : 39, 572 | isc_spb_prp_am_readwrite : 40, 573 | 574 | // RESERVE_SPACE_PARAMETERS 575 | isc_spb_prp_res_use_full : 35, 576 | isc_spb_prp_res : 36, 577 | 578 | // Option Flags 579 | isc_spb_prp_activate : 0x0100, 580 | isc_spb_prp_db_online : 0x0200, 581 | }; 582 | 583 | /****************************/ 584 | /* Service info */ 585 | /****************************/ 586 | const serviceInfo = { 587 | isc_info_svc_svr_db_info: 50, /* Retrieves the number of attachments and databases */ 588 | isc_info_svc_get_license: 51, /* Retrieves all license keys and IDs from the license file */ 589 | isc_info_svc_get_license_mask: 52, /* Retrieves a bitmask representing licensed options on the server */ 590 | isc_info_svc_get_config: 53, /* Retrieves the parameters and values for IB_CONFIG */ 591 | isc_info_svc_version: 54, /* Retrieves the version of the services manager */ 592 | isc_info_svc_server_version: 55, /* Retrieves the version of the InterBase server */ 593 | isc_info_svc_implementation: 56, /* Retrieves the implementation of the InterBase server */ 594 | isc_info_svc_capabilities: 57, /* Retrieves a bitmask representing the server's capabilities */ 595 | isc_info_svc_user_dbpath: 58, /* Retrieves the path to the security database in use by the server */ 596 | isc_info_svc_get_env: 59, /* Retrieves the setting of $INTERBASE */ 597 | isc_info_svc_get_env_lock: 60, /* Retrieves the setting of $INTERBASE_LCK */ 598 | isc_info_svc_get_env_msg: 61, /* Retrieves the setting of $INTERBASE_MSG */ 599 | isc_info_svc_line: 62, /* Retrieves 1 line of service output per call */ 600 | isc_info_svc_to_eof: 63, /* Retrieves as much of the server output as will fit in the supplied buffer */ 601 | isc_info_svc_timeout: 64, /* Sets / signifies a timeout value for reading service information */ 602 | isc_info_svc_get_licensed_users: 65, /* Retrieves the number of users licensed for accessing the server */ 603 | isc_info_svc_limbo_trans: 66, /* Retrieve the limbo transactions */ 604 | isc_info_svc_running: 67, /* Checks to see if a service is running on an attachment */ 605 | isc_info_svc_get_users: 68, /* Returns the user information from isc_action_svc_display_users */ 606 | isc_info_svc_stdin: 78, 607 | }; 608 | 609 | /*************************************/ 610 | /* Services parameter block stuff */ 611 | /*************************************/ 612 | const spb = { 613 | isc_spb_version1 : 1, 614 | isc_spb_current_version : 2, 615 | isc_spb_version : 2, // isc_spb_current_version, 616 | isc_spb_user_name : dpb.isc_dpb_user_name, 617 | isc_spb_sys_user_name : dpb.isc_dpb_sys_user_name, 618 | isc_spb_sys_user_name_enc : dpb.isc_dpb_sys_user_name_enc, 619 | isc_spb_password : dpb.isc_dpb_password, 620 | isc_spb_password_enc : dpb.isc_dpb_password_enc, 621 | isc_spb_command_line : 105, 622 | isc_spb_dbname : 106, 623 | isc_spb_verbose : 107, 624 | isc_spb_options : 108, 625 | }; 626 | 627 | /* · Backup Service ·*/ 628 | const serviceBackup = { 629 | isc_spb_bkp_file : 5, 630 | isc_spb_bkp_factor : 6, 631 | isc_spb_bkp_length : 7, 632 | isc_spb_bkp_ignore_checksums : 0x01, 633 | isc_spb_bkp_ignore_limbo : 0x02, 634 | isc_spb_bkp_metadata_only : 0x04, 635 | isc_spb_bkp_no_garbage_collect : 0x08, 636 | isc_spb_bkp_old_descriptions : 0x10, 637 | isc_spb_bkp_non_transportable : 0x20, 638 | isc_spb_bkp_convert : 0x40, 639 | isc_spb_bkp_expand : 0x80, 640 | isc_spb_bkp_no_triggers : 0x8000, 641 | // nbackup 642 | isc_spb_nbk_level : 5, 643 | isc_spb_nbk_file : 6, 644 | isc_spb_nbk_direct : 7, 645 | isc_spb_nbk_no_triggers : 0x01, 646 | }; 647 | 648 | /* Restore Service ·*/ 649 | const serviceRestore = { 650 | isc_spb_res_buffers : 9, 651 | isc_spb_res_page_size : 10, 652 | isc_spb_res_length : 11, 653 | isc_spb_res_access_mode : 12, 654 | isc_spb_res_fix_fss_data : 13, 655 | isc_spb_res_fix_fss_metadata : 14, 656 | isc_spb_res_am_readonly : service.isc_spb_prp_am_readonly, 657 | isc_spb_res_am_readwrite : service.isc_spb_prp_am_readwrite, 658 | isc_spb_res_deactivate_idx : 0x0100, 659 | isc_spb_res_no_shadow : 0x0200, 660 | isc_spb_res_no_validity : 0x0400, 661 | isc_spb_res_one_at_a_time : 0x0800, 662 | isc_spb_res_replace : 0x1000, 663 | isc_spb_res_create : 0x2000, 664 | isc_spb_res_use_all_space : 0x4000, 665 | }; 666 | 667 | /* · Repair Service ·*/ 668 | const serviceRepair = { 669 | isc_spb_rpr_commit_trans : 15, 670 | isc_spb_rpr_rollback_trans : 34, 671 | isc_spb_rpr_recover_two_phase : 17, 672 | isc_spb_tra_id : 18, 673 | isc_spb_single_tra_id : 19, 674 | isc_spb_multi_tra_id : 20, 675 | isc_spb_tra_state : 21, 676 | isc_spb_tra_state_limbo : 22, 677 | isc_spb_tra_state_commit : 23, 678 | isc_spb_tra_state_rollback : 24, 679 | isc_spb_tra_state_unknown : 25, 680 | isc_spb_tra_host_site : 26, 681 | isc_spb_tra_remote_site : 27, 682 | isc_spb_tra_db_path : 28, 683 | isc_spb_tra_advise : 29, 684 | isc_spb_tra_advise_commit : 30, 685 | isc_spb_tra_advise_rollback : 31, 686 | isc_spb_tra_advise_unknown : 33, 687 | isc_spb_rpr_validate_db : 0x01, 688 | isc_spb_rpr_sweep_db : 0x02, 689 | isc_spb_rpr_mend_db : 0x04, 690 | isc_spb_rpr_list_limbo_trans : 0x08, 691 | isc_spb_rpr_check_db : 0x10, 692 | isc_spb_rpr_ignore_checksum : 0x20, 693 | isc_spb_rpr_kill_shadows : 0x40, 694 | isc_spb_rpr_full : 0x80, 695 | isc_spb_rpr_icu : 0x0800, 696 | }; 697 | 698 | /* · Security Service ·*/ 699 | const serviceSecurity = { 700 | isc_spb_sec_userid : 5, 701 | isc_spb_sec_groupid : 6, 702 | isc_spb_sec_username : 7, 703 | isc_spb_sec_password : 8, 704 | isc_spb_sec_groupname : 9, 705 | isc_spb_sec_firstname : 10, 706 | isc_spb_sec_middlename : 11, 707 | isc_spb_sec_lastname : 12, 708 | isc_spb_sec_admin : 13, 709 | }; 710 | 711 | /* License Service */ 712 | const serviceLicence = { 713 | isc_spb_lic_key : 5, 714 | isc_spb_lic_id : 6, 715 | isc_spb_lic_desc : 7, 716 | }; 717 | 718 | /* Statistics Service */ 719 | const serviceStatistics = { 720 | isc_spb_sts_data_pages : 0x01, 721 | isc_spb_sts_db_log : 0x02, 722 | isc_spb_sts_hdr_pages : 0x04, 723 | isc_spb_sts_idx_pages : 0x08, 724 | isc_spb_sts_sys_relations : 0x10, 725 | isc_spb_sts_record_versions : 0x20, 726 | isc_spb_sts_table : 0x40, 727 | isc_spb_sts_nocreation : 0x80, 728 | }; 729 | 730 | /* Trace Service */ 731 | const serviceTrace = { 732 | isc_spb_trc_id : 1, 733 | isc_spb_trc_name : 2, 734 | isc_spb_trc_cfg : 3, 735 | }; 736 | 737 | module.exports = Object.freeze({ 738 | ...acceptType, 739 | ...authPlugin, 740 | ...authOptions, 741 | ...blr, 742 | ...blobType, 743 | ...buffer, 744 | ...cnct, 745 | ...common, 746 | ...connect, 747 | ...defaultOptions, 748 | DESCRIBE, 749 | ...dpb, 750 | ...dsql, 751 | ...int, 752 | ...iscAction, 753 | ...iscError, 754 | ...op, 755 | ...protocol, 756 | ...service, 757 | ...serviceBackup, 758 | ...serviceInfo, 759 | ...serviceLicence, 760 | ...serviceRestore, 761 | ...serviceRepair, 762 | ...serviceSecurity, 763 | ...serviceStatistics, 764 | ...serviceTrace, 765 | ...sqlInfo, 766 | ...sqlType, 767 | ...spb, 768 | ...statementInfo, 769 | SUPPORTED_PROTOCOL, 770 | ...tpb, 771 | ...transactionIsolation, 772 | }); 773 | -------------------------------------------------------------------------------- /lib/wire/database.js: -------------------------------------------------------------------------------- 1 | const Events = require('events'); 2 | const { doError } = require('../callback'); 3 | const { escape } = require('../utils'); 4 | const EventConnection = require('./eventConnection'); 5 | const FbEventManager = require('./fbEventManager'); 6 | 7 | /*************************************** 8 | * 9 | * Database 10 | * 11 | ***************************************/ 12 | 13 | function Database(connection) { 14 | this.connection = connection; 15 | connection.db = this; 16 | this.eventid = 1; 17 | } 18 | 19 | Database.prototype.__proto__ = Object.create(Events.EventEmitter.prototype, { 20 | constructor: { 21 | value: Database, 22 | enumberable: false 23 | } 24 | }); 25 | 26 | Database.prototype.escape = function(value) { 27 | return escape(value, this.connection.accept.protocolVersion); 28 | }; 29 | 30 | Database.prototype.detach = function(callback, force) { 31 | 32 | var self = this; 33 | 34 | if (!force && self.connection._pending.length > 0) { 35 | self.connection._detachAuto = true; 36 | self.connection._detachCallback = callback; 37 | return self; 38 | } 39 | 40 | if (self.connection._pooled === false) { 41 | self.connection.detach(function (err, obj) { 42 | 43 | self.connection.disconnect(); 44 | self.emit('detach', false); 45 | 46 | if (callback) 47 | callback(err, obj); 48 | 49 | }, force); 50 | } else { 51 | self.emit('detach', false); 52 | if (callback) 53 | callback(); 54 | } 55 | 56 | return self; 57 | }; 58 | 59 | Database.prototype.transaction = function(options, callback) { 60 | return this.startTransaction(options, callback); 61 | }; 62 | 63 | Database.prototype.startTransaction = function(options, callback) { 64 | this.connection.startTransaction(options, callback); 65 | return this; 66 | }; 67 | 68 | Database.prototype.newStatement = function (query, callback) { 69 | 70 | this.startTransaction(function(err, transaction) { 71 | 72 | if (err) { 73 | callback(err); 74 | return; 75 | } 76 | 77 | transaction.newStatement(query, function(err, statement) { 78 | 79 | if (err) { 80 | callback(err); 81 | return; 82 | } 83 | 84 | transaction.commit(function(err) { 85 | callback(err, statement); 86 | }); 87 | }); 88 | }); 89 | 90 | return this; 91 | }; 92 | 93 | Database.prototype.execute = function(query, params, callback, custom) { 94 | 95 | if (params instanceof Function) { 96 | custom = callback; 97 | callback = params; 98 | params = undefined; 99 | } 100 | 101 | var self = this; 102 | 103 | self.connection.startTransaction(function(err, transaction) { 104 | 105 | if (err) { 106 | doError(err, callback); 107 | return; 108 | } 109 | 110 | transaction.execute(query, params, function(err, result, meta, isSelect) { 111 | 112 | if (err) { 113 | transaction.rollback(function() { 114 | doError(err, callback); 115 | }); 116 | return; 117 | } 118 | 119 | transaction.commit(function(err) { 120 | if (callback) 121 | callback(err, result, meta, isSelect); 122 | }); 123 | 124 | }, custom); 125 | }); 126 | 127 | return self; 128 | }; 129 | 130 | Database.prototype.sequentially = function(query, params, on, callback, asArray) { 131 | 132 | if (params instanceof Function) { 133 | asArray = callback; 134 | callback = on; 135 | on = params; 136 | params = undefined; 137 | } 138 | 139 | if (on === undefined){ 140 | throw new Error('Expected "on" delegate.'); 141 | } 142 | 143 | if (callback instanceof Boolean) { 144 | asArray = callback; 145 | callback = undefined; 146 | } 147 | 148 | var self = this; 149 | self.execute(query, params, callback, { asObject: !asArray, asStream: true, on: on }); 150 | return self; 151 | }; 152 | 153 | Database.prototype.query = function(query, params, callback) { 154 | 155 | if (params instanceof Function) { 156 | callback = params; 157 | params = undefined; 158 | } 159 | 160 | var self = this; 161 | self.execute(query, params, callback, { asObject: true, asStream: callback === undefined || callback === null }); 162 | return self; 163 | }; 164 | 165 | Database.prototype.drop = function(callback) { 166 | return this.connection.dropDatabase(callback); 167 | }; 168 | 169 | Database.prototype.attachEvent = function (callback) { 170 | var self = this; 171 | this.connection.auxConnection(function (err, socket_info) { 172 | 173 | if (err) { 174 | doError(err, callback); 175 | return; 176 | } 177 | 178 | const eventConnection = new EventConnection(self.connection.host, socket_info.port, function (err) { 179 | if (err) { 180 | doError(err, callback); 181 | return; 182 | } 183 | 184 | const evt = new FbEventManager(self, eventConnection, self.eventid++, function (err) { 185 | if (err) { 186 | doError(err, callback); 187 | return; 188 | } 189 | 190 | callback(err, evt); 191 | }); 192 | }, self); 193 | }); 194 | 195 | return this; 196 | } 197 | 198 | module.exports = Database; 199 | -------------------------------------------------------------------------------- /lib/wire/eventConnection.js: -------------------------------------------------------------------------------- 1 | const net = require('net'); 2 | const { XdrReader } = require('./serialize'); 3 | const DEFAULT_ENCODING = 'utf8'; 4 | const Const = require('./const'); 5 | 6 | var EventConnection = function (host, port, callback, db) { 7 | var self = this; 8 | this.db = db; 9 | this.emgr = null; 10 | this._isClosed = false; 11 | this._isOpened = false; 12 | this._socket = net.createConnection(port, host); 13 | this._bind_events(host, port, callback); 14 | this.error; 15 | this.eventcallback; 16 | }; 17 | 18 | EventConnection.prototype._bind_events = function (host, port, callback) { 19 | var self = this; 20 | 21 | self._socket.on('close', function () { 22 | 23 | self._isClosed = true; 24 | }) 25 | 26 | self._socket.on('error', function (e) { 27 | 28 | self.error = e; 29 | }) 30 | 31 | self._socket.on('connect', function () { 32 | self._isClosed = false; 33 | self._isOpened = true; 34 | if (callback) 35 | callback(); 36 | }); 37 | 38 | self._socket.on('data', function (data) { 39 | var xdr, buf; 40 | 41 | if (!self._xdr) { 42 | xdr = new XdrReader(data); 43 | } else { 44 | xdr = self._xdr; 45 | delete (self._xdr); 46 | buf = Buffer.from(data.length + xdr.buffer.length); 47 | xdr.buffer.copy(buf); 48 | data.copy(buf, xdr.buffer.length); 49 | xdr.buffer = buf; 50 | } 51 | 52 | try { 53 | var item, op; 54 | var op_pos = xdr.pos; 55 | var tmp_event; 56 | while (xdr.pos < xdr.buffer.length) { 57 | do { 58 | var r = xdr.readInt(); 59 | } while (r === Const.op_dummy); 60 | 61 | switch (r) { 62 | case Const.op_event: 63 | xdr.readInt(); // db handle 64 | var buf = xdr.readArray(); 65 | // first byte is always set to 1 66 | tmp_event = {}; 67 | var lst_event = []; 68 | var eventname = ''; 69 | var eventcount = 0; 70 | var pos = 1; 71 | while (pos < buf.length) { 72 | var len = buf.readInt8(pos++); 73 | eventname = buf.toString(DEFAULT_ENCODING, pos, pos + len); 74 | var prevcount = self.emgr.events[eventname] || 0; 75 | pos += len; 76 | eventcount = buf.readInt32LE(pos); 77 | tmp_event[eventname] = eventcount; 78 | pos += 4; 79 | if (prevcount !== eventcount) 80 | lst_event.push({ name: eventname, count: eventcount }); 81 | } 82 | xdr.readInt64(); // ignore AST INFO 83 | var event_id = xdr.readInt(); 84 | // set the new count in global event hash 85 | for (var evt in tmp_event) { 86 | self.emgr.events[evt] = tmp_event[evt]; 87 | } 88 | if (self.eventcallback) 89 | return self.eventcallback(null, { eventid: event_id, events: lst_event }); 90 | 91 | default: 92 | return cb(new Error('Unexpected:' + r)); 93 | } 94 | } 95 | } catch (err) { 96 | if (err instanceof RangeError) { // incomplete packet case 97 | xdr.buffer = xdr.buffer = xdr.buffer.slice(op_pos); 98 | xdr.pos = 0; 99 | self._xdr = xdr; 100 | } 101 | } 102 | }) 103 | } 104 | 105 | EventConnection.prototype.throwClosed = function (callback) { 106 | var err = new Error('Event Connection is closed.'); 107 | this.db.emit('error', err); 108 | if (callback) 109 | callback(err); 110 | return this; 111 | }; 112 | 113 | module.exports = EventConnection; -------------------------------------------------------------------------------- /lib/wire/fbEventManager.js: -------------------------------------------------------------------------------- 1 | const Events = require('events'); 2 | const { doError } = require('../callback'); 3 | 4 | 5 | function FbEventManager(db, eventconnection, eventid, callback) { 6 | this.db = db; 7 | this.eventconnection = eventconnection; 8 | this.events = {}; 9 | this.eventid = eventid; 10 | this._createEventLoop(callback); 11 | } 12 | 13 | FbEventManager.prototype.__proto__ = Object.create(Events.EventEmitter.prototype, { 14 | constructor: { 15 | value: FbEventManager, 16 | enumberable: false 17 | } 18 | }); 19 | 20 | FbEventManager.prototype._createEventLoop = function (callback) { 21 | var self = this; 22 | var cnx = this.db.connection; 23 | this.eventconnection.emgr = this; 24 | // create the loop 25 | function loop(first) { 26 | cnx.queEvents(self.events, self.eventid, function (err, ret) { 27 | if (err) { 28 | doError(err, callback); 29 | return; 30 | } 31 | if (first) 32 | callback(); 33 | }) 34 | } 35 | 36 | this.eventconnection.eventcallback = function (err, ret) { 37 | if (err || (self.eventid !== ret.eventid)) { 38 | doError(err || new Error('Bad eventid'), callback); 39 | return; 40 | } 41 | 42 | ret.events.forEach(function (event) { 43 | self.emit('post_event', event.name, event.count) 44 | }) 45 | 46 | loop(false); 47 | } 48 | 49 | loop(true); 50 | } 51 | 52 | FbEventManager.prototype._changeEvent = function (callback) { 53 | var self = this; 54 | 55 | self.db.connection.closeEvents(this.eventid, function (err) { 56 | if (err) { 57 | doError(err, callback); 58 | return; 59 | } 60 | 61 | self.db.connection.queEvents(self.events, self.eventid, callback); 62 | }) 63 | } 64 | 65 | FbEventManager.prototype.registerEvent = function (events, callback) { 66 | var self = this; 67 | 68 | if (self.db.connection._isClosed || self.eventconnection._isClosed) 69 | return self.eventconnection.throwClosed(callback); 70 | 71 | events.forEach((event) => self.events[event] = self.events[event] || 0); 72 | self._changeEvent(callback); 73 | } 74 | 75 | FbEventManager.prototype.unregisterEvent = function (events, callback) { 76 | var self = this; 77 | 78 | if (self.db.connection._isClosed || self.eventconnection._isClosed) 79 | return self.eventconnection.throwClosed(callback); 80 | 81 | events.forEach(function (event) { delete self.events[event] }); 82 | self._changeEvent(callback); 83 | } 84 | 85 | FbEventManager.prototype.close = function (callback) { 86 | var self = this; 87 | 88 | self.db.connection.closeEvents(this.eventid, function (err) { 89 | if (err) { 90 | doError(err, callback); 91 | return; 92 | } 93 | 94 | self.eventconnection._socket.end(); 95 | }); 96 | } 97 | 98 | module.exports = FbEventManager; -------------------------------------------------------------------------------- /lib/wire/serialize.js: -------------------------------------------------------------------------------- 1 | var Long = require('long'); 2 | 3 | function align(n) { 4 | return (n + 3) & ~3; 5 | } 6 | 7 | /*************************************** 8 | * 9 | * BLR Writer 10 | * 11 | ***************************************/ 12 | 13 | const 14 | MAX_STRING_SIZE = 255; 15 | 16 | var BlrWriter = exports.BlrWriter = function(size){ 17 | this.buffer = Buffer.alloc(size || 32); 18 | this.pos = 0; 19 | }; 20 | 21 | BlrWriter.prototype.addByte = function (b) { 22 | this.ensure(1); 23 | this.buffer.writeUInt8(b, this.pos); 24 | this.pos++; 25 | }; 26 | 27 | BlrWriter.prototype.addShort = function (b) { 28 | this.ensure(1); 29 | this.buffer.writeInt8(b, this.pos); 30 | this.pos++; 31 | }; 32 | 33 | BlrWriter.prototype.addSmall = function (b) { 34 | this.ensure(2); 35 | this.buffer.writeInt16LE(b, this.pos); 36 | this.pos += 2; 37 | }; 38 | 39 | BlrWriter.prototype.addWord = function (b) { 40 | this.ensure(2); 41 | this.buffer.writeUInt16LE(b, this.pos); 42 | this.pos += 2; 43 | }; 44 | 45 | BlrWriter.prototype.addInt32 = function (b) { 46 | this.ensure(4); 47 | this.buffer.writeUInt32LE(b, this.pos); 48 | this.pos += 4; 49 | }; 50 | 51 | BlrWriter.prototype.addByteInt32 = function (c, b) { 52 | this.addByte(c); 53 | this.ensure(4); 54 | this.buffer.writeUInt32LE(b, this.pos); 55 | this.pos += 4; 56 | }; 57 | 58 | BlrWriter.prototype.addNumeric = function (c, v) { 59 | 60 | if (v < 256){ 61 | this.ensure(3); 62 | this.buffer.writeUInt8(c, this.pos); 63 | this.pos++; 64 | this.buffer.writeUInt8(1, this.pos); 65 | this.pos++; 66 | this.buffer.writeUInt8(v, this.pos); 67 | this.pos++; 68 | return; 69 | } 70 | 71 | this.ensure(6); 72 | this.buffer.writeUInt8(c, this.pos); 73 | this.pos++; 74 | this.buffer.writeUInt8(4, this.pos); 75 | this.pos++; 76 | this.buffer.writeInt32BE(v, this.pos); 77 | this.pos += 4; 78 | 79 | }; 80 | 81 | BlrWriter.prototype.addBytes = function (b) { 82 | 83 | this.ensure(b.length); 84 | for (var i = 0, length = b.length; i < length; i++) { 85 | this.buffer.writeUInt8(b[i], this.pos); 86 | this.pos++; 87 | } 88 | }; 89 | 90 | BlrWriter.prototype.addString = function (c, s, encoding) { 91 | this.addByte(c); 92 | 93 | var len = Buffer.byteLength(s, encoding); 94 | if (len > MAX_STRING_SIZE) 95 | throw new Error('blr string is too big'); 96 | 97 | this.ensure(len + 1); 98 | this.buffer.writeUInt8(len, this.pos); 99 | this.pos++; 100 | this.buffer.write(s, this.pos, len, encoding); 101 | this.pos += len; 102 | }; 103 | 104 | BlrWriter.prototype.addBuffer = function (b) { 105 | this.addSmall(b.length); 106 | this.ensure(b.length); 107 | b.copy(this.buffer, this.pos); 108 | this.pos += b.length; 109 | }; 110 | 111 | BlrWriter.prototype.addString2 = function (c, s, encoding) { 112 | this.addByte(c); 113 | 114 | var len = Buffer.byteLength(s, encoding); 115 | if (len > MAX_STRING_SIZE* MAX_STRING_SIZE) 116 | throw new Error('blr string is too big'); 117 | 118 | this.ensure(len + 2); 119 | this.buffer.writeUInt16LE(len, this.pos); 120 | this.pos += 2; 121 | this.buffer.write(s, this.pos, len, encoding); 122 | this.pos += len; 123 | }; 124 | 125 | BlrWriter.prototype.addMultiblockPart = function (c, s, encoding) { 126 | var buff = Buffer.from(s, encoding); 127 | var remaining = buff.length; 128 | var step = 0; 129 | 130 | while (remaining > 0) { 131 | var toWrite = Math.min(remaining, 254); 132 | 133 | this.addByte(c); 134 | this.addByte(toWrite + 1); 135 | this.addByte(step); 136 | 137 | this.ensure(toWrite); 138 | buff.copy(this.buffer, this.pos, step * 254, (step * 254) + toWrite); 139 | 140 | step++; 141 | remaining -= toWrite; 142 | this.pos += toWrite; 143 | } 144 | }; 145 | 146 | /*************************************** 147 | * 148 | * BLR Reader 149 | * 150 | ***************************************/ 151 | 152 | var BlrReader = exports.BlrReader = function(buffer) { 153 | this.buffer = buffer; 154 | this.pos = 0; 155 | }; 156 | 157 | BlrReader.prototype.readByteCode = function(){ 158 | return this.buffer.readUInt8(this.pos++); 159 | }; 160 | 161 | BlrReader.prototype.readInt32 = function () { 162 | var value = this.buffer.readUInt32LE(this.pos); 163 | this.pos += 4; 164 | return value; 165 | } 166 | 167 | BlrReader.prototype.readInt = function(){ 168 | var len = this.buffer.readUInt16LE(this.pos); 169 | this.pos += 2; 170 | var value; 171 | switch (len) { 172 | case 1: 173 | value = this.buffer.readInt8(this.pos); 174 | break; 175 | case 2: 176 | value = this.buffer.readInt16LE(this.pos); 177 | break; 178 | case 4: 179 | value = this.buffer.readInt32LE(this.pos) 180 | } 181 | this.pos += len; 182 | return value; 183 | }; 184 | 185 | BlrReader.prototype.readString = function(encoding){ 186 | 187 | var len = this.buffer.readUInt16LE(this.pos); 188 | var str; 189 | 190 | this.pos += 2; 191 | if (len <= 0) 192 | return ''; 193 | 194 | str = this.buffer.toString(encoding, this.pos, this.pos + len); 195 | this.pos += len; 196 | return str; 197 | }; 198 | 199 | BlrReader.prototype.readSegment = function() { 200 | 201 | var ret, tmp; 202 | var len = this.buffer.readUInt16LE(this.pos); 203 | 204 | this.pos += 2; 205 | 206 | while (len > 0) { 207 | 208 | if (ret) { 209 | tmp = ret; 210 | ret = Buffer.alloc(tmp.length + len); 211 | tmp.copy(ret); 212 | this.buffer.copy(ret, tmp.length, this.pos, this.pos + len); 213 | } else { 214 | ret = Buffer.alloc(len); 215 | this.buffer.copy(ret, 0, this.pos, this.pos + len); 216 | } 217 | 218 | this.pos += len; 219 | 220 | if (this.pos === this.buffer.length) 221 | break; 222 | 223 | len = this.buffer.readUInt16LE(this.pos); 224 | this.pos += 2; 225 | } 226 | 227 | return ret ? ret : Buffer.alloc(0); 228 | }; 229 | 230 | /*************************************** 231 | * 232 | * XDR Writer 233 | * 234 | ***************************************/ 235 | 236 | var XdrWriter = exports.XdrWriter = function(size){ 237 | this.buffer = Buffer.alloc(size || 32); 238 | this.pos = 0; 239 | }; 240 | 241 | XdrWriter.prototype.ensure = BlrWriter.prototype.ensure = function (len) { 242 | var newlen = this.buffer.length; 243 | 244 | while (newlen < this.pos + len) 245 | newlen *= 2 246 | 247 | if (this.buffer.length >= newlen) 248 | return; 249 | 250 | var b = Buffer.alloc(newlen); 251 | this.buffer.copy(b); 252 | delete(this.buffer); 253 | this.buffer = b; 254 | }; 255 | 256 | XdrWriter.prototype.addInt = function (value) { 257 | this.ensure(4); 258 | this.buffer.writeInt32BE(value, this.pos); 259 | this.pos += 4; 260 | }; 261 | 262 | XdrWriter.prototype.addInt64 = function (value) { 263 | this.ensure(8); 264 | var l = Long.fromNumber(value); 265 | this.buffer.writeInt32BE(l.high, this.pos); 266 | this.pos += 4; 267 | this.buffer.writeInt32BE(l.low, this.pos); 268 | this.pos += 4; 269 | }; 270 | 271 | XdrWriter.prototype.addInt128 = function (value) { 272 | this.ensure(16); 273 | 274 | const bigValue = BigInt(value); 275 | 276 | const high = bigValue >> BigInt(64); 277 | const low = bigValue & BigInt("0xFFFFFFFFFFFFFFFF"); 278 | 279 | this.buffer.writeBigUInt64BE(high, this.pos); 280 | this.pos += 8; 281 | this.buffer.writeBigUInt64BE(low, this.pos); 282 | this.pos += 8; 283 | }; 284 | 285 | XdrWriter.prototype.addUInt = function (value) { 286 | this.ensure(4); 287 | this.buffer.writeUInt32BE(value, this.pos); 288 | this.pos += 4; 289 | }; 290 | 291 | XdrWriter.prototype.addString = function(s, encoding) { 292 | var len = Buffer.byteLength(s, encoding); 293 | var alen = align(len); 294 | this.ensure(alen + 4); 295 | this.buffer.writeInt32BE(len, this.pos); 296 | this.pos += 4; 297 | this.buffer.write(s, this.pos, len, encoding); 298 | this.pos += alen; 299 | }; 300 | 301 | XdrWriter.prototype.addText = function(s, encoding) { 302 | var len = Buffer.byteLength(s, encoding); 303 | var alen = align(len); 304 | this.ensure(alen); 305 | this.buffer.write(s, this.pos, len, encoding); 306 | this.pos += alen; 307 | }; 308 | 309 | XdrWriter.prototype.addBlr = function(blr) { 310 | var alen = align(blr.pos); 311 | this.ensure(alen + 4); 312 | this.buffer.writeInt32BE(blr.pos, this.pos); 313 | this.pos += 4; 314 | blr.buffer.copy(this.buffer, this.pos); 315 | this.pos += alen; 316 | }; 317 | 318 | XdrWriter.prototype.getData = function() { 319 | return this.buffer.slice(0, this.pos); 320 | }; 321 | 322 | XdrWriter.prototype.addDouble = function(value) { 323 | this.ensure(8); 324 | this.buffer.writeDoubleBE(value, this.pos); 325 | this.pos += 8; 326 | }; 327 | 328 | XdrWriter.prototype.addQuad = function(quad) { 329 | this.ensure(8); 330 | var b = this.buffer; 331 | b.writeInt32BE(quad.high, this.pos); 332 | this.pos += 4; 333 | b.writeInt32BE(quad.low, this.pos); 334 | this.pos += 4; 335 | }; 336 | 337 | XdrWriter.prototype.addBuffer = function(buffer) { 338 | this.ensure(buffer.length); 339 | buffer.copy(this.buffer, this.pos, 0, buffer.length); 340 | this.pos += buffer.length; 341 | } 342 | 343 | XdrWriter.prototype.addAlignment = function(len) { 344 | var alen = (4 - len) & 3; 345 | 346 | this.ensure(alen); 347 | this.buffer.write('ffffff', this.pos, alen, 'hex'); 348 | this.pos += alen; 349 | } 350 | 351 | /*************************************** 352 | * 353 | * XDR Reader 354 | * 355 | ***************************************/ 356 | 357 | var XdrReader = exports.XdrReader = function(buffer){ 358 | this.buffer = buffer; 359 | this.pos = 0; 360 | }; 361 | 362 | XdrReader.prototype.readInt = function () { 363 | var r = this.buffer.readInt32BE(this.pos); 364 | this.pos += 4; 365 | return r; 366 | }; 367 | 368 | XdrReader.prototype.readUInt = function () { 369 | var r = this.buffer.readUInt32BE(this.pos); 370 | this.pos += 4; 371 | return r; 372 | }; 373 | 374 | XdrReader.prototype.readInt64 = function () { 375 | var high = this.buffer.readInt32BE(this.pos); 376 | this.pos += 4; 377 | var low = this.buffer.readInt32BE(this.pos); 378 | this.pos += 4; 379 | return new Long(low, high).toNumber(); 380 | }; 381 | 382 | XdrReader.prototype.readInt128 = function () { 383 | var high = this.buffer.readBigUInt64BE(this.pos) 384 | this.pos += 8 385 | 386 | var low = this.buffer.readBigUInt64BE(this.pos) 387 | this.pos += 8 388 | 389 | return (BigInt(high) << BigInt(64)) + BigInt(low) 390 | }; 391 | 392 | XdrReader.prototype.readShort = function () { 393 | var r = this.buffer.readInt16BE(this.pos); 394 | this.pos += 2; 395 | return r; 396 | }; 397 | 398 | XdrReader.prototype.readQuad = function () { 399 | var b = this.buffer; 400 | var high = b.readInt32BE(this.pos); 401 | this.pos += 4; 402 | var low = b.readInt32BE(this.pos); 403 | this.pos += 4; 404 | return {low: low, high: high} 405 | }; 406 | 407 | XdrReader.prototype.readFloat = function () { 408 | var r = this.buffer.readFloatBE(this.pos); 409 | this.pos += 4; 410 | return r; 411 | }; 412 | 413 | XdrReader.prototype.readDouble = function () { 414 | var r = this.buffer.readDoubleBE(this.pos); 415 | this.pos += 8; 416 | return r; 417 | }; 418 | 419 | XdrReader.prototype.readArray = function () { 420 | var len = this.readInt(); 421 | if (!len) 422 | return; 423 | var r = this.buffer.slice(this.pos, this.pos + len); 424 | this.pos += align(len); 425 | return r; 426 | }; 427 | 428 | XdrReader.prototype.readBuffer = function (len, toAlign = true) { 429 | if (!arguments.length) { 430 | len = this.readInt(); 431 | } 432 | 433 | if (len !== null && len !== undefined) { 434 | 435 | if (len <= 0){ 436 | return Buffer.alloc(0); 437 | } 438 | 439 | var r = this.buffer.slice(this.pos, this.pos + len); 440 | this.pos += toAlign ? align(len) : len; 441 | return r; 442 | } 443 | }; 444 | 445 | XdrReader.prototype.readString = function(encoding) { 446 | var len = this.readInt(); 447 | return this.readText(len, encoding); 448 | }; 449 | 450 | XdrReader.prototype.readText = function(len, encoding) { 451 | if (len <= 0) 452 | return ''; 453 | 454 | var r = this.buffer.toString(encoding, this.pos, this.pos + len); 455 | this.pos += align(len); 456 | return r; 457 | }; 458 | 459 | /*************************************** 460 | * 461 | * BitSet 462 | * 463 | ***************************************/ 464 | var WORD_LOG = 5; 465 | var BUFFER_BITS = 8; 466 | var BIT_ON = 1; 467 | var BIT_OFF = 0; 468 | 469 | var BitSet = exports.BitSet = function(buffer) { 470 | this.data = []; 471 | 472 | if (buffer) { 473 | this.scale(buffer.length * BUFFER_BITS); 474 | 475 | for (var i = 0; i < buffer.length; i++) { 476 | var n = buffer[i]; 477 | 478 | for (var j = 0; j < BUFFER_BITS; j++) { 479 | var k = i * BUFFER_BITS + j; 480 | this.data[k >>> WORD_LOG] |= (n >> j & BIT_ON) << k; 481 | } 482 | } 483 | } 484 | }; 485 | 486 | BitSet.prototype.scale = function(index) { 487 | var l = index >>> WORD_LOG; 488 | 489 | for (var i = this.data.length; l >= i; l--) { 490 | this.data.push(BIT_OFF); 491 | } 492 | }; 493 | 494 | BitSet.prototype.set = function(index, value) { 495 | let pos = index >>> 3; 496 | 497 | for (let i = this.data.length; pos >= i; pos--) { 498 | this.data.push(BIT_OFF); 499 | } 500 | 501 | pos = index >>> 3; 502 | 503 | if (value === undefined || value) { 504 | this.data[pos] |= (1 << (index % BUFFER_BITS)); 505 | } else { 506 | this.data[pos] &= ~(1 << (index % BUFFER_BITS)); 507 | } 508 | }; 509 | 510 | BitSet.prototype.get = function(index) { 511 | var n = index >>> WORD_LOG; 512 | 513 | if (n >= this.data.length) { 514 | return BIT_OFF; 515 | } 516 | 517 | return (this.data[n] >>> index) & BIT_ON; 518 | }; 519 | 520 | BitSet.prototype.toBuffer = function() { 521 | return Buffer.from(this.data); 522 | }; 523 | -------------------------------------------------------------------------------- /lib/wire/socket.js: -------------------------------------------------------------------------------- 1 | const net = require("net"); 2 | const zlib = require("zlib"); 3 | 4 | /** 5 | * Socket proxy. 6 | */ 7 | class Socket { 8 | constructor(port, host) { 9 | this._socket = net.createConnection(port, host); 10 | this._socket.setNoDelay(true); 11 | this.compressor = null; 12 | this.compressorBuffer = []; 13 | this.decompressor = null; 14 | this.decompressorBuffer = []; 15 | this.buffer = null; 16 | 17 | return new Proxy(this._socket, this); 18 | } 19 | 20 | /** 21 | * Decompress data when receive it if compression is enabled. 22 | * Override on data event. 23 | */ 24 | on(event, cb) { 25 | if (event === 'data') { 26 | const mainCb = cb; 27 | cb = (data) => { 28 | if (this.compress) { 29 | this.decompressor.write(data, () => { 30 | mainCb(Buffer.concat(this.decompressorBuffer)); 31 | this.decompressorBuffer = []; // Reset buffer 32 | }); 33 | } else { 34 | mainCb(data); 35 | } 36 | }; 37 | } 38 | 39 | this._socket.on(event, cb); 40 | } 41 | 42 | /** 43 | * Compress data before sending to socket if compression is enabled. 44 | */ 45 | write(data, defer = false) { 46 | if (defer) { 47 | this.buffer = Buffer.from(data); 48 | return; 49 | } 50 | 51 | if (!defer && this.buffer) { 52 | data = Buffer.concat([this.buffer, data]); 53 | this.buffer = null; 54 | } 55 | 56 | if (this.compress) { 57 | this.compressor.write(data, () => { 58 | this._socket.write(Buffer.concat(this.compressorBuffer)); 59 | this.compressorBuffer = []; // Reset buffer 60 | }); 61 | } else { 62 | this._socket.write(data); 63 | } 64 | } 65 | 66 | /** 67 | * Enable compression/decompression on the fly. 68 | */ 69 | enableCompression() { 70 | this.compress = true; 71 | 72 | // Create decompressor instance 73 | this.decompressor = zlib.createInflate(); 74 | this.decompressor.on('data', (inflate) => { 75 | this.decompressorBuffer.push(inflate); 76 | }); 77 | 78 | // Create compressor instance 79 | this.compressor = zlib.createDeflate({ 80 | flush: zlib.constants.Z_FULL_FLUSH, 81 | finishFlush: zlib.constants.Z_SYNC_FLUSH 82 | }); 83 | this.compressor.on('data', (deflate) => { 84 | this.compressorBuffer.push(deflate); 85 | }); 86 | } 87 | 88 | /** 89 | * Proxy trap. 90 | */ 91 | get(target, field) { 92 | if (field in this) { 93 | return this[field].bind(this); 94 | } 95 | 96 | return target[field]; 97 | } 98 | } 99 | 100 | module.exports = Socket; 101 | -------------------------------------------------------------------------------- /lib/wire/statement.js: -------------------------------------------------------------------------------- 1 | /*************************************** 2 | * 3 | * Statement 4 | * 5 | ***************************************/ 6 | 7 | function Statement(connection) { 8 | this.connection = connection; 9 | } 10 | 11 | Statement.prototype.close = function(callback) { 12 | this.connection.closeStatement(this, callback); 13 | }; 14 | 15 | Statement.prototype.drop = function(callback) { 16 | this.connection.dropStatement(this, callback); 17 | }; 18 | 19 | Statement.prototype.release = function(callback) { 20 | var cache_query = this.connection.getCachedQuery(this.query); 21 | if (cache_query) 22 | this.connection.closeStatement(this, callback); 23 | else 24 | this.connection.dropStatement(this, callback); 25 | }; 26 | 27 | Statement.prototype.execute = function(transaction, params, callback, custom) { 28 | 29 | if (params instanceof Function) { 30 | custom = callback; 31 | callback = params; 32 | params = undefined; 33 | } 34 | 35 | this.custom = custom; 36 | this.connection.executeStatement(transaction, this, params, callback, custom); 37 | }; 38 | 39 | Statement.prototype.fetch = function(transaction, count, callback) { 40 | this.connection.fetch(this, transaction, count, callback); 41 | }; 42 | 43 | Statement.prototype.fetchAll = function(transaction, callback) { 44 | this.connection.fetchAll(this, transaction, callback); 45 | }; 46 | 47 | module.exports = Statement; 48 | -------------------------------------------------------------------------------- /lib/wire/transaction.js: -------------------------------------------------------------------------------- 1 | const {doCallback, doError} = require('../callback'); 2 | const Const = require('./const'); 3 | 4 | /*************************************** 5 | * 6 | * Transaction 7 | * 8 | ***************************************/ 9 | 10 | function Transaction(connection) { 11 | this.connection = connection; 12 | this.db = connection.db; 13 | } 14 | 15 | Transaction.prototype.newStatement = function(query, callback) { 16 | var cnx = this.connection; 17 | var self = this; 18 | var query_cache = cnx.getCachedQuery(query); 19 | 20 | if (query_cache) { 21 | callback(null, query_cache); 22 | } else { 23 | cnx.prepare(self, query, false, callback); 24 | } 25 | }; 26 | 27 | Transaction.prototype.execute = function(query, params, callback, custom) { 28 | 29 | if (params instanceof Function) { 30 | custom = callback; 31 | callback = params; 32 | params = undefined; 33 | } 34 | 35 | var self = this; 36 | this.newStatement(query, function(err, statement) { 37 | 38 | if (err) { 39 | doError(err, callback); 40 | return; 41 | } 42 | 43 | function dropError(err) { 44 | statement.release(); 45 | doCallback(err, callback); 46 | } 47 | 48 | statement.execute(self, params, function(err, ret) { 49 | if (err) { 50 | dropError(err); 51 | return; 52 | } 53 | 54 | switch (statement.type) { 55 | case Const.isc_info_sql_stmt_select: 56 | statement.fetchAll(self, function(err, r) { 57 | if (err) { 58 | dropError(err); 59 | return; 60 | } 61 | 62 | statement.release(); 63 | 64 | if (callback) 65 | callback(undefined, r, statement.output, true); 66 | 67 | }); 68 | 69 | break; 70 | 71 | case Const.isc_info_sql_stmt_exec_procedure: 72 | if (ret && ret.data && ret.data.length > 0) { 73 | statement.release(); 74 | 75 | if (callback) 76 | callback(undefined, ret.data[0], statement.output, true); 77 | 78 | break; 79 | } else if (statement.output.length) { 80 | statement.fetch(self, 1, function(err, ret) { 81 | if (err) { 82 | dropError(err); 83 | return; 84 | } 85 | 86 | statement.release(); 87 | 88 | if (callback) 89 | callback(undefined, ret.data[0], statement.output, false); 90 | }); 91 | 92 | break; 93 | } 94 | 95 | // Fall through is normal 96 | default: 97 | statement.release(); 98 | if (callback) 99 | callback() 100 | break; 101 | } 102 | 103 | }, custom); 104 | }); 105 | }; 106 | 107 | Transaction.prototype.sequentially = function (query, params, on, callback, asArray) { 108 | 109 | if (params instanceof Function) { 110 | asArray = callback; 111 | callback = on; 112 | on = params; 113 | params = undefined; 114 | } 115 | 116 | if (on === undefined){ 117 | throw new Error('Expected "on" delegate.'); 118 | } 119 | 120 | if (callback instanceof Boolean) { 121 | asArray = callback; 122 | callback = undefined; 123 | } 124 | 125 | var self = this; 126 | self.execute(query, params, callback, { asObject: !asArray, asStream: true, on: on }); 127 | return self; 128 | }; 129 | 130 | Transaction.prototype.query = function(query, params, callback) { 131 | 132 | if (params instanceof Function) { 133 | callback = params; 134 | params = undefined; 135 | } 136 | 137 | if (callback === undefined) 138 | callback = noop; 139 | 140 | this.execute(query, params, callback, { asObject: true, asStream: callback === undefined || callback === null }); 141 | 142 | }; 143 | 144 | Transaction.prototype.commit = function(callback) { 145 | this.connection.commit(this, callback); 146 | }; 147 | 148 | Transaction.prototype.rollback = function(callback) { 149 | this.connection.rollback(this, callback); 150 | }; 151 | 152 | Transaction.prototype.commitRetaining = function(callback) { 153 | this.connection.commitRetaining(this, callback); 154 | }; 155 | 156 | Transaction.prototype.rollbackRetaining = function(callback) { 157 | this.connection.rollbackRetaining(this, callback); 158 | }; 159 | 160 | module.exports = Transaction; 161 | -------------------------------------------------------------------------------- /lib/wire/xsqlvar.js: -------------------------------------------------------------------------------- 1 | const Const= require('./const'); 2 | 3 | /*************************************** 4 | * 5 | * SQLVar 6 | * 7 | ***************************************/ 8 | 9 | const 10 | ScaleDivisor = [1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000,10000000000, 100000000000,1000000000000,10000000000000,100000000000000,1000000000000000]; 11 | const 12 | DateOffset = 40587, 13 | TimeCoeff = 86400000, 14 | MsPerMinute = 60000; 15 | 16 | //------------------------------------------------------ 17 | 18 | function SQLVarText() {} 19 | 20 | SQLVarText.prototype.decode = function(data, lowerV13) { 21 | let ret; 22 | if (this.subType > 1) { 23 | // ToDo: with column charset 24 | ret = data.readText(this.length, Const.DEFAULT_ENCODING); 25 | } else if (this.subType === 0) { 26 | // without charset definition 27 | ret = data.readText(this.length, Const.DEFAULT_ENCODING); 28 | } else { 29 | ret = data.readBuffer(this.length); 30 | } 31 | 32 | if (!lowerV13 || !data.readInt()) { 33 | return ret; 34 | } 35 | 36 | return null; 37 | }; 38 | 39 | SQLVarText.prototype.calcBlr = function(blr) { 40 | blr.addByte(Const.blr_text); 41 | blr.addWord(this.length); 42 | }; 43 | 44 | //------------------------------------------------------ 45 | 46 | function SQLVarNull() {} 47 | SQLVarNull.prototype = new SQLVarText(); 48 | SQLVarNull.prototype.constructor = SQLVarNull; 49 | 50 | //------------------------------------------------------ 51 | 52 | function SQLVarString() {} 53 | 54 | SQLVarString.prototype.decode = function(data, lowerV13) { 55 | let ret; 56 | if (this.subType > 1) { 57 | // ToDo: with column charset 58 | ret = data.readString(Const.DEFAULT_ENCODING); 59 | } else if (this.subType === 0) { 60 | // without charset definition 61 | ret = data.readString(Const.DEFAULT_ENCODING); 62 | } else { 63 | ret = data.readBuffer(); 64 | } 65 | 66 | if (!lowerV13 || !data.readInt()) { 67 | return ret; 68 | } 69 | 70 | return null; 71 | }; 72 | 73 | SQLVarString.prototype.calcBlr = function(blr) { 74 | blr.addByte(Const.blr_varying); 75 | blr.addWord(this.length); 76 | }; 77 | 78 | //------------------------------------------------------ 79 | 80 | function SQLVarQuad() {} 81 | 82 | SQLVarQuad.prototype.decode = function(data, lowerV13) { 83 | var ret = data.readQuad(); 84 | 85 | if (!lowerV13 || !data.readInt()) { 86 | return ret; 87 | } 88 | return null; 89 | }; 90 | 91 | SQLVarQuad.prototype.calcBlr = function(blr) { 92 | blr.addByte(Const.blr_quad); 93 | blr.addShort(this.scale); 94 | }; 95 | 96 | //------------------------------------------------------ 97 | 98 | function SQLVarBlob() {} 99 | SQLVarBlob.prototype = new SQLVarQuad(); 100 | SQLVarBlob.prototype.constructor = SQLVarBlob; 101 | 102 | SQLVarBlob.prototype.calcBlr = function(blr) { 103 | blr.addByte(Const.blr_quad); 104 | blr.addShort(0); 105 | }; 106 | 107 | //------------------------------------------------------ 108 | 109 | function SQLVarArray() {} 110 | SQLVarArray.prototype = new SQLVarQuad(); 111 | SQLVarArray.prototype.constructor = SQLVarArray; 112 | 113 | SQLVarArray.prototype.calcBlr = function(blr) { 114 | blr.addByte(Const.blr_quad); 115 | blr.addShort(0); 116 | }; 117 | 118 | //------------------------------------------------------ 119 | 120 | function SQLVarInt() {} 121 | 122 | SQLVarInt.prototype.decode = function(data, lowerV13) { 123 | var ret = data.readInt(); 124 | 125 | if (this.scale) { 126 | ret = ret / ScaleDivisor[Math.abs(this.scale)]; 127 | } 128 | 129 | if (!lowerV13 || !data.readInt()) { 130 | return ret; 131 | } 132 | 133 | return null; 134 | }; 135 | 136 | SQLVarInt.prototype.calcBlr = function(blr) { 137 | blr.addByte(Const.blr_long); 138 | blr.addShort(this.scale); 139 | }; 140 | 141 | //------------------------------------------------------ 142 | 143 | function SQLVarShort() {} 144 | SQLVarShort.prototype = new SQLVarInt(); 145 | SQLVarShort.prototype.constructor = SQLVarShort; 146 | 147 | SQLVarShort.prototype.calcBlr = function(blr) { 148 | blr.addByte(Const.blr_short); 149 | blr.addShort(this.scale); 150 | }; 151 | 152 | //------------------------------------------------------ 153 | 154 | function SQLVarInt64() {} 155 | 156 | SQLVarInt64.prototype.decode = function(data, lowerV13) { 157 | var ret = data.readInt64(); 158 | 159 | if (this.scale) { 160 | ret = ret / ScaleDivisor[Math.abs(this.scale)]; 161 | } 162 | 163 | if (!lowerV13 || !data.readInt()) { 164 | return ret; 165 | } 166 | return null; 167 | }; 168 | 169 | SQLVarInt64.prototype.calcBlr = function(blr) { 170 | blr.addByte(Const.blr_int64); 171 | blr.addShort(this.scale); 172 | }; 173 | 174 | //------------------------------------------------------ 175 | 176 | function SQLVarInt128() {} 177 | 178 | SQLVarInt128.prototype.decode = function (data, lowerV13) { 179 | var retBigInt = BigInt(data.readInt128()) 180 | 181 | if (retBigInt > BigInt(Number.MAX_SAFE_INTEGER)) { 182 | var ret = retBigInt.toString(); 183 | 184 | var integerPart = ret.slice(0, Math.abs(this.scale) * -1) 185 | var decimalPart = ret.slice(Math.abs(this.scale) * -1) 186 | 187 | if (integerPart === '') integerPart = '0' 188 | 189 | ret = `${integerPart}.${decimalPart}` 190 | } else { 191 | var ret = Number(retBigInt); 192 | ret = ret / ScaleDivisor[Math.abs(this.scale)]; 193 | } 194 | 195 | if (!lowerV13 || !data.readInt()) { 196 | return ret; 197 | } 198 | 199 | return null; 200 | }; 201 | 202 | SQLVarInt128.prototype.calcBlr = function (blr) { 203 | blr.addByte(Const.blr_int128); 204 | blr.addShort(this.scale); 205 | }; 206 | 207 | //------------------------------------------------------ 208 | 209 | function SQLVarFloat() { } 210 | 211 | SQLVarFloat.prototype.decode = function(data, lowerV13) { 212 | var ret = data.readFloat(); 213 | 214 | if (!lowerV13 || !data.readInt()) { 215 | return ret; 216 | } 217 | 218 | return null; 219 | }; 220 | 221 | SQLVarFloat.prototype.calcBlr = function(blr) { 222 | blr.addByte(Const.blr_float); 223 | }; 224 | 225 | //------------------------------------------------------ 226 | 227 | function SQLVarDouble() {} 228 | 229 | SQLVarDouble.prototype.decode = function(data, lowerV13) { 230 | var ret = data.readDouble(); 231 | 232 | if (!lowerV13 || !data.readInt()) { 233 | return ret; 234 | } 235 | 236 | return null; 237 | }; 238 | 239 | SQLVarDouble.prototype.calcBlr = function(blr) { 240 | blr.addByte(Const.blr_double); 241 | }; 242 | 243 | //------------------------------------------------------ 244 | 245 | function SQLVarDate() {} 246 | 247 | SQLVarDate.prototype.decode = function(data, lowerV13) { 248 | var ret = data.readInt(); 249 | 250 | if (!lowerV13 || !data.readInt()) { 251 | var d = new Date(0); 252 | d.setMilliseconds((ret - DateOffset) * TimeCoeff + d.getTimezoneOffset() * MsPerMinute); 253 | return d; 254 | } 255 | 256 | return null; 257 | }; 258 | 259 | SQLVarDate.prototype.calcBlr = function(blr) { 260 | blr.addByte(Const.blr_sql_date); 261 | }; 262 | 263 | //------------------------------------------------------ 264 | 265 | function SQLVarTime() {} 266 | 267 | SQLVarTime.prototype.decode = function(data, lowerV13) { 268 | var ret = data.readUInt(); 269 | 270 | if (!lowerV13 || !data.readInt()) { 271 | var d = new Date(0); 272 | d.setMilliseconds(Math.floor(ret / 10) + d.getTimezoneOffset() * MsPerMinute); 273 | return d; 274 | } 275 | return null; 276 | }; 277 | 278 | SQLVarTime.prototype.calcBlr = function(blr) { 279 | blr.addByte(Const.blr_sql_time); 280 | }; 281 | 282 | //------------------------------------------------------ 283 | 284 | function SQLVarTimeStamp() {} 285 | 286 | SQLVarTimeStamp.prototype.decode = function(data, lowerV13) { 287 | var date = data.readInt(); 288 | var time = data.readUInt(); 289 | 290 | if (!lowerV13 || !data.readInt()) { 291 | var d = new Date(0); 292 | d.setMilliseconds((date - DateOffset) * TimeCoeff + Math.floor(time / 10) + d.getTimezoneOffset() * MsPerMinute); 293 | return d; 294 | } 295 | 296 | return null; 297 | }; 298 | 299 | SQLVarTimeStamp.prototype.calcBlr = function(blr) { 300 | blr.addByte(Const.blr_timestamp); 301 | }; 302 | 303 | //------------------------------------------------------ 304 | 305 | function SQLVarBoolean() {} 306 | 307 | SQLVarBoolean.prototype.decode = function(data, lowerV13) { 308 | var ret = data.readInt(); 309 | 310 | if (!lowerV13 || !data.readInt()) { 311 | return Boolean(ret); 312 | } 313 | return null; 314 | }; 315 | 316 | SQLVarBoolean.prototype.calcBlr = function(blr) { 317 | blr.addByte(Const.blr_bool); 318 | }; 319 | 320 | //------------------------------------------------------ 321 | 322 | function SQLParamInt(value){ 323 | this.value = value; 324 | } 325 | 326 | SQLParamInt.prototype.calcBlr = function(blr) { 327 | blr.addByte(Const.blr_long); 328 | blr.addShort(0); 329 | }; 330 | 331 | SQLParamInt.prototype.encode = function(data) { 332 | if (this.value != null) { 333 | data.addInt(this.value); 334 | } else { 335 | data.addInt(0); 336 | data.addInt(1); 337 | } 338 | }; 339 | 340 | //------------------------------------------------------ 341 | 342 | function SQLParamInt64(value){ 343 | this.value = value; 344 | } 345 | 346 | SQLParamInt64.prototype.calcBlr = function(blr) { 347 | blr.addByte(Const.blr_int64); 348 | blr.addShort(0); 349 | }; 350 | 351 | SQLParamInt64.prototype.encode = function(data) { 352 | if (this.value != null) { 353 | data.addInt64(this.value); 354 | } else { 355 | data.addInt64(0); 356 | data.addInt(1); 357 | } 358 | }; 359 | 360 | //------------------------------------------------------ 361 | 362 | function SQLParamInt128(value) { 363 | this.value = value; 364 | } 365 | 366 | SQLParamInt128.prototype.calcBlr = function (blr) { 367 | blr.addByte(Const.blr_int128); 368 | blr.addShort(0); 369 | }; 370 | 371 | SQLParamInt128.prototype.encode = function (data) { 372 | if (this.value != null) { 373 | data.addInt128(this.value); 374 | } else { 375 | data.addInt128(0); 376 | data.addInt(1); 377 | } 378 | }; 379 | 380 | //------------------------------------------------------ 381 | 382 | function SQLParamDouble(value) { 383 | this.value = value; 384 | } 385 | 386 | SQLParamDouble.prototype.encode = function(data) { 387 | if (this.value != null) { 388 | data.addDouble(this.value); 389 | } else { 390 | data.addDouble(0); 391 | data.addInt(1); 392 | } 393 | }; 394 | 395 | SQLParamDouble.prototype.calcBlr = function(blr) { 396 | blr.addByte(Const.blr_double); 397 | }; 398 | 399 | //------------------------------------------------------ 400 | 401 | function SQLParamString(value) { 402 | this.value = value; 403 | } 404 | 405 | SQLParamString.prototype.encode = function(data) { 406 | if (this.value != null) { 407 | data.addText(this.value, Const.DEFAULT_ENCODING); 408 | } else { 409 | data.addInt(1); 410 | } 411 | }; 412 | 413 | SQLParamString.prototype.calcBlr = function(blr) { 414 | blr.addByte(Const.blr_text); 415 | var len = this.value ? Buffer.byteLength(this.value, Const.DEFAULT_ENCODING) : 0; 416 | blr.addWord(len); 417 | }; 418 | 419 | //------------------------------------------------------ 420 | 421 | function SQLParamQuad(value) { 422 | this.value = value; 423 | } 424 | 425 | SQLParamQuad.prototype.encode = function(data) { 426 | if (this.value != null) { 427 | data.addInt(this.value.high); 428 | data.addInt(this.value.low); 429 | } else { 430 | data.addInt(0); 431 | data.addInt(0); 432 | data.addInt(1); 433 | } 434 | }; 435 | 436 | SQLParamQuad.prototype.calcBlr = function(blr) { 437 | blr.addByte(Const.blr_quad); 438 | blr.addShort(0); 439 | }; 440 | 441 | //------------------------------------------------------ 442 | 443 | function SQLParamDate(value) { 444 | this.value = value; 445 | } 446 | 447 | SQLParamDate.prototype.encode = function(data) { 448 | if (this.value != null) { 449 | 450 | var value = this.value.getTime() - this.value.getTimezoneOffset() * MsPerMinute; 451 | var time = value % TimeCoeff; 452 | var date = (value - time) / TimeCoeff + DateOffset; 453 | time *= 10; 454 | 455 | // check overflow 456 | if (time < 0) { 457 | date--; 458 | time = TimeCoeff*10 + time; 459 | } 460 | 461 | data.addInt(date); 462 | data.addUInt(time); 463 | } else { 464 | data.addInt(0); 465 | data.addUInt(0); 466 | data.addInt(1); 467 | } 468 | }; 469 | 470 | SQLParamDate.prototype.calcBlr = function(blr) { 471 | blr.addByte(Const.blr_timestamp); 472 | }; 473 | 474 | //------------------------------------------------------ 475 | 476 | function SQLParamBool(value) { 477 | this.value = value; 478 | } 479 | 480 | SQLParamBool.prototype.encode = function(data) { 481 | if (this.value != null) { 482 | data.addInt(this.value ? 1 : 0); 483 | } else { 484 | data.addInt(0); 485 | data.addInt(1); 486 | } 487 | }; 488 | 489 | SQLParamBool.prototype.calcBlr = function(blr) { 490 | blr.addByte(Const.blr_short); 491 | blr.addShort(0); 492 | }; 493 | 494 | module.exports = { 495 | SQLVarArray, 496 | SQLVarDate, 497 | SQLVarBlob, 498 | SQLVarBoolean, 499 | SQLVarDouble, 500 | SQLVarInt, 501 | SQLVarInt64, 502 | SQLVarInt128, 503 | SQLVarFloat, 504 | SQLVarNull, 505 | SQLVarShort, 506 | SQLVarString, 507 | SQLVarText, 508 | SQLVarTime, 509 | SQLVarTimeStamp, 510 | SQLParamBool, 511 | SQLParamDate, 512 | SQLParamDouble, 513 | SQLParamInt, 514 | SQLParamInt64, 515 | SQLParamInt128, 516 | SQLParamQuad, 517 | SQLParamString, 518 | }; 519 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-firebird", 3 | "version": "1.1.9", 4 | "description": "Pure JavaScript and Asynchronous Firebird client for Node.js.", 5 | "keywords": [ 6 | "firebird", 7 | "database", 8 | "rdbms", 9 | "sql" 10 | ], 11 | "homepage": "https://github.com/hgourvest/node-firebird", 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/hgourvest/node-firebird.git" 15 | }, 16 | "author": { 17 | "name": "Henri Gourvest", 18 | "email": "hgourvest@gmail.com" 19 | }, 20 | "contributors": [ 21 | "Popa Marius Adrian ", 22 | "Peter Širka " 23 | ], 24 | "main": "./lib", 25 | "types": "./lib/index.d.ts", 26 | "license": "MPL-2.0", 27 | "scripts": { 28 | "test": "mocha" 29 | }, 30 | "dependencies": { 31 | "big-integer": "^1.6.51", 32 | "long": "^5.2.3" 33 | }, 34 | "devDependencies": { 35 | "coveralls-next": "^4.2.0", 36 | "mocha": "^10.2.0", 37 | "nyc": "^15.0.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const currentDate = new Date(); 4 | const testDir = path.resolve(__dirname); 5 | const dbName = 'test-' + currentDate.getTime() + '.fdb'; 6 | 7 | exports.default = { 8 | database: path.join(process.env.FIREBIRD_DATA || testDir, dbName), 9 | host: '127.0.0.1', 10 | port: 3050, 11 | user: 'SYSDBA', 12 | password: 'masterkey', 13 | role: null, 14 | pageSize: 4096, 15 | timeout: 3000, 16 | lowercase_keys: true, 17 | retryConnectionInterval: 100, 18 | wireCompression: false, 19 | }; 20 | 21 | exports.currentDate = currentDate; 22 | exports.testDir = testDir; 23 | 24 | exports.extends = function(base, args) { 25 | return Object.assign({}, base, args); 26 | } 27 | 28 | -------------------------------------------------------------------------------- /test/fbtrace-2.conf: -------------------------------------------------------------------------------- 1 | 2 | enabled true 3 | log_connections true 4 | log_transactions true 5 | 6 | 7 | 8 | enabled false 9 | 10 | -------------------------------------------------------------------------------- /test/fbtrace-3.conf: -------------------------------------------------------------------------------- 1 | database 2 | { 3 | enabled = true 4 | log_statements = true 5 | log_transactions = true 6 | } 7 | 8 | database = security2.fdb 9 | { 10 | enabled = false 11 | } 12 | -------------------------------------------------------------------------------- /test/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hgourvest/node-firebird/e1c4dd963bb7bea0568a083b9f1013fc70b1776f/test/image.png -------------------------------------------------------------------------------- /test/service.js: -------------------------------------------------------------------------------- 1 | const Firebird = require('../lib/index.js'); 2 | const Config = require('./config'); 3 | 4 | const assert = require('assert'); 5 | const stream = require('stream'); 6 | const path = require('path'); 7 | const fs = require('fs'); 8 | 9 | const config = Object.assign({}, Config.default, {manager: true}); 10 | const REMOVE_DB = false; 11 | 12 | function readStream(s, cb) { 13 | var result = ''; 14 | 15 | s.on('data', chunk => result += chunk.toString() + '\n'); 16 | s.on('end', () => cb(result)); 17 | } 18 | 19 | describe('Test Service', () => { 20 | const DATABASE = Config.default; 21 | 22 | // Create DB before tests 23 | before(done => { 24 | Firebird.attachOrCreate(DATABASE, (err, db) => { 25 | assert.ok(!err, err); 26 | db.detach(done); 27 | }); 28 | }); 29 | 30 | // Remove DB and backup files after tests 31 | after(done => { 32 | if (!REMOVE_DB) { 33 | done(); 34 | return; 35 | } 36 | 37 | fs.readdir(path.resolve(__dirname), (err, fileNames) => { 38 | if (err) throw err; 39 | 40 | for (const name of fileNames) { 41 | if (name.toLowerCase().indexOf('.fdb') > -1 || name.toLowerCase().indexOf('.fbk') > -1) { 42 | fs.unlinkSync(path.resolve(__dirname, name)); 43 | } 44 | } 45 | done(); 46 | }); 47 | }); 48 | 49 | it('should attach', done => { 50 | Firebird.attach(config, (err, srv) => { 51 | assert.ok(!err, err); 52 | srv.detach(done); 53 | }); 54 | }); 55 | 56 | describe('Server info', () => { 57 | it('should get all server infos', done => { 58 | Firebird.attach(config, (err, srv) => { 59 | assert.ok(!err, err); 60 | 61 | srv.getFbserverInfos([], {}, (err, data) => { 62 | assert.ok(!err, err); 63 | 64 | assert.ok(data.dbinfo, 'Db info not found'); 65 | assert.ok(data.dbinfo.database, 'Db info database not found'); 66 | assert.ok(data.dbinfo.nbattachment != null, 'Db info nb attachment not found'); 67 | assert.ok(data.dbinfo.nbdatabase != null, 'Db info nb database not found'); 68 | assert.ok(data.svcversion, 'Svc version not found'); 69 | assert.ok(data.fbversion, 'Fb version not found'); 70 | assert.ok(data.fbimplementation, 'Fb implementation not found'); 71 | assert.ok(data.fbcapatibilities, 'Fb capatibilities not found'); 72 | assert.ok(data.pathsecuritydb, 'Fb security DB path not found'); 73 | assert.ok(data.fbenv, 'Fb environment not found'); 74 | assert.ok(data.fbenvlock, 'Fb lock environment not found'); 75 | assert.ok(data.fbenvmsg, 'Fb messages environment not found'); 76 | srv.detach(done); 77 | }); 78 | }); 79 | }); 80 | 81 | it('should get one server info', done => { 82 | Firebird.attach(config, (err, srv) => { 83 | assert.ok(!err, err); 84 | 85 | srv.getFbserverInfos({dbinfo: true}, {}, (err, data) => { 86 | assert.ok(!err, err); 87 | assert.ok(data.dbinfo, 'Db Info not found') 88 | assert.ok(!data.fbversion, 'FB version found (must not)'); 89 | 90 | srv.detach(done); 91 | }); 92 | }); 93 | }); 94 | }); 95 | 96 | describe('Server stats and logs', () => { 97 | it('should get stats', done => { 98 | Firebird.attach(config, (err, srv) => { 99 | assert.ok(!err, err); 100 | 101 | srv.getStats({}, (err, data) => { 102 | assert.ok(!err, err); 103 | 104 | readStream(data, result => { 105 | assert.ok(result.indexOf('Gstat execution time') > -1, '"Gstat execution time" text not found') 106 | 107 | srv.detach(done); 108 | }); 109 | }); 110 | }); 111 | }); 112 | 113 | it('should get logs', done => { 114 | Firebird.attach(config, (err, srv) => { 115 | assert.ok(!err, err); 116 | 117 | srv.getLog({}, (err, data) => { 118 | assert.ok(!err, err); 119 | 120 | readStream(data, result => { 121 | assert.equal(typeof result, 'string'); 122 | 123 | srv.detach(done); 124 | }); 125 | }); 126 | }); 127 | }); 128 | }); 129 | 130 | describe('Server properties', () => { 131 | // Add delay for skip error : Service is currently busy // TODO better srv.detach ? 132 | beforeEach(done => setTimeout(done, 100)); 133 | 134 | it('should set dialect', done => { 135 | testProperty( 136 | 'setDialect', [DATABASE.database, 1], 137 | RegExp.prototype.test.bind(/Database dialect\s*1/), done 138 | ); 139 | }); 140 | 141 | it('should set sweep interval', done => { 142 | testProperty( 143 | 'setSweepinterval', [DATABASE.database, 20000], 144 | RegExp.prototype.test.bind(/Sweep interval:\s*20000/), done 145 | ); 146 | }); 147 | 148 | it('should set cache buffer', done => { 149 | testProperty( 150 | 'setCachebuffer', [DATABASE.database, 4000], 151 | RegExp.prototype.test.bind(/Page buffers\s*4000/), done 152 | ); 153 | }); 154 | 155 | it('should old shutdown', done => { 156 | testProperty( 157 | 'Shutdown', [DATABASE.database, 0, 0], 158 | RegExp.prototype.test.bind(/Attributes\s*.*? multi-user maintenance/), done 159 | ); 160 | }); 161 | 162 | it('should bring online', done => { 163 | testProperty( 164 | 'BringOnline', [DATABASE.database], 165 | (data) => !data.indexOf('maintenance') > -1, done 166 | ) 167 | }); 168 | 169 | const SHUTDOWN = [ 170 | {name: 'force - multi', args: [0, 0, 1], test: /Attributes\s*.*? multi-user maintenance/}, 171 | {name: 'force - single', args: [0, 0, 2], test: /Attributes\s*.*? single-user maintenance/}, 172 | {name: 'transaction - multi', args: [1, 0, 1], test: /Attributes\s*.*? multi-user maintenance/}, 173 | {name: 'transaction - single',args: [1, 0, 2], test: /Attributes\s*.*? single-user maintenance/}, 174 | {name: 'attachement - multi',args: [2, 0, 1], test: /Attributes\s*.*? multi-user maintenance/}, 175 | {name: 'attachement - single',args: [2, 0, 2], test: /Attributes\s*.*? single-user maintenance/} 176 | ]; 177 | SHUTDOWN.forEach(possibility => { 178 | it('should new shutdown : ' + possibility.name, done => { 179 | possibility.args.unshift(DATABASE.database); 180 | 181 | testProperty( 182 | 'Shutdown', possibility.args, 183 | RegExp.prototype.test.bind(possibility.test), () => { 184 | testProperty( 185 | 'BringOnline', [DATABASE.database], 186 | (data) => !data.indexOf('maintenance') > -1, done 187 | ); 188 | } 189 | ); 190 | }); 191 | }); 192 | 193 | it('should set shadow'); // TODO 194 | 195 | it('should disable force write', done => { 196 | testProperty( 197 | 'setForcewrite', [DATABASE.database, false], 198 | data => data.indexOf('force write') === -1, done 199 | ); 200 | }); 201 | 202 | it('should enable force write', done => { 203 | testProperty( 204 | 'setForcewrite', [DATABASE.database, true], 205 | data => data.indexOf('force write') > -1, done 206 | ); 207 | }); 208 | 209 | it('should disable reverse space', done => { 210 | testProperty( 211 | 'setReservespace', [DATABASE.database, false], 212 | data => data.indexOf('no reserve') > -1, done 213 | ); 214 | }); 215 | 216 | it('should enable reverse space', done => { 217 | testProperty( 218 | 'setReservespace', [DATABASE.database, true], 219 | data => !data.indexOf('no reverse') > -1, done 220 | ); 221 | }); 222 | 223 | it('should set read only', done => { 224 | testProperty( 225 | 'setReadonlyMode', [DATABASE.database], 226 | RegExp.prototype.test.bind(/Attributes\s*.*?read only/), done 227 | ); 228 | }); 229 | 230 | it('should set read write', done => { 231 | testProperty( 232 | 'setReadwriteMode', [DATABASE.database], 233 | RegExp.prototype.test.bind(/^\s*Attributes((?!read only).)*$/m), done 234 | ); 235 | }); 236 | 237 | /** 238 | * Execute service manager function and call getStat for check. 239 | * 240 | * @param func Function on the service manager to execute 241 | * @param args Arguments without callback 242 | * @param verifier Callback return boolean asserting 243 | * @param done Done callback 244 | */ 245 | function testProperty(func, args, verifier, done) { 246 | Firebird.attach(config, (err, srv) => { 247 | assert.ok(!err, err); 248 | 249 | // Push callback into args 250 | args.push((err, data) => { 251 | assert.ok(!err, err); 252 | 253 | srv.getStats({}, (err, data) => { 254 | assert.ok(!err, err); 255 | 256 | readStream(data, result => { 257 | assert.ok(verifier(result), result); 258 | 259 | srv.detach(done); 260 | }); 261 | }); 262 | }); 263 | 264 | srv[func].apply(srv, args); 265 | }); 266 | } 267 | }); 268 | 269 | describe('Server users', () => { 270 | it('should get user', done => { 271 | Firebird.attach(config, (err, srv) => { 272 | assert.ok(!err, err); 273 | 274 | srv.getUsers('sysdba', (err, data) => { 275 | assert.ok(!err, err); 276 | verifyUser(data.fbusers[0], { 277 | username: 'SYSDBA', 278 | firstname: 'Sql', 279 | middlename: 'Server', 280 | lastname: 'Administrator', 281 | userid: 0, 282 | groupid: 0 283 | }); 284 | 285 | srv.detach(done); 286 | }); 287 | }); 288 | }); 289 | 290 | it('should get all users', done => { 291 | Firebird.attach(config, (err, srv) => { 292 | assert.ok(!err, err); 293 | 294 | srv.getUsers(undefined, (err, data) => { 295 | assert.ok(!err, err); 296 | assert.ok(data.fbusers.length > 0); 297 | 298 | srv.detach(done); 299 | }); 300 | }); 301 | }); 302 | 303 | const EXPECTED_USER = { 304 | username: 'TEST-'+(new Date()).getTime(), 305 | password: 'test', 306 | firstname: 'Sql', 307 | middlename: 'server', 308 | lastname: 'user', 309 | userid: 2, 310 | groupid: 0 311 | }; 312 | it('should create user', done => { 313 | Firebird.attach(config, (err, srv) => { 314 | assert.ok(!err, err); 315 | 316 | srv.addUser(EXPECTED_USER.username, EXPECTED_USER.password, EXPECTED_USER, (err, data) => { 317 | assert.ok(!err, err); 318 | 319 | srv.getUsers(EXPECTED_USER.username, (err, userData) => { 320 | assert.ok(!err, err); 321 | verifyUser(userData.fbusers[0], EXPECTED_USER); 322 | 323 | srv.detach(done); 324 | }); 325 | }); 326 | }); 327 | }); 328 | 329 | const EDIT_EXPECTED_USER = Object.assign({}, EXPECTED_USER, { 330 | firstname: 'Sql2', 331 | middlename: 'server2', 332 | lastname: 'user2' 333 | }); 334 | it('should edit user', done => { 335 | Firebird.attach(config, (err, srv) => { 336 | assert.ok(!err, err); 337 | 338 | srv.editUser(EDIT_EXPECTED_USER.username, EDIT_EXPECTED_USER, (err, data) => { 339 | assert.ok(!err, err); 340 | 341 | srv.getUsers(EDIT_EXPECTED_USER.username, (err, userData) => { 342 | assert.ok(!err, err); 343 | verifyUser(userData.fbusers[0], EDIT_EXPECTED_USER); 344 | 345 | srv.detach(done); 346 | }); 347 | }); 348 | }); 349 | }); 350 | 351 | it('should remove user', done => { 352 | Firebird.attach(config, (err, srv) => { 353 | assert.ok(!err, err); 354 | 355 | srv.removeUser(EXPECTED_USER.username, '', (err, data) => { 356 | assert.ok(!err, err); 357 | 358 | srv.getUsers('', (err, userData) => { 359 | assert.ok(!err, err); 360 | const users = userData.fbusers.filter(u => u.username === EXPECTED_USER.username) 361 | assert.equal(users.length, 0); 362 | 363 | srv.detach(done); 364 | }); 365 | }); 366 | }); 367 | }); 368 | 369 | function verifyUser(user, expected) { 370 | assert.equal(user.username, expected.username); 371 | assert.equal(user.firstname, expected.firstname); 372 | assert.equal(user.middlename, expected.middlename); 373 | assert.equal(user.lastname, expected.lastname); 374 | assert.equal(user.userid, expected.userid); 375 | assert.equal(user.groupid, expected.groupid); 376 | } 377 | }); 378 | 379 | describe('Backup/Restaure', () => { 380 | it('should backup', done => { 381 | const BACKUP_OPTS = { 382 | database: DATABASE.database, 383 | files: [ 384 | {filename: DATABASE.database.replace('.fdb', '-backup.fbk')} 385 | ] 386 | }; 387 | 388 | Firebird.attach(config, (err, srv) => { 389 | assert.ok(!err, err); 390 | 391 | srv.backup(BACKUP_OPTS, (err, data) => { 392 | assert.ok(!err, err); 393 | assert.ok(data instanceof stream.Readable); 394 | 395 | data.on('data', () => {}); 396 | data.on('end', () => { 397 | assert.ok(fs.existsSync(path.resolve(BACKUP_OPTS.files[0].filename))); 398 | srv.detach(done); 399 | }); 400 | }); 401 | }); 402 | }); 403 | 404 | it('should restore', done => { 405 | const RESTORE_OPTS = { 406 | database: DATABASE.database.replace('.fdb', '-rest.fdb'), 407 | files: [ 408 | DATABASE.database.replace('.fdb', '-backup.fbk') 409 | ] 410 | }; 411 | 412 | Firebird.attach(config, (err, srv) => { 413 | assert.ok(!err, err); 414 | 415 | srv.restore(RESTORE_OPTS, (err, data) => { 416 | assert.ok(!err, err); 417 | assert.ok(data instanceof stream.Readable); 418 | 419 | data.on('data', () => {}); 420 | data.on('end', () => { 421 | assert.ok(fs.existsSync(path.resolve(RESTORE_OPTS.database))); 422 | srv.detach(done); 423 | }); 424 | }); 425 | }); 426 | }); 427 | 428 | it('should nbackup', done => { 429 | const BACKUP_OPTS = { 430 | database: DATABASE.database, 431 | file: DATABASE.database.replace('.fdb', '-nbackup.fbk') 432 | }; 433 | 434 | Firebird.attach(config, (err, srv) => { 435 | assert.ok(!err, err); 436 | 437 | srv.nbackup(BACKUP_OPTS, (err, data) => { 438 | assert.ok(!err, err); 439 | assert.ok(data instanceof stream.Readable); 440 | 441 | assert.ok(fs.existsSync(path.resolve(BACKUP_OPTS.file))); 442 | srv.detach(done); 443 | }); 444 | }); 445 | }); 446 | 447 | it('should nrestore', done => { 448 | const RESTORE_OPTS = { 449 | database: DATABASE.database.replace('.fdb', '-nrestore.fdb'), 450 | files: [ 451 | DATABASE.database.replace('.fdb', '-nbackup.fbk') 452 | ] 453 | }; 454 | 455 | Firebird.attach(config, (err, srv) => { 456 | assert.ok(!err, err); 457 | 458 | srv.nrestore(RESTORE_OPTS, (err, data) => { 459 | assert.ok(!err, err); 460 | assert.ok(data instanceof stream.Readable); 461 | 462 | assert.ok(fs.existsSync(path.resolve(RESTORE_OPTS.database))); 463 | srv.detach(done); 464 | }); 465 | }); 466 | }); 467 | }); 468 | 469 | describe('Trace', () => { 470 | const traceName = 'test-trace-' + Config.currentDate.getTime(); 471 | var traceConfig, traceId; 472 | 473 | before(done => { 474 | Firebird.attach(config, (err, srv) => { 475 | assert.ok(!err, err); 476 | 477 | srv.getFbserverInfos({'fbversion': true}, {}, (err, data) => { 478 | assert.ok(!err, err); 479 | 480 | var matches = data.fbversion.match(/((\w{2})-(\w)(\d+)\.(\d+)\.(\d+)\.(\d+)(?:-\S+)?) (.+)/); 481 | var serverMajorVersion = parseInt(matches[4]); 482 | var traceConfigFile = serverMajorVersion > 2 ? 'fbtrace-3.conf' : 'fbtrace-2.conf'; 483 | traceConfig = fs.readFileSync(path.resolve(__dirname, traceConfigFile), {encoding: 'utf8'}); 484 | 485 | srv.detach(done); 486 | }); 487 | }); 488 | }); 489 | 490 | // TODO test logging of new transaction or statement 491 | it('should start trace', done => { 492 | Firebird.attach(config, (err, srv) => { 493 | assert.ok(!err, err); 494 | 495 | srv.startTrace({configfile: traceConfig, tracename: traceName}, (err, data) => { 496 | assert.ok(!err, err); 497 | assert.ok(data instanceof stream.Readable); 498 | 499 | var result = ''; 500 | data.on('data', chunk => { 501 | traceId = chunk.substring('Trace session ID '.length, chunk.indexOf(' started')); 502 | assert.ok(traceId); 503 | result += chunk; 504 | 505 | done(); // Done when trace is starting 506 | }); 507 | data.on('end', () => { 508 | srv.detach(); 509 | }); 510 | }); 511 | }); 512 | }); 513 | 514 | it('should suspend trace', done => { 515 | Firebird.attach(config, (err, srv) => { 516 | assert.ok(!err, err); 517 | 518 | srv.suspendTrace({traceid: traceId}, (err, data) => { 519 | assert.ok(!err, err); 520 | assert.ok(data instanceof stream.Readable); 521 | 522 | readStream(data, result => { 523 | assert.equal(result.trim(), 'Trace session ID ' + traceId + ' paused'); 524 | 525 | srv.detach(done); 526 | }); 527 | }); 528 | }); 529 | }); 530 | 531 | it('should resume trace', done => { 532 | Firebird.attach(config, (err, srv) => { 533 | assert.ok(!err, err); 534 | 535 | srv.resumeTrace({traceid: traceId}, (err, data) => { 536 | assert.ok(!err, err); 537 | assert.ok(data instanceof stream.Readable); 538 | 539 | readStream(data, result => { 540 | assert.equal(result.trim(), 'Trace session ID ' + traceId + ' resumed'); 541 | 542 | srv.detach(done); 543 | }); 544 | }); 545 | }); 546 | }); 547 | 548 | it('should get trace', done => { 549 | Firebird.attach(config, (err, srv) => { 550 | assert.ok(!err, err); 551 | 552 | srv.getTraceList({}, (err, data) => { 553 | assert.ok(!err, err); 554 | 555 | readStream(data, result => { 556 | assert.ok(result.indexOf('Session ID: ' + traceId) > -1); 557 | assert.ok(result.indexOf('name: ' + traceName) > -1); 558 | assert.ok(result.indexOf('user: ' + config.user.toUpperCase()) > -1); 559 | assert.ok(result.indexOf('date:') > -1); 560 | assert.ok(result.indexOf('flags: active') > -1); 561 | 562 | srv.detach(done); 563 | }); 564 | }); 565 | }); 566 | }); 567 | 568 | it('should stop trace', done => { 569 | Firebird.attach(config, (err, srv) => { 570 | assert.ok(!err, err); 571 | 572 | srv.stopTrace({traceid: traceId}, (err, data) => { 573 | assert.ok(!err, err); 574 | 575 | readStream(data, result => { 576 | assert.equal(result.trim(), 'Trace session ID ' + traceId + ' stopped'); 577 | 578 | srv.detach(done); 579 | }); 580 | }); 581 | }); 582 | }); 583 | }); 584 | }); 585 | -------------------------------------------------------------------------------- /test/srp.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Srp = require('../lib/srp.js'); 3 | var BigInt = require('big-integer'); 4 | var crypto = require('crypto'); 5 | 6 | const USER = 'SYSDBA'; 7 | const PASSWORD = 'masterkey'; 8 | const DEBUG_PRIVATE_KEY = BigInt('60975527035CF2AD1989806F0407210BC81EDC04E2762A56AFD529DDDA2D4393', 16); 9 | const DEBUG_SALT = '02E268803000000079A478A700000002D1A6979000000026E1601C000000054F'; 10 | 11 | const EXPECT_CLIENT_KEY = BigInt('712c5f8a2db82464c4d640ae971025aa50ab64906d4f044f822e8af8a58adabbdbe1efaba00bccd4cdaa8a955bc43c3600beab9ebb9bd41acc56e37f1a48f17293f24e876b53eea6a60712d3f943769056b63202416827b400e162a8c0938d482274307585e0bc1d9dd52efa7330b28e41b7cfcefd9e8523fd11440ee5de93a8', 16); 12 | 13 | describe('Test Srp client', function () { 14 | it('should generate client keys', function(done) { 15 | var keys = Srp.clientSeed(DEBUG_PRIVATE_KEY); 16 | 17 | assert.ok(keys.public.equals(EXPECT_CLIENT_KEY)); 18 | done(); 19 | }); 20 | 21 | it('should generate server keys with debug input value', function(done) { 22 | testSrp(done, 'sha1', DEBUG_SALT, DEBUG_PRIVATE_KEY); 23 | }); 24 | 25 | it('should generate sha1 server keys with random keys', function(done) { 26 | testSrp(done, 'sha1', crypto.randomBytes(32).toString('hex')); 27 | }); 28 | 29 | it('should generate sha256 server keys with random keys', function(done) { 30 | testSrp(done, 'sha256', crypto.randomBytes(32).toString('hex')); 31 | }); 32 | 33 | /** 34 | * Test function 35 | */ 36 | function testSrp(done, algo, salt, client, server) { 37 | var clientKeys = client ? Srp.clientSeed(client) : Srp.clientSeed(); 38 | var serverKeys = Srp.serverSeed(USER, PASSWORD, salt); 39 | 40 | const serverSessionKey = Srp.serverSession( 41 | USER, PASSWORD, salt, 42 | clientKeys.public, serverKeys.public, serverKeys.private 43 | ); 44 | 45 | const proof = Srp.clientProof( 46 | USER, PASSWORD, salt, 47 | clientKeys.public, serverKeys.public, clientKeys.private, 48 | algo 49 | ); 50 | 51 | assert.ok(proof.clientSessionKey.equals(serverSessionKey), 'Session key mismatch'); 52 | done(); 53 | } 54 | }); 55 | --------------------------------------------------------------------------------