node-firebird 2.3.3 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +544 -6
  2. package/lib/callback.d.ts +20 -0
  3. package/lib/callback.js +6 -16
  4. package/lib/firebird.msg.json +1370 -1370
  5. package/lib/gdscodes.d.ts +1519 -1520
  6. package/lib/gdscodes.js +5 -5
  7. package/lib/ieee754-decimal.d.ts +38 -0
  8. package/lib/ieee754-decimal.js +71 -118
  9. package/lib/index.d.ts +37 -338
  10. package/lib/index.js +97 -86
  11. package/lib/messages.d.ts +6 -0
  12. package/lib/messages.js +135 -162
  13. package/lib/pool.d.ts +23 -0
  14. package/lib/pool.js +34 -47
  15. package/lib/srp.d.ts +51 -0
  16. package/lib/srp.js +62 -72
  17. package/lib/types.d.ts +375 -0
  18. package/lib/types.js +22 -0
  19. package/lib/unix-crypt.d.ts +1 -0
  20. package/lib/unix-crypt.js +126 -152
  21. package/lib/utils.d.ts +16 -0
  22. package/lib/utils.js +24 -48
  23. package/lib/wire/connection.d.ts +100 -0
  24. package/lib/wire/connection.js +1029 -1009
  25. package/lib/wire/const.d.ts +567 -0
  26. package/lib/wire/const.js +620 -599
  27. package/lib/wire/database.d.ts +57 -0
  28. package/lib/wire/database.js +111 -108
  29. package/lib/wire/eventConnection.d.ts +16 -0
  30. package/lib/wire/eventConnection.js +29 -31
  31. package/lib/wire/fbEventManager.d.ts +45 -0
  32. package/lib/wire/fbEventManager.js +38 -57
  33. package/lib/wire/serialize.d.ts +97 -0
  34. package/lib/wire/serialize.js +42 -141
  35. package/lib/wire/service.d.ts +66 -0
  36. package/lib/wire/service.js +374 -401
  37. package/lib/wire/socket.d.ts +59 -0
  38. package/lib/wire/socket.js +79 -40
  39. package/lib/wire/statement.d.ts +25 -0
  40. package/lib/wire/statement.js +13 -9
  41. package/lib/wire/transaction.d.ts +21 -0
  42. package/lib/wire/transaction.js +33 -61
  43. package/lib/wire/xsqlvar.d.ts +175 -0
  44. package/lib/wire/xsqlvar.js +169 -269
  45. package/package.json +12 -4
  46. package/src/callback.ts +55 -0
  47. package/{poc/node_modules/node-firebird/lib/gdscodes.js → src/gdscodes.ts} +3 -3
  48. package/{poc/node_modules/node-firebird/lib/ieee754-decimal.js → src/ieee754-decimal.ts} +7 -14
  49. package/src/index.ts +157 -0
  50. package/{poc/node_modules/node-firebird/lib/messages.js → src/messages.ts} +163 -162
  51. package/src/pool.ts +209 -0
  52. package/{poc/node_modules/node-firebird/lib/srp.js → src/srp.ts} +74 -33
  53. package/src/types.ts +419 -0
  54. package/{poc/node_modules/node-firebird/lib/unix-crypt.js → src/unix-crypt.ts} +6 -12
  55. package/{poc/node_modules/node-firebird/lib/utils.js → src/utils.ts} +11 -20
  56. package/{poc/node_modules/node-firebird/lib/wire/connection.js → src/wire/connection.ts} +611 -129
  57. package/{poc/node_modules/node-firebird/lib/wire/const.js → src/wire/const.ts} +96 -9
  58. package/{poc/node_modules/node-firebird/lib/wire/database.js → src/wire/database.ts} +110 -44
  59. package/{poc/node_modules/node-firebird/lib/wire/eventConnection.js → src/wire/eventConnection.ts} +29 -14
  60. package/{poc/node_modules/node-firebird/lib/wire/fbEventManager.js → src/wire/fbEventManager.ts} +39 -30
  61. package/{poc/node_modules/node-firebird/lib/wire/serialize.js → src/wire/serialize.ts} +78 -59
  62. package/{poc/node_modules/node-firebird/lib/wire/service.js → src/wire/service.ts} +90 -88
  63. package/src/wire/socket.ts +262 -0
  64. package/src/wire/statement.ts +71 -0
  65. package/{poc/node_modules/node-firebird/lib/wire/transaction.js → src/wire/transaction.ts} +25 -19
  66. package/{poc/node_modules/node-firebird/lib/wire/xsqlvar.js → src/wire/xsqlvar.ts} +244 -143
  67. package/.eslintrc.json +0 -12
  68. package/.github/workflows/codeql.yml +0 -76
  69. package/.github/workflows/node.js.yml +0 -95
  70. package/BIGINT_MIGRATION.md +0 -374
  71. package/CI_DEBUGGING_GUIDE.md +0 -148
  72. package/ENCRYPTION_CALLBACK.md +0 -152
  73. package/FIREBIRD_LOG_FEATURE.md +0 -145
  74. package/MINIMAL_CHANGES_SUMMARY.md +0 -136
  75. package/PR_SUMMARY.md +0 -96
  76. package/ROADMAP.md +0 -223
  77. package/SRP_PROTOCOL.md +0 -482
  78. package/poc/README.md +0 -160
  79. package/poc/helpers.js +0 -59
  80. package/poc/node_modules/.package-lock.json +0 -14
  81. package/poc/node_modules/node-firebird/.eslintrc.json +0 -12
  82. package/poc/node_modules/node-firebird/.github/workflows/codeql.yml +0 -76
  83. package/poc/node_modules/node-firebird/.github/workflows/node.js.yml +0 -95
  84. package/poc/node_modules/node-firebird/BIGINT_MIGRATION.md +0 -374
  85. package/poc/node_modules/node-firebird/CI_DEBUGGING_GUIDE.md +0 -148
  86. package/poc/node_modules/node-firebird/ENCRYPTION_CALLBACK.md +0 -152
  87. package/poc/node_modules/node-firebird/FIREBIRD_LOG_FEATURE.md +0 -145
  88. package/poc/node_modules/node-firebird/LICENSE +0 -373
  89. package/poc/node_modules/node-firebird/MINIMAL_CHANGES_SUMMARY.md +0 -136
  90. package/poc/node_modules/node-firebird/PR_SUMMARY.md +0 -96
  91. package/poc/node_modules/node-firebird/README.md +0 -794
  92. package/poc/node_modules/node-firebird/ROADMAP.md +0 -223
  93. package/poc/node_modules/node-firebird/SRP_PROTOCOL.md +0 -482
  94. package/poc/node_modules/node-firebird/lib/callback.js +0 -38
  95. package/poc/node_modules/node-firebird/lib/gdscodes.d.ts +0 -1524
  96. package/poc/node_modules/node-firebird/lib/index.d.ts +0 -316
  97. package/poc/node_modules/node-firebird/lib/index.js +0 -128
  98. package/poc/node_modules/node-firebird/lib/pool.js +0 -108
  99. package/poc/node_modules/node-firebird/lib/wire/socket.js +0 -175
  100. package/poc/node_modules/node-firebird/lib/wire/statement.js +0 -48
  101. package/poc/node_modules/node-firebird/package.json +0 -38
  102. package/poc/node_modules/node-firebird/vitest.config.js +0 -24
  103. package/poc/package-lock.json +0 -21
  104. package/poc/package.json +0 -12
  105. package/poc/reproduce-fixed.js +0 -150
  106. package/poc/reproduce.js +0 -133
  107. package/vitest.config.js +0 -27
  108. /package/{poc/node_modules/node-firebird/lib → src}/firebird.msg +0 -0
  109. /package/{poc/node_modules/node-firebird/lib → src}/firebird.msg.json +0 -0
