node-firebird 2.3.2 → 2.3.3

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 (49) hide show
  1. package/README.md +163 -31
  2. package/lib/index.d.ts +12 -0
  3. package/lib/pool.js +10 -1
  4. package/lib/srp.js +12 -1
  5. package/lib/wire/connection.js +39 -7
  6. package/lib/wire/socket.js +13 -1
  7. package/lib/wire/xsqlvar.js +69 -6
  8. package/package.json +1 -1
  9. package/poc/node_modules/.package-lock.json +14 -0
  10. package/poc/node_modules/node-firebird/.eslintrc.json +12 -0
  11. package/poc/node_modules/node-firebird/.github/workflows/codeql.yml +76 -0
  12. package/poc/node_modules/node-firebird/.github/workflows/node.js.yml +95 -0
  13. package/poc/node_modules/node-firebird/BIGINT_MIGRATION.md +374 -0
  14. package/poc/node_modules/node-firebird/CI_DEBUGGING_GUIDE.md +148 -0
  15. package/poc/node_modules/node-firebird/ENCRYPTION_CALLBACK.md +152 -0
  16. package/poc/node_modules/node-firebird/FIREBIRD_LOG_FEATURE.md +145 -0
  17. package/poc/node_modules/node-firebird/LICENSE +373 -0
  18. package/poc/node_modules/node-firebird/MINIMAL_CHANGES_SUMMARY.md +136 -0
  19. package/poc/node_modules/node-firebird/PR_SUMMARY.md +96 -0
  20. package/poc/node_modules/node-firebird/README.md +794 -0
  21. package/poc/node_modules/node-firebird/ROADMAP.md +223 -0
  22. package/poc/node_modules/node-firebird/SRP_PROTOCOL.md +482 -0
  23. package/poc/node_modules/node-firebird/lib/callback.js +38 -0
  24. package/poc/node_modules/node-firebird/lib/firebird.msg +0 -0
  25. package/poc/node_modules/node-firebird/lib/firebird.msg.json +1371 -0
  26. package/poc/node_modules/node-firebird/lib/gdscodes.d.ts +1524 -0
  27. package/poc/node_modules/node-firebird/lib/gdscodes.js +1531 -0
  28. package/poc/node_modules/node-firebird/lib/ieee754-decimal.js +500 -0
  29. package/poc/node_modules/node-firebird/lib/index.d.ts +316 -0
  30. package/poc/node_modules/node-firebird/lib/index.js +128 -0
  31. package/poc/node_modules/node-firebird/lib/messages.js +162 -0
  32. package/poc/node_modules/node-firebird/lib/pool.js +108 -0
  33. package/poc/node_modules/node-firebird/lib/srp.js +299 -0
  34. package/poc/node_modules/node-firebird/lib/unix-crypt.js +343 -0
  35. package/poc/node_modules/node-firebird/lib/utils.js +164 -0
  36. package/poc/node_modules/node-firebird/lib/wire/connection.js +2510 -0
  37. package/poc/node_modules/node-firebird/lib/wire/const.js +807 -0
  38. package/poc/node_modules/node-firebird/lib/wire/database.js +378 -0
  39. package/poc/node_modules/node-firebird/lib/wire/eventConnection.js +118 -0
  40. package/poc/node_modules/node-firebird/lib/wire/fbEventManager.js +326 -0
  41. package/poc/node_modules/node-firebird/lib/wire/serialize.js +588 -0
  42. package/poc/node_modules/node-firebird/lib/wire/service.js +1058 -0
  43. package/poc/node_modules/node-firebird/lib/wire/socket.js +175 -0
  44. package/poc/node_modules/node-firebird/lib/wire/statement.js +48 -0
  45. package/poc/node_modules/node-firebird/lib/wire/transaction.js +206 -0
  46. package/poc/node_modules/node-firebird/lib/wire/xsqlvar.js +703 -0
  47. package/poc/node_modules/node-firebird/package.json +38 -0
  48. package/poc/node_modules/node-firebird/vitest.config.js +24 -0
  49. package/vitest.config.js +3 -1
@@ -0,0 +1,482 @@
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)
@@ -0,0 +1,38 @@
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
+ }