package/SRP_PROTOCOL.md DELETED
@@ -1,482 +0,0 @@
1
- # Firebird SRP Authentication Protocol
2
-
3
- ## Overview
4
-
5
- Firebird uses **Secure Remote Password (SRP)** authentication for all connections that do not use the legacy wire-encryption scheme (`Legacy_Auth`). SRP provides password-authenticated key agreement: neither side ever sends the plaintext password over the wire, and both sides prove knowledge of the password without revealing it.
6
-
7
- - **Firebird 3** introduced SRP (`Srp` plugin, SHA-1 HMAC, Protocol Version 14).
8
- - **Firebird 4** added `Srp256` (SHA-256 HMAC, Protocol Version 16) and kept `Srp` as fallback.
9
- - **Firebird 5** uses Protocol Version 17 with the same plugin set as FB4, plus optional wire compression.
10
- - **Firebird 6** (in development) continues Protocol Version 17 and retains full backward compatibility.
11
-
12
- ---
13
-
14
- ## SRP Concepts
15
-
16
- | Term | Meaning |
17
- |---|---|
18
- | `N` | A large 1024-bit prime (the SRP group parameter, same for all Firebird versions) |
19
- | `g` | Generator `2` |
20
- | `k` | Multiplier parameter derived from `N` and `g` |
21
- | `s` | Random salt stored in the security database |
22
- | `v` | Verifier: `g^x mod N`, where `x = H(s, H(U:p))` |
23
- | `a`, `A` | Client private/public ephemeral keys: `A = g^a mod N` |
24
- | `b`, `B` | Server private/public ephemeral keys: `B = kv + g^b mod N` |
25
- | `u` | Scrambler: `H(A, B)` |
26
- | `K` | Session key derived by both sides |
27
- | `M1` | Client proof: `H(H(N)⊕H(g), H(I), s, A, B, K)` |
28
- | `M2` | Server proof: `H(A, M1, K)` (client may or may not verify this) |
29
-
30
- The SRP group parameters are shared constants defined in `lib/srp.js`:
31
-
32
- ```
33
- N = E67D2E994B2F900C3F41F08F5BB2627ED0D49EE1FE767A52EFCD565CD6E768812C3E1E9CE8F0A8BEA6CB13CD29DDEBF7A96D4A93B55D488DF099A15C89DCB0640738EB2CBDD9A8F7BAB561AB1B0DC1C6CDABF303264A08D1BCA932D1F1EE428B619D970F342ABA9A65793B8B2F041AE5364350C16F735F56ECBCA87BD57B29E7
34
- g = 2
35
- k = 1277432915985975349439481660349303019122249719989
36
- ```
37
-
38
- > **Important exponent reduction note**: The Firebird engine reduces intermediate SRP exponents modulo N, which deviates from the SRP specification. `node-firebird` mirrors this behaviour in `lib/srp.js` so that client and server agree on the session key `K`.
39
-
40
- ---
41
-
42
- ## Protocol Version Matrix
43
-
44
- | Firebird Version | Wire Protocol | Auth Plugin | Hash | `op_accept_data` opcode | `op_cond_accept` opcode |
45
- |---|---|---|---|---|---|
46
- | 2.5 | 13 | `Legacy_Auth` | DES crypt | 94 | — |
47
- | 3.x | 14 (`0x800E`) | `Srp` | SHA-1 | 94 | 98 |
48
- | 4.x | 16 (`0x8010`) | `Srp`, `Srp256` | SHA-1 / SHA-256 | 94 | 98 |
49
- | 5.x | 17 (`0x8011`) | `Srp`, `Srp256` | SHA-1 / SHA-256 | 94 | 98 |
50
-
51
- The high bit of the protocol version number (`0x8000`) is the **Firebird private flag**: it prevents ambiguity with Borland InterBase protocol numbers (protocols 1–13).
52
-
53
- ---
54
-
55
- ## Wire-Protocol Sequence Diagrams
56
-
57
- ### Legacy Auth (no SRP, `is_authenticated = 1`)
58
-
59
- ```
60
- Client Server
61
- ────── ──────
62
- op_connect (plugin=Legacy_Auth, A=hash(pw))
63
- ─────────────────────────────────────────────▶
64
- op_accept_data (is_authenticated=1)
65
- ◀─────────────────────────────────────────────
66
- op_attach (database, DPB)
67
- ─────────────────────────────────────────────▶
68
- op_response (dbHandle)
69
- ◀─────────────────────────────────────────────
70
- ```
71
-
72
- ### SRP Auth (FB3 Protocol 14 / FB4 Protocol 16 / FB5 Protocol 17)
73
-
74
- ```
75
- Client Server
76
- ────── ──────
77
- 1. op_connect
78
- plugin = "Srp" (or "Srp256")
79
- A = clientPublicKey (hex)
80
- ─────────────────────────────────────────────▶
81
-
82
- 2. op_cond_accept (opcode 98)
83
- BLR auth data:
84
- [uint16LE saltLen][salt hex]
85
- [uint16LE keyLen ][B hex ]
86
- plugin = "Srp"
87
- is_authenticated = 0
88
- ◀─────────────────────────────────────────────
89
-
90
- 3. Client computes:
91
- u = SHA1(pad(A), pad(B))
92
- x = SHA1(salt, SHA1(U + ':' + password))
93
- S = (B - k·g^x)^(a + u·x) mod N
94
- K = SHA1(S)
95
- M1 = SHA1(SHA1(N)⊕SHA1(g), SHA1(user), salt, A, B, K)
96
-
97
- 4. op_cont_auth (opcode 92)
98
- auth_data = M1 (hex)
99
- plugin = "Srp"
100
- ─────────────────────────────────────────────▶
101
-
102
- 5. Server verifies M1, computes M2
103
- op_cont_auth (opcode 92)
104
- auth_data = M2 (server proof, may be empty)
105
- plugin = "Srp"
106
- ◀─────────────────────────────────────────────
107
-
108
- 6. op_accept (opcode 3)
109
- protocolVersion = 0x800E / 0x8010 / 0x8011
110
- ◀─────────────────────────────────────────────
111
-
112
- [If wireCrypt ≠ DISABLE]:
113
- 7. op_crypt (opcode 96) "Arc4"
114
- ─────────────────────────────────────────────▶
115
- op_response
116
- ◀─────────────────────────────────────────────
117
- Both sides enable Arc4 stream cipher using
118
- session key K (padded to 20 bytes / SHA-1 length)
119
-
120
- 8. op_attach (database, DPB)
121
- ─────────────────────────────────────────────▶
122
- op_response (dbHandle)
123
- ◀─────────────────────────────────────────────
124
- ```
125
-
126
- ### SRP256 (Srp256 plugin — FB4/FB5 only)
127
-
128
- The wire sequence is identical to SRP (above). The only difference is:
129
-
130
- - The hash algorithm for `M1` / `M2` computation switches from **SHA-1** to **SHA-256**.
131
- - The plugin name in `op_cond_accept` is `"Srp256"` instead of `"Srp"`.
132
-
133
- ---
134
-
135
- ## Changes Per Firebird Version
136
-
137
- ### Firebird 3 (Protocol 14, plugin `Srp`)
138
-
139
- - Introduced the SRP authentication framework.
140
- - Hash algorithm for `M1` and `M2`: **SHA-1**.
141
- - Arc4 stream cipher for wire encryption.
142
- - Auth data encoding: BLR byte array inside `op_cond_accept`.
143
- - No wire compression support.
144
-
145
- ### Firebird 4 (Protocol 16, plugins `Srp256` and `Srp`)
146
-
147
- - Added `Srp256` plugin with **SHA-256** hashing (preferred).
148
- - Falls back to `Srp` (SHA-1) if the server does not offer `Srp256`.
149
- - Wire compression support added (`pflag_compress` in protocol negotiation).
150
- - `op_response_piggyback` (opcode 72) introduced — server sends this as an unsolicited completion notification; clients must silently discard it.
151
-
152
- ### Firebird 5 (Protocol 17, plugins `Srp256` and `Srp`)
153
-
154
- - Protocol version bumped to 17 (`0x8011`).
155
- - Wire protocol is otherwise compatible with Protocol 16.
156
- - `op_response_piggyback` usage more prevalent during `EventConnection` teardown.
157
- - BigInt arithmetic in SRP key generation can be significantly **slower** on resource-constrained CI runners (see [Timing Issue](#timing-issue-and-fix) below).
158
-
159
- ### Firebird 6 (Protocol 17, in development)
160
-
161
- - Retains Protocol Version 17.
162
- - Continues to support `Srp256` and `Srp` plugins.
163
- - No client-visible protocol changes from Firebird 5 at this time.
164
-
165
- ---
166
-
167
- ## Timing Issue and Fix
168
-
169
- ### Root Cause
170
-
171
- SRP key generation involves **modular exponentiation** over 1024-bit integers. In Node.js, this is performed by the `big-integer` library using pure-JavaScript arithmetic. On a developer machine the `clientSeed()` call completes in < 1 ms and `clientProof()` in < 5 ms. On a loaded CI runner (especially with Firebird 3 which uses SHA-1 requiring more steps), both calls can take **500–3000 ms** combined.
172
-
173
- The original per-test timeout was 10 s → raised to 30 s → **raised to 60 s** (current) to handle the worst-case loaded runner scenario.
174
-
175
- ### How to Diagnose
176
-
177
- Set the environment variable `FIREBIRD_DEBUG=1` before running your tests. You will see:
178
-
179
- ```
180
- [fb-debug] srp.clientSeed: 843ms
181
- [fb-debug] srp.clientProof(sha1): 1247ms
182
- ```
183
-
184
- - `srp.clientSeed` measures the time to generate the client ephemeral key pair `(a, A = g^a mod N)`.
185
- - `srp.clientProof(sha1|sha256)` measures the time to derive `K` and compute `M1`.
186
-
187
- If these values exceed 4000 ms combined, the 60-second timeout may still be too tight on extremely loaded runners. In that case, increase the `it(…, { timeout: … })` value in `test/index.js` for the SRP tests.
188
-
189
- ### The Fix
190
-
191
- `test/index.js`:
192
- ```js
193
- it('should attach with srp plugin', { timeout: 60000 }, async function () { … });
194
- ```
195
-
196
- ### SRP Timing Debug Logs (in `lib/wire/connection.js`)
197
-
198
- ```js
199
- // clientSeed — key pair generation
200
- const _t0 = Date.now();
201
- this.clientKeys = srp.clientSeed();
202
- if (process.env.FIREBIRD_DEBUG) {
203
- console.log('[fb-debug] srp.clientSeed: %dms', Date.now() - _t0);
204
- }
205
-
206
- // clientProof — session key + M1 computation
207
- const _t1 = Date.now();
208
- var proof = srp.clientProof(user, password, salt, A, B, a, hashAlgo);
209
- if (process.env.FIREBIRD_DEBUG) {
210
- console.log('[fb-debug] srp.clientProof(%s): %dms', accept.srpAlgo, Date.now() - _t1);
211
- }
212
- ```
213
-
214
- ---
215
-
216
- ## Session Key Arc4 Encryption
217
-
218
- After the SRP handshake, both the client and server share the session key `K = SHA1(S)`. This key is a 160-bit (20-byte) SHA-1 hash returned as a BigInt from `srp.clientProof()`.
219
-
220
- The key is used to initialise an **Arc4 (RC4)** stream cipher for all subsequent bytes on the socket. A critical padding rule applies:
221
-
222
- ```js
223
- // K is a BigInt; BigInt.toString(16) may omit leading zeros
224
- // Arc4 requires exactly 20 bytes (SHA-1 output length)
225
- var keyBuf = Buffer.from(ret.sessionKey.toString(16).padStart(40, '0'), 'hex');
226
- // ^^^^^^^^^^^^^^^^^^^
227
- // without this, a K with a leading 0x00 byte will be only
228
- // 19 bytes, causing a key mismatch and a garbled connection
229
- self._socket.enableEncryption(keyBuf);
230
- ```
231
-
232
- > **Bug fixed (commit `fc0021d`)**: the `padStart(40, '0')` call was missing. A session key whose most-significant byte was `0x00` would produce a 19-byte (or shorter) key, while the Firebird server always uses the full 20 bytes. This caused connection corruption for ~1 in 256 SRP sessions.
233
-
234
- ---
235
-
236
- ## How to Run the Online SRP Test
237
-
238
- Requires a real Firebird server with:
239
- - `AuthServer = Srp` (or `Srp256`) configured in `firebird.conf`
240
- - A user `SYSDBA` with password `masterkey` (or adjust `test/config.js`)
241
-
242
- ```bash
243
- # Basic run
244
- npm test
245
-
246
- # With debug logging
247
- FIREBIRD_DEBUG=1 npm test -- --grep "srp"
248
- ```
249
-
250
- The test verifies:
251
- 1. `Firebird.attach()` succeeds using SRP.
252
- 2. The `'attach'` driver event fires exactly once.
253
- 3. `db.detach()` succeeds cleanly.
254
-
255
- ---
256
-
257
- ## How to Run the Offline SRP Mock-Server Tests
258
-
259
- No Firebird installation required.
260
-
261
- ```bash
262
- # Run only the offline tests
263
- npx vitest run test/mock-server.js
264
-
265
- # With debug timing output
266
- FIREBIRD_DEBUG=1 npx vitest run test/mock-server.js
267
- ```
268
-
269
- The offline tests use `test/mock-server.js`, which contains a minimal TCP server that speaks enough of the Firebird wire protocol to exercise the full SRP handshake.
270
-
271
- ### Covered Scenarios
272
-
273
- | Test | Protocol | What is verified |
274
- |---|---|---|
275
- | Full SRP attach/detach (FB3) | 14 (`Srp`) | Complete op_connect → op_cond_accept → op_cont_auth → op_accept → op_attach cycle |
276
- | Full SRP attach/detach (FB4) | 16 (`Srp`) | Same flow with Protocol 16 |
277
- | Full SRP attach/detach (FB5) | 17 (`Srp`) | Same flow with Protocol 17 |
278
- | `parseOpConnect` BLR | any | Extracts plugin name `"Srp"` and client key A from `CNCT_specific_data` multi-block |
279
- | `parseOpContAuth` | any | Extracts M1 hex proof from client `op_cont_auth` message |
280
- | `op_cond_accept` XDR round-trip | 14 | BLR format: `[u16LE saltLen][salt][u16LE keyLen][B]` |
281
- | `op_cont_auth` XDR round-trip | any | Correct opcode, empty M2 array, plugin name |
282
- | `op_accept` XDR round-trip | 16 | Correct opcode and protocol version field |
283
- | Protocol version constants | 14/16/17 | `FB_PROTOCOL_FLAG`, `FB_PROTOCOL_MASK` |
284
-
285
- ### SRP Phase Timing (FIREBIRD_DEBUG)
286
-
287
- When `FIREBIRD_DEBUG=1` is set, each SRP mock-server test emits a timing trace:
288
-
289
- ```
290
- [srp-test fb3] opConnectRecv=2ms challengeSent=847ms m1Recv=2094ms acceptSent=2094ms opAttachRecv=2095ms
291
- ```
292
-
293
- Fields:
294
- - `opConnectRecv` — server received `op_connect` from client
295
- - `challengeSent` — server sent `op_cond_accept` (salt + B)
296
- - `m1Recv` — server received `op_cont_auth` (M1 proof)
297
- - `acceptSent` — server sent `op_cont_auth` (M2) + `op_accept`
298
- - `opAttachRecv` — server received `op_attach`
299
-
300
- The gap between `opConnectRecv` and `challengeSent` is the server-side `srp.serverSeed()` time; the gap between `challengeSent` and `m1Recv` is the client-side `srp.clientSeed()` + `srp.clientProof()` time.
301
-
302
- ---
303
-
304
- ## BLR Auth Data Format in `op_cond_accept`
305
-
306
- The `op_cond_accept` (opcode 98) frame carries the SRP challenge in a **BLR byte array** field. The format is:
307
-
308
- ```
309
- Offset Size Field
310
- ────── ──── ─────
311
- 0 2 saltLen (uint16 little-endian) — length of the salt hex string
312
- 2 N salt (ASCII hex string, N = saltLen bytes)
313
- 2+N 2 keyLen (uint16 little-endian) — length of the server B hex string
314
- 4+N M B (ASCII hex string, M = keyLen bytes)
315
- ```
316
-
317
- Example:
318
- ```
319
- 00 40 → saltLen = 64 (32 bytes of salt → 64 hex chars)
320
- 3031323334... → 64 ASCII hex characters of salt
321
- 00 80 → keyLen = 128 (64 bytes of B → 128 hex chars)
322
- 61626364... → 128 ASCII hex characters of B
323
- ```
324
-
325
- `node-firebird` parses this in `lib/wire/connection.js`:
326
- ```js
327
- var saltLen = d.buffer.readUInt16LE(0);
328
- var keyLen = d.buffer.readUInt16LE(saltLen + 2);
329
- var keyStart = saltLen + 4;
330
- cnx.serverKeys = {
331
- salt: d.buffer.slice(2, saltLen + 2).toString('utf8'),
332
- public: BigInt(d.buffer.slice(keyStart).toString('utf8'), 16)
333
- };
334
- ```
335
-
336
- ---
337
-
338
- ## `op_cont_auth` Client Message Format
339
-
340
- The client sends `op_cont_auth` (opcode 92) with:
341
-
342
- ```
343
- Field XDR type Content
344
- ────────────── ──────── ───────────────────────────────
345
- opcode int32 92 (op_cont_auth)
346
- auth_data array M1 proof as ASCII hex string
347
- plugin_name string "Srp" or "Srp256"
348
- plist string "" (empty)
349
- pkey string "" (empty)
350
- ```
351
-
352
- ---
353
-
354
- ## `op_cont_auth` Server Response Format
355
-
356
- The server replies with `op_cont_auth` (opcode 92) carrying M2:
357
-
358
- ```
359
- Field XDR type Content
360
- ────────────── ──────── ────────────────────────────────
361
- opcode int32 92 (op_cont_auth)
362
- auth_data array M2 server proof (may be empty)
363
- plugin_name string "Srp" or "Srp256"
364
- plist string "" (empty)
365
- pkey string "" (empty)
366
- ```
367
-
368
- > **node-firebird does NOT validate M2**. After receiving the server `op_cont_auth`, it simply waits for the subsequent `op_accept`. This is safe for practical use but means a compromised server could send any M2 value.
369
-
370
- ---
371
-
372
- ## Offline Mock-Server Architecture
373
-
374
- ```
375
- ┌─────────────────────────────────────────────────────────┐
376
- │ test/mock-server.js │
377
- │ │
378
- │ ┌──────────────────────────────────────────────────┐ │
379
- │ │ Frame Builders (XdrWriter / BlrWriter) │ │
380
- │ │ buildOpAcceptData(plugin) │ │
381
- │ │ buildOpResponse(handle) │ │
382
- │ │ buildOpEvent(dbHandle, eventRid) │ │
383
- │ │ buildOpResponsePiggyback() │ │
384
- │ │ buildOpCondAcceptSRP(proto, salt, B) │ │
385
- │ │ buildOpContAuthServer(m2Data) │ │
386
- │ │ buildOpAccept(proto) │ │
387
- │ └──────────────────────────────────────────────────┘ │
388
- │ │
389
- │ ┌──────────────────────────────────────────────────┐ │
390
- │ │ Frame Parsers │ │
391
- │ │ parseOpConnect(buf) — BLR parser │ │
392
- │ │ parseOpContAuth(buf) — M1 extractor │ │
393
- │ └──────────────────────────────────────────────────┘ │
394
- │ │
395
- │ ┌──────────────────────────────────────────────────┐ │
396
- │ │ Server Helpers │ │
397
- │ │ startMockServer() / stopMockServer() │ │
398
- │ │ makeDispatcher(port, handler) │ │
399
- │ │ makeFullDispatcher(port, handler) │ │
400
- │ │ withMockAttach(port) / withMockDetach(db) │ │
401
- │ │ withMockSrpAttach(port, proto?) │ │
402
- │ └──────────────────────────────────────────────────┘ │
403
- └─────────────────────────────────────────────────────────┘
404
- │ TCP loopback
405
-
406
- ┌─────────────────────────────────────────────────────────┐
407
- │ lib/wire/connection.js (Connection class) │
408
- │ connect() → decodeResponse() → attach() → detach() │
409
- └─────────────────────────────────────────────────────────┘
410
- ```
411
-
412
- ---
413
-
414
- ## Related Files
415
-
416
- | File | Description |
417
- |---|---|
418
- | `lib/srp.js` | Pure-JS SRP implementation (BigInt arithmetic, SHA-1/SHA-256 hashing) |
419
- | `lib/wire/connection.js` | Wire protocol encode/decode; SRP handshake; debug logging |
420
- | `lib/wire/const.js` | Protocol version constants, opcode numbers, auth plugin names |
421
- | `lib/wire/serialize.js` | `XdrWriter`, `XdrReader`, `BlrWriter`, `BlrReader` |
422
- | `test/srp.js` | Unit tests for SRP arithmetic helpers and end-to-end handshake |
423
- | `test/mock-server.js` | Offline wire-protocol tests (SRP auth + queue integrity) |
424
- | `test/index.js` | Online integration tests (real Firebird server required) |
425
- | `BIGINT_MIGRATION.md` | Migration guide: `big-integer` → native `BigInt` (root-cause analysis, modPow docs, performance) |
426
-
427
- ---
428
-
429
- ## Troubleshooting
430
-
431
- ### `should attach with srp plugin` times out in CI
432
-
433
- 1. Set `FIREBIRD_DEBUG=1` and check the timing logs:
434
- ```
435
- [fb-debug] srp.clientSeed: Xms
436
- [fb-debug] srp.clientProof(sha1): Yms
437
- ```
438
- 2. If `X + Y > 5000`, the runner is overloaded. Increase the timeout in `test/index.js`:
439
- ```js
440
- it('should attach with srp plugin', { timeout: 120000 }, async function () { … });
441
- ```
442
- 3. Alternatively, run the tests at off-peak times or use a dedicated runner.
443
-
444
- ### Connection succeeds but data is garbled (after SRP)
445
-
446
- Most likely cause: session key padding bug. Verify `lib/wire/connection.js` has:
447
- ```js
448
- var keyBuf = Buffer.from(ret.sessionKey.toString(16).padStart(40, '0'), 'hex');
449
- ```
450
- If the `padStart(40, '0')` is absent, keys with a leading zero byte will be truncated.
451
-
452
- ### `op_response_piggyback` causes queue corruption (Firebird 5)
453
-
454
- Fixed in `lib/wire/connection.js`. Verify the `decodeResponse` switch statement has:
455
- ```js
456
- case Const.op_response_piggyback:
457
- parseOpResponse(data, {}, cb);
458
- return { _isOpEvent: true }; // skip queue shift
459
- ```
460
-
461
- ### `op_event` on main connection causes hang
462
-
463
- Fixed in `lib/wire/connection.js`. Verify:
464
- ```js
465
- case Const.op_event:
466
- data.readInt(); // db handle
467
- data.readArray(); // EPB
468
- data.readInt64(); // AST pointer
469
- data.readInt(); // event RID
470
- return { _isOpEvent: true }; // skip queue shift
471
- ```
472
-
473
- ---
474
-
475
- ## References
476
-
477
- - [RFC 2945 — The SRP Authentication and Key Exchange System](https://www.ietf.org/rfc/rfc2945.txt)
478
- - [Firebird source: `src/auth/SecureRemotePassword/`](https://github.com/FirebirdSQL/firebird/tree/master/src/auth/SecureRemotePassword)
479
- - [Firebird Wire Protocol documentation](https://github.com/FirebirdSQL/firebird/blob/master/doc/WhatsNew)
480
- - [node-firebird `lib/srp.js`](lib/srp.js)
481
- - [node-firebird `lib/wire/connection.js`](lib/wire/connection.js)
482
- - [node-firebird `test/mock-server.js`](test/mock-server.js)
package/poc/README.md DELETED
@@ -1,160 +0,0 @@
1
- # node-firebird pool — Bug PoC & Fix
2
-
3
- Proof of concept for three bugs in `lib/pool.js` (node-firebird ≥ 2.x).
4
- No real Firebird server is needed — a tiny fake TCP server simulates the failure scenario.
5
-
6
- ---
7
-
8
- ## The bugs
9
-
10
- ### Bug 1 — `_creating` stuck forever (critical)
11
-
12
- **File:** `lib/pool.js` → `Pool.check()`
13
-
14
- When no idle connection exists, `check()` calls `this.attach()` and increments `_creating`. The counter is only decremented **inside the `attach()` callback**. If the server accepts TCP but never responds to the Firebird wire protocol (e.g. during SRP authentication under load), the callback never fires. `_creating` stays elevated permanently, the slot is lost, and every `pool.get()` caller waits forever.
15
-
16
- ```
17
- // original code
18
- self._creating++;
19
- this.attach(self.options, function (err, db) {
20
- self._creating--; // ← never reached if attach() hangs
21
- cb(err, db); // ← caller never notified
22
- });
23
- ```
24
-
25
- **Real-world log signature:**
26
- ```
27
- *ERRO* Timeout (15000ms) ao aguardar conexão para tenant X.
28
- Estado do pool: {"dbinuse":0,"_creating":1,"pending":0,"idle":4}
29
- ```
30
-
31
- **Fix:** `connectTimeout` option — a `setTimeout` wraps the `attach()` call. On expiry, `_creating` is decremented and the caller receives an error immediately. A late-arriving connection (if attach eventually completes) is discarded safely.
32
-
33
- ---
34
-
35
- ### Bug 2 — `destroy()` does not drain the `pending` queue
36
-
37
- **File:** `lib/pool.js` → `Pool.destroy()`
38
-
39
- Callbacks queued in `this.pending` (waiting for a free pool slot) are silently abandoned when `destroy()` is called. Their callers hang indefinitely — there is no error, no timeout, nothing. This is especially harmful when `destroy()` is used as a recovery strategy after a stuck pool.
40
-
41
- **Fix:** `this.pending.splice(0)` at the start of `destroy()` calls every waiting callback with `Error('Pool is being destroyed')` before any connection is closed.
42
-
43
- ---
44
-
45
- ### Bug 3 — `pool.get()` after `pool.destroy()` silently accumulates
46
-
47
- **File:** `lib/pool.js` → `Pool.get()` and `Pool.check()`
48
-
49
- There is no `_destroyed` flag. Calling `pool.get()` on an already-destroyed pool pushes the callback into `this.pending` and calls `check()`. `check()` returns early because all slots are gone, but the callback is never served. The caller hangs forever.
50
-
51
- **Fix:** `_destroyed = true` is set in `destroy()`. Both `get()` and `check()` check it and reject immediately with `Error('Pool has been destroyed')`.
52
-
53
- ---
54
-
55
- ### Bug 4 (minor) — `destroy()` callback never fires when connections are in use
56
-
57
- **File:** `lib/pool.js` → `Pool.destroy()`
58
-
59
- The original `destroy()` only calls `detachCallback()` for connections found in `pooldb` (idle). Connections currently in use (`dbinuse > 0`) fall through the `forEach` without decrementing `connectionCount`, so it never reaches zero and the `destroy()` completion callback is never invoked.
60
-
61
- **Fix:** The `else` branch counts in-use connections down without forcing a detach — releasing them remains the caller's responsibility.
62
-
63
- ---
64
-
65
- ## Files
66
-
67
- | File | Purpose |
68
- |------|---------|
69
- | `helpers.js` | Shared logging and fake-server factory |
70
- | `reproduce.js` | Demonstrates all bugs — exits with code **1** |
71
- | `reproduce-fixed.js` | Validates all fixes — exits with code **0** |
72
- | `pool-patched.js` | Drop-in replacement for `lib/pool.js` |
73
-
74
- ---
75
-
76
- ## How to run
77
-
78
- ```bash
79
- npm install
80
-
81
- # Show the bugs (process hangs → forced exit 1)
82
- node reproduce.js
83
-
84
- # Show the fixes (process exits cleanly → exit 0)
85
- node reproduce-fixed.js
86
- ```
87
-
88
- ---
89
-
90
- ## Expected output
91
-
92
- ### `node reproduce.js` (bugs present)
93
-
94
- ```
95
- [fake-server] Listening on 127.0.0.1:13050 ...
96
- [BUG-1] pool.get() → TCP connects, Firebird protocol hangs, callback NEVER fires
97
- [pool-state] creating=1 idle=0 inuse=0 pending=0 destroyed=(no flag)
98
- [pool-state] creating=1 idle=0 inuse=0 pending=0 destroyed=(no flag)
99
- [BUG-2] Queuing a second get() in pending, then calling destroy()
100
- [BUG-2] Before destroy — creating=1 idle=0 inuse=0 pending=1 ...
101
- [BUG-2] After destroy — creating=1 idle=0 inuse=0 pending=1 ... ← pending NOT drained
102
- [BUG-2] ↑ pending callback was NEVER called — Promise hangs forever
103
- [BUG-3] pool.get() AFTER pool.destroy() — should be rejected immediately
104
- [BUG-3] After post-destroy get — creating=1 idle=0 inuse=0 pending=2 ...
105
- [SUMMARY] Bug 1 confirmed : _creating=1, permanently stuck (slot lost)
106
- [SUMMARY] Bug 2 confirmed : pending callback from before destroy never fired
107
- [SUMMARY] Bug 3 confirmed : post-destroy get silently accumulated in pending
108
- ```
109
-
110
- ### `node reproduce-fixed.js` (fixes applied)
111
-
112
- ```
113
- [fake-server] Listening on 127.0.0.1:13051 ...
114
- [FIX-1] pool.get() — attach() will hang, timeout fires after 1500 ms
115
- [pool-state] creating=1 idle=0 inuse=0 pending=0 destroyed=false
116
- [pool-state] creating=1 idle=0 inuse=0 pending=0 destroyed=false
117
- [FIX-1 ✓ OK] callback received error (expected): "Connection timeout after 1500ms"
118
- [pool-state] creating=0 idle=0 inuse=0 pending=0 destroyed=false ← back to 0 ✓
119
- [FIX-4] Queuing a get() in pending, then destroying pool immediately
120
- [FIX-4 ✓ OK] pending callback received error: "Pool is being destroyed" ← immediate ✓
121
- [FIX-4] After destroy — creating=0 idle=0 inuse=0 pending=0 destroyed=true
122
- [FIX-2] pool.get() AFTER pool.destroy() — must be rejected immediately
123
- [FIX-2 ✓ OK] immediately rejected: "Pool has been destroyed" ← no accumulation ✓
124
- [SUMMARY] Fix 1 confirmed : _creating returned to 0 after 1500 ms timeout
125
- [SUMMARY] Fix 2 confirmed : post-destroy get() rejected immediately (_destroyed flag)
126
- [SUMMARY] Fix 4 confirmed : pending callback drained synchronously by destroy()
127
- ```
128
-
129
- ---
130
-
131
- ## Proposed change to `lib/index.d.ts`
132
-
133
- Add `connectTimeout` to the `Options` interface:
134
-
135
- ```typescript
136
- export interface Options {
137
- // ... existing fields ...
138
-
139
- /**
140
- * Timeout in milliseconds for a single pool.get() attach operation.
141
- * If attach() does not complete within this time the slot is freed,
142
- * the caller receives an error, and any late-arriving connection is
143
- * safely discarded. Set to 0 or omit to disable (default: no timeout).
144
- *
145
- * Recommended value: 5000–10000 ms depending on network latency and
146
- * expected Firebird server response time under load.
147
- */
148
- connectTimeout?: number;
149
- }
150
- ```
151
-
152
- ---
153
-
154
- ## Backward compatibility
155
-
156
- All changes are **fully backward-compatible**:
157
-
158
- - `connectTimeout` is optional and defaults to disabled (`undefined > 0` is `false`)
159
- - `_destroyed` defaults to `false` — existing code paths are unchanged
160
- - `destroy()` draining of `pending` is new behaviour but harmless for callers that did not add pending requests before calling `destroy()`