node-firebird 2.0.2 → 2.3.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.
- package/.github/workflows/node.js.yml +20 -0
- package/BIGINT_MIGRATION.md +374 -0
- package/CI_DEBUGGING_GUIDE.md +148 -0
- package/FIREBIRD_LOG_FEATURE.md +145 -0
- package/MINIMAL_CHANGES_SUMMARY.md +136 -0
- package/PR_SUMMARY.md +88 -131
- package/README.md +142 -22
- package/ROADMAP.md +223 -0
- package/SRP_PROTOCOL.md +482 -0
- package/lib/ieee754-decimal.js +500 -0
- package/lib/index.d.ts +10 -6
- package/lib/srp.js +65 -50
- package/lib/wire/connection.js +257 -56
- package/lib/wire/const.js +29 -0
- package/lib/wire/database.js +78 -9
- package/lib/wire/eventConnection.js +6 -3
- package/lib/wire/fbEventManager.js +234 -41
- package/lib/wire/serialize.js +39 -0
- package/lib/wire/service.js +102 -94
- package/lib/wire/statement.js +4 -4
- package/lib/wire/transaction.js +27 -9
- package/lib/wire/xsqlvar.js +176 -0
- package/package.json +5 -5
- package/vitest.config.js +12 -1
- package/Roadmap.md +0 -80
package/lib/wire/connection.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const Events = require('events');
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const BigInt = require('big-integer');
|
|
5
4
|
|
|
6
5
|
const {XdrWriter, BlrWriter, XdrReader, BitSet, BlrReader} = require('./serialize');
|
|
7
6
|
const {doCallback, doError} = require('../callback');
|
|
@@ -140,6 +139,7 @@ class Connection {
|
|
|
140
139
|
|
|
141
140
|
self._socket.on('data', function (data) {
|
|
142
141
|
var xdr;
|
|
142
|
+
var hadSavedBuffer = Boolean(self._xdr);
|
|
143
143
|
|
|
144
144
|
if (!self._xdr) {
|
|
145
145
|
xdr = new XdrReader(data);
|
|
@@ -147,6 +147,11 @@ class Connection {
|
|
|
147
147
|
xdr = new XdrReader(Buffer.concat([self._xdr.buffer, data], self._xdr.buffer.length + data.length));
|
|
148
148
|
delete (self._xdr);
|
|
149
149
|
}
|
|
150
|
+
|
|
151
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
152
|
+
console.log('[fb-debug] data event: bytes=%d queue=%d pending=%d xdr.pos=%d savedBuf=%s',
|
|
153
|
+
xdr.buffer.length, self._queue.length, self._pending.length, xdr.pos, hadSavedBuffer);
|
|
154
|
+
}
|
|
150
155
|
|
|
151
156
|
while (xdr.pos < xdr.buffer.length) {
|
|
152
157
|
var cb = self._queue[0], pos = xdr.pos;
|
|
@@ -154,12 +159,30 @@ class Connection {
|
|
|
154
159
|
decodeResponse(xdr, cb, self, self._lowercase_keys, function (err, obj) {
|
|
155
160
|
|
|
156
161
|
if (err) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
self.
|
|
162
|
+
if (err instanceof RangeError) {
|
|
163
|
+
// Genuinely incomplete packet – buffer the remaining bytes
|
|
164
|
+
// and wait for the next 'data' event to reassemble.
|
|
165
|
+
xdr.buffer = xdr.buffer.slice(pos);
|
|
166
|
+
xdr.pos = 0;
|
|
167
|
+
self._xdr = xdr;
|
|
168
|
+
|
|
169
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
170
|
+
console.log('[fb-debug] incomplete packet: saved %d bytes at pos=%d queue=%d',
|
|
171
|
+
xdr.buffer.length, pos, self._queue.length);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (self.accept && self.accept.protocolMinimumType === Const.ptype_lazy_send && self._queue.length > 0) {
|
|
175
|
+
self._queue[0].lazy_count = 2;
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
// Any other error (truly unknown opcode not handled above).
|
|
179
|
+
// Save the buffer so it can be retried, but log a warning.
|
|
180
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
181
|
+
console.warn(`[fb-debug] unhandled protocol error: ${err.message} pos=${pos} bytes=${xdr.buffer.length} queue=${self._queue.length}`);
|
|
182
|
+
}
|
|
183
|
+
xdr.buffer = xdr.buffer.slice(pos);
|
|
184
|
+
xdr.pos = 0;
|
|
185
|
+
self._xdr = xdr;
|
|
163
186
|
}
|
|
164
187
|
return;
|
|
165
188
|
}
|
|
@@ -168,10 +191,27 @@ class Connection {
|
|
|
168
191
|
if (xdr.r) {
|
|
169
192
|
delete (xdr.r);
|
|
170
193
|
}
|
|
194
|
+
|
|
195
|
+
// op_event / op_response_piggyback received on the main connection:
|
|
196
|
+
// data has been consumed by decodeResponse but it does not belong to
|
|
197
|
+
// any queued request – do NOT shift the queue or invoke any pending
|
|
198
|
+
// callback.
|
|
199
|
+
if (obj && obj._isOpEvent) {
|
|
200
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
201
|
+
console.log('[fb-debug] async opcode consumed (ignored): queue=%d xdr.pos=%d remaining=%d',
|
|
202
|
+
self._queue.length, xdr.pos, xdr.buffer.length - xdr.pos);
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
171
206
|
|
|
172
207
|
self._queue.shift();
|
|
173
208
|
self._pending.shift();
|
|
174
209
|
|
|
210
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
211
|
+
console.log('[fb-debug] response dispatched: queue remaining=%d pending remaining=%d xdr.pos=%d',
|
|
212
|
+
self._queue.length, self._pending.length, xdr.pos);
|
|
213
|
+
}
|
|
214
|
+
|
|
175
215
|
if (obj && obj.status) {
|
|
176
216
|
obj.message = lookupMessages(obj.status);
|
|
177
217
|
doCallback(obj, cb);
|
|
@@ -272,6 +312,7 @@ class Connection {
|
|
|
272
312
|
var blr = this._blr;
|
|
273
313
|
|
|
274
314
|
this._pending.push('connect');
|
|
315
|
+
this._authStartTime = Date.now();
|
|
275
316
|
|
|
276
317
|
msg.pos = 0;
|
|
277
318
|
blr.pos = 0;
|
|
@@ -282,7 +323,11 @@ class Connection {
|
|
|
282
323
|
|
|
283
324
|
var specificData = '';
|
|
284
325
|
if (Const.AUTH_PLUGIN_SRP_LIST.indexOf(pluginName) > -1) {
|
|
326
|
+
const _t0 = Date.now();
|
|
285
327
|
this.clientKeys = srp.clientSeed();
|
|
328
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
329
|
+
console.log('[fb-debug] srp.clientSeed: %dms', Date.now() - _t0);
|
|
330
|
+
}
|
|
286
331
|
specificData = this.clientKeys.public.toString(16);
|
|
287
332
|
blr.addMultiblockPart(Const.CNCT_specific_data, specificData, Const.DEFAULT_ENCODING);
|
|
288
333
|
} else if (pluginName === Const.AUTH_PLUGIN_LEGACY) {
|
|
@@ -335,7 +380,7 @@ class Connection {
|
|
|
335
380
|
// Wire encryption: send op_crypt if SRP session key is available
|
|
336
381
|
if (ret.sessionKey && ret.protocolVersion >= Const.PROTOCOL_VERSION13 &&
|
|
337
382
|
options.wireCrypt !== Const.WIRE_CRYPT_DISABLE) {
|
|
338
|
-
var keyBuf = Buffer.from(
|
|
383
|
+
var keyBuf = Buffer.from(ret.sessionKey.toString(16).padStart(40, '0'), 'hex');
|
|
339
384
|
self.sendOpCrypt('Arc4');
|
|
340
385
|
self._socket.enableEncryption(keyBuf);
|
|
341
386
|
self._pending.push('crypt');
|
|
@@ -354,6 +399,9 @@ class Connection {
|
|
|
354
399
|
callback(undefined, ret);
|
|
355
400
|
}
|
|
356
401
|
|
|
402
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
403
|
+
console.log('[fb-debug] auth: op_connect sent plugin=%s t=%dms', pluginName, Date.now() - this._authStartTime);
|
|
404
|
+
}
|
|
357
405
|
this._queueEvent(cb);
|
|
358
406
|
}
|
|
359
407
|
|
|
@@ -408,6 +456,10 @@ class Connection {
|
|
|
408
456
|
if (this.accept.authData) {
|
|
409
457
|
blr.addString(Const.isc_dpb_specific_auth_data, this.accept.authData, Const.DEFAULT_ENCODING);
|
|
410
458
|
}
|
|
459
|
+
|
|
460
|
+
if (options.sessionTimeZone) {
|
|
461
|
+
blr.addString(Const.isc_dpb_session_time_zone, options.sessionTimeZone, Const.DEFAULT_ENCODING);
|
|
462
|
+
}
|
|
411
463
|
|
|
412
464
|
msg.addInt(Const.op_attach);
|
|
413
465
|
msg.addInt(0); // Database Object ID
|
|
@@ -423,6 +475,8 @@ class Connection {
|
|
|
423
475
|
self.dbhandle = ret.handle;
|
|
424
476
|
if (callback)
|
|
425
477
|
callback(undefined, ret);
|
|
478
|
+
if (!db)
|
|
479
|
+
ret.emit('attach', ret);
|
|
426
480
|
}
|
|
427
481
|
|
|
428
482
|
// For reconnect
|
|
@@ -508,6 +562,10 @@ class Connection {
|
|
|
508
562
|
if (this.accept.authData) {
|
|
509
563
|
blr.addString(Const.isc_dpb_specific_auth_data, this.accept.authData, Const.DEFAULT_ENCODING);
|
|
510
564
|
}
|
|
565
|
+
|
|
566
|
+
if (options.sessionTimeZone) {
|
|
567
|
+
blr.addString(Const.isc_dpb_session_time_zone, options.sessionTimeZone, Const.DEFAULT_ENCODING);
|
|
568
|
+
}
|
|
511
569
|
|
|
512
570
|
blr.addNumeric(Const.isc_dpb_sql_dialect, 3);
|
|
513
571
|
blr.addNumeric(Const.isc_dpb_force_write, 1);
|
|
@@ -527,14 +585,12 @@ class Connection {
|
|
|
527
585
|
|
|
528
586
|
if (ret)
|
|
529
587
|
self.dbhandle = ret.handle;
|
|
530
|
-
|
|
531
|
-
setImmediate(function() {
|
|
532
|
-
if (self.db)
|
|
533
|
-
self.db.emit('attach', ret);
|
|
534
|
-
});
|
|
535
|
-
|
|
588
|
+
|
|
536
589
|
if (callback)
|
|
537
590
|
callback(err, ret);
|
|
591
|
+
|
|
592
|
+
if (!err && ret)
|
|
593
|
+
ret.emit('attach', ret);
|
|
538
594
|
}
|
|
539
595
|
|
|
540
596
|
cb.response = new Database(this);
|
|
@@ -910,17 +966,10 @@ class Connection {
|
|
|
910
966
|
}
|
|
911
967
|
|
|
912
968
|
function PrepareParams(params, input, callback) {
|
|
913
|
-
|
|
969
|
+
|
|
914
970
|
var value, meta;
|
|
915
971
|
var ret = new Array(params.length);
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
function done() {
|
|
919
|
-
wait--;
|
|
920
|
-
if (wait === 0)
|
|
921
|
-
callback(ret);
|
|
922
|
-
}
|
|
923
|
-
|
|
972
|
+
|
|
924
973
|
function putBlobData(index, value, callback) {
|
|
925
974
|
|
|
926
975
|
self.createBlob2(transaction, function(err, blob) {
|
|
@@ -943,41 +992,41 @@ class Connection {
|
|
|
943
992
|
self.batchSegments(blob, b, next);
|
|
944
993
|
}, function() {
|
|
945
994
|
ret[index] = new Xsql.SQLParamQuad(blob.oid);
|
|
946
|
-
self.closeBlob(blob, callback);
|
|
995
|
+
self.closeBlob(blob, callback, false);
|
|
947
996
|
});
|
|
948
997
|
return;
|
|
949
998
|
}
|
|
950
|
-
|
|
999
|
+
|
|
951
1000
|
var isReading = false;
|
|
952
1001
|
var isEnd = false;
|
|
953
|
-
|
|
1002
|
+
|
|
954
1003
|
value.on('data', function(chunk) {
|
|
955
1004
|
// Optimization: If chunk is smaller than transfer size, send directly
|
|
956
1005
|
if (chunk.length <= chunkSize) {
|
|
957
1006
|
self.batchSegments(blob, chunk, function () {
|
|
958
1007
|
if (isEnd && !isReading) {
|
|
959
1008
|
ret[index] = new Xsql.SQLParamQuad(blob.oid);
|
|
960
|
-
self.closeBlob(blob, callback);
|
|
1009
|
+
self.closeBlob(blob, callback, false);
|
|
961
1010
|
}
|
|
962
1011
|
});
|
|
963
1012
|
return;
|
|
964
1013
|
}
|
|
965
|
-
|
|
1014
|
+
|
|
966
1015
|
value.pause();
|
|
967
1016
|
isReading = true;
|
|
968
1017
|
bufferReader(chunk, chunkSize, function (b, next) {
|
|
969
1018
|
self.batchSegments(blob, b, next);
|
|
970
1019
|
}, function() {
|
|
971
1020
|
isReading = false;
|
|
972
|
-
|
|
1021
|
+
|
|
973
1022
|
if (isEnd) {
|
|
974
1023
|
ret[index] = new Xsql.SQLParamQuad(blob.oid);
|
|
975
|
-
self.closeBlob(blob, callback);
|
|
1024
|
+
self.closeBlob(blob, callback, false);
|
|
976
1025
|
} else
|
|
977
1026
|
value.resume();
|
|
978
1027
|
});
|
|
979
1028
|
});
|
|
980
|
-
|
|
1029
|
+
|
|
981
1030
|
value.on('end', function() {
|
|
982
1031
|
isEnd = true;
|
|
983
1032
|
if (isReading)
|
|
@@ -986,16 +1035,21 @@ class Connection {
|
|
|
986
1035
|
// If we are reading, the callback in batchSegments/bufferReader will handle closure
|
|
987
1036
|
if (!isReading) {
|
|
988
1037
|
ret[index] = new Xsql.SQLParamQuad(blob.oid);
|
|
989
|
-
self.closeBlob(blob, callback);
|
|
1038
|
+
self.closeBlob(blob, callback, false);
|
|
990
1039
|
}
|
|
991
1040
|
});
|
|
992
1041
|
});
|
|
993
1042
|
}
|
|
994
1043
|
|
|
995
|
-
|
|
1044
|
+
function step(i) {
|
|
1045
|
+
if (i === params.length) {
|
|
1046
|
+
callback(ret);
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
996
1050
|
value = params[i];
|
|
997
1051
|
meta = input[i];
|
|
998
|
-
|
|
1052
|
+
|
|
999
1053
|
if (value === null || value === undefined) {
|
|
1000
1054
|
switch (meta.type) {
|
|
1001
1055
|
case Const.SQL_VARYING:
|
|
@@ -1011,6 +1065,10 @@ class Connection {
|
|
|
1011
1065
|
case Const.SQL_TYPE_DATE:
|
|
1012
1066
|
case Const.SQL_TYPE_TIME:
|
|
1013
1067
|
case Const.SQL_TIMESTAMP:
|
|
1068
|
+
case Const.SQL_TIME_TZ:
|
|
1069
|
+
case Const.SQL_TIME_TZ_EX:
|
|
1070
|
+
case Const.SQL_TIMESTAMP_TZ:
|
|
1071
|
+
case Const.SQL_TIMESTAMP_TZ_EX:
|
|
1014
1072
|
ret[i] = new Xsql.SQLParamDate(null);
|
|
1015
1073
|
break;
|
|
1016
1074
|
case Const.SQL_BLOB:
|
|
@@ -1027,27 +1085,30 @@ class Connection {
|
|
|
1027
1085
|
default:
|
|
1028
1086
|
ret[i] = null;
|
|
1029
1087
|
}
|
|
1030
|
-
|
|
1088
|
+
step(i + 1);
|
|
1031
1089
|
} else {
|
|
1032
1090
|
switch (meta.type) {
|
|
1033
1091
|
case Const.SQL_BLOB:
|
|
1034
|
-
putBlobData(i, value,
|
|
1092
|
+
putBlobData(i, value, function() { step(i + 1); });
|
|
1035
1093
|
break;
|
|
1036
|
-
|
|
1094
|
+
|
|
1037
1095
|
case Const.SQL_TIMESTAMP:
|
|
1038
1096
|
case Const.SQL_TYPE_DATE:
|
|
1039
1097
|
case Const.SQL_TYPE_TIME:
|
|
1040
|
-
|
|
1098
|
+
case Const.SQL_TIME_TZ:
|
|
1099
|
+
case Const.SQL_TIME_TZ_EX:
|
|
1100
|
+
case Const.SQL_TIMESTAMP_TZ:
|
|
1101
|
+
case Const.SQL_TIMESTAMP_TZ_EX:
|
|
1041
1102
|
if (value instanceof Date)
|
|
1042
1103
|
ret[i] = new Xsql.SQLParamDate(value);
|
|
1043
1104
|
else if (typeof(value) === 'string')
|
|
1044
1105
|
ret[i] = new Xsql.SQLParamDate(parseDate(value));
|
|
1045
1106
|
else
|
|
1046
1107
|
ret[i] = new Xsql.SQLParamDate(new Date(value));
|
|
1047
|
-
|
|
1048
|
-
|
|
1108
|
+
|
|
1109
|
+
step(i + 1);
|
|
1049
1110
|
break;
|
|
1050
|
-
|
|
1111
|
+
|
|
1051
1112
|
default:
|
|
1052
1113
|
switch (typeof value) {
|
|
1053
1114
|
case 'bigint':
|
|
@@ -1073,10 +1134,12 @@ class Connection {
|
|
|
1073
1134
|
ret[i] = new Xsql.SQLParamString(value.toString());
|
|
1074
1135
|
break;
|
|
1075
1136
|
}
|
|
1076
|
-
|
|
1137
|
+
step(i + 1);
|
|
1077
1138
|
}
|
|
1078
1139
|
}
|
|
1079
1140
|
}
|
|
1141
|
+
|
|
1142
|
+
step(0);
|
|
1080
1143
|
}
|
|
1081
1144
|
|
|
1082
1145
|
var input = statement.input;
|
|
@@ -1173,6 +1236,11 @@ class Connection {
|
|
|
1173
1236
|
}
|
|
1174
1237
|
msg.addInt(0); // out_message_number = out_message_type
|
|
1175
1238
|
}
|
|
1239
|
+
|
|
1240
|
+
if (this.accept.protocolVersion >= Const.PROTOCOL_VERSION16) {
|
|
1241
|
+
// TODO impl statement timout
|
|
1242
|
+
msg.addInt(statement.options?.timeout || 0); // p_sqldata_timeout
|
|
1243
|
+
}
|
|
1176
1244
|
|
|
1177
1245
|
callback.statement = statement;
|
|
1178
1246
|
this._queueEvent(callback);
|
|
@@ -1208,7 +1276,7 @@ class Connection {
|
|
|
1208
1276
|
|
|
1209
1277
|
fetchAll(statement, transaction, callback) {
|
|
1210
1278
|
const self = this;
|
|
1211
|
-
const custom = statement.
|
|
1279
|
+
const custom = statement.options || {};
|
|
1212
1280
|
const asStream = custom.asStream && custom.on;
|
|
1213
1281
|
const data = asStream ? null : [];
|
|
1214
1282
|
let streamIndex = 0;
|
|
@@ -1272,12 +1340,12 @@ class Connection {
|
|
|
1272
1340
|
}
|
|
1273
1341
|
|
|
1274
1342
|
|
|
1275
|
-
closeBlob(blob, callback) {
|
|
1343
|
+
closeBlob(blob, callback, defer = true) {
|
|
1276
1344
|
var msg = this._msg;
|
|
1277
1345
|
msg.pos = 0;
|
|
1278
1346
|
msg.addInt(Const.op_close_blob);
|
|
1279
1347
|
msg.addInt(blob.handle);
|
|
1280
|
-
this._queueEvent(callback,
|
|
1348
|
+
this._queueEvent(callback, defer);
|
|
1281
1349
|
}
|
|
1282
1350
|
|
|
1283
1351
|
|
|
@@ -1449,9 +1517,16 @@ class Connection {
|
|
|
1449
1517
|
msg.addInt(1); // async
|
|
1450
1518
|
msg.addInt(self.dbhandle);
|
|
1451
1519
|
msg.addInt(0);
|
|
1520
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1521
|
+
console.log('[fb-debug] auxConnection: sending op_connect_request(53) dbhandle=%d queue_before=%d xdr_saved=%s',
|
|
1522
|
+
self.dbhandle, self._queue.length, Boolean(self._xdr));
|
|
1523
|
+
}
|
|
1452
1524
|
function cb(err, ret) {
|
|
1453
1525
|
|
|
1454
1526
|
if (err) {
|
|
1527
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1528
|
+
console.log('[fb-debug] auxConnection: op_connect_request error: %s queue=%d', err.message, self._queue.length);
|
|
1529
|
+
}
|
|
1455
1530
|
doError(err, callback);
|
|
1456
1531
|
return;
|
|
1457
1532
|
}
|
|
@@ -1461,6 +1536,11 @@ class Connection {
|
|
|
1461
1536
|
port: ret.buffer.readUInt16BE(2),
|
|
1462
1537
|
host: ret.buffer.readUInt8(4) + '.' + ret.buffer.readUInt8(5) + '.' + ret.buffer.readUInt8(6) + '.' + ret.buffer.readUInt8(7)
|
|
1463
1538
|
}
|
|
1539
|
+
|
|
1540
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1541
|
+
console.log('[fb-debug] auxConnection: op_response ok → aux family=%d port=%d host=%s queue=%d',
|
|
1542
|
+
socket_info.family, socket_info.port, socket_info.host, self._queue.length);
|
|
1543
|
+
}
|
|
1464
1544
|
|
|
1465
1545
|
callback(undefined, socket_info);
|
|
1466
1546
|
}
|
|
@@ -1470,7 +1550,6 @@ class Connection {
|
|
|
1470
1550
|
|
|
1471
1551
|
queEvents(events, eventid, callback) {
|
|
1472
1552
|
var self = this;
|
|
1473
|
-
console.log('[Connection] queEvents called, eventid:', eventid, 'events:', Object.keys(events), 'isClosed:', this._isClosed);
|
|
1474
1553
|
if (this._isClosed)
|
|
1475
1554
|
return this.throwClosed(callback);
|
|
1476
1555
|
var msg = this._msg;
|
|
@@ -1492,9 +1571,7 @@ class Connection {
|
|
|
1492
1571
|
msg.addInt(0); // args
|
|
1493
1572
|
msg.addInt(eventid);
|
|
1494
1573
|
|
|
1495
|
-
console.log('[Connection] queEvents: Message prepared, queuing event');
|
|
1496
1574
|
function cb(err, ret) {
|
|
1497
|
-
console.log('[Connection] queEvents callback invoked, err:', err, 'ret:', ret);
|
|
1498
1575
|
if (err) {
|
|
1499
1576
|
doError(err, callback);
|
|
1500
1577
|
return;
|
|
@@ -1504,13 +1581,11 @@ class Connection {
|
|
|
1504
1581
|
}
|
|
1505
1582
|
|
|
1506
1583
|
this._queueEvent(cb);
|
|
1507
|
-
console.log('[Connection] queEvents: Event queued');
|
|
1508
1584
|
}
|
|
1509
1585
|
|
|
1510
1586
|
|
|
1511
1587
|
closeEvents(eventid, callback) {
|
|
1512
1588
|
var self = this;
|
|
1513
|
-
console.log('[Connection] closeEvents called, eventid:', eventid, 'isClosed:', this._isClosed);
|
|
1514
1589
|
if (this._isClosed)
|
|
1515
1590
|
return this.throwClosed(callback);
|
|
1516
1591
|
var msg = self._msg;
|
|
@@ -1520,7 +1595,6 @@ class Connection {
|
|
|
1520
1595
|
msg.addInt(eventid);
|
|
1521
1596
|
|
|
1522
1597
|
function cb(err, ret) {
|
|
1523
|
-
console.log('[Connection] closeEvents callback invoked, err:', err);
|
|
1524
1598
|
if (err) {
|
|
1525
1599
|
doError(err, callback);
|
|
1526
1600
|
return;
|
|
@@ -1530,17 +1604,26 @@ class Connection {
|
|
|
1530
1604
|
}
|
|
1531
1605
|
|
|
1532
1606
|
this._queueEvent(cb);
|
|
1533
|
-
console.log('[Connection] closeEvents: Event queued');
|
|
1534
1607
|
}
|
|
1535
1608
|
|
|
1536
1609
|
}
|
|
1537
1610
|
|
|
1611
|
+
// Reverse-lookup table: opcode number → name for FIREBIRD_DEBUG trace logging.
|
|
1612
|
+
const opcodeNames = Object.fromEntries(
|
|
1613
|
+
Object.entries(Const).filter(([k]) => k.startsWith('op_')).map(([k, v]) => [v, k])
|
|
1614
|
+
);
|
|
1615
|
+
|
|
1538
1616
|
function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
1539
1617
|
try {
|
|
1540
1618
|
do {
|
|
1541
1619
|
var r = data.r || data.readInt();
|
|
1542
1620
|
} while (r === Const.op_dummy);
|
|
1543
1621
|
|
|
1622
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1623
|
+
console.log('[fb-debug] decodeResponse: opcode=%d(%s) pos=%d buflen=%d',
|
|
1624
|
+
r, opcodeNames[r] || 'unknown', data.pos, data.buffer.length);
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1544
1627
|
var item, op, response;
|
|
1545
1628
|
|
|
1546
1629
|
switch (r) {
|
|
@@ -1575,7 +1658,7 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1575
1658
|
case Const.op_sql_response:
|
|
1576
1659
|
var statement = callback.statement;
|
|
1577
1660
|
var output = statement.output;
|
|
1578
|
-
var custom = statement.
|
|
1661
|
+
var custom = statement.options || {};
|
|
1579
1662
|
var isOpFetch = r === Const.op_fetch_response;
|
|
1580
1663
|
var _xdrpos;
|
|
1581
1664
|
statement.nbrowsfetched = statement.nbrowsfetched || 0;
|
|
@@ -1762,9 +1845,10 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1762
1845
|
// Server keys
|
|
1763
1846
|
cnx.serverKeys = {
|
|
1764
1847
|
salt: d.buffer.slice(2, saltLen + 2).toString('utf8'),
|
|
1765
|
-
public: BigInt(d.buffer.slice(keyStart, d.buffer.length).toString('utf8')
|
|
1848
|
+
public: BigInt('0x' + d.buffer.slice(keyStart, d.buffer.length).toString('utf8'))
|
|
1766
1849
|
};
|
|
1767
1850
|
|
|
1851
|
+
const _t1 = Date.now();
|
|
1768
1852
|
var proof = srp.clientProof(
|
|
1769
1853
|
cnx.options.user.toUpperCase(),
|
|
1770
1854
|
cnx.options.password,
|
|
@@ -1774,6 +1858,9 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1774
1858
|
cnx.clientKeys.private,
|
|
1775
1859
|
accept.srpAlgo
|
|
1776
1860
|
);
|
|
1861
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1862
|
+
console.log('[fb-debug] srp.clientProof(%s): %dms', accept.srpAlgo, Date.now() - _t1);
|
|
1863
|
+
}
|
|
1777
1864
|
|
|
1778
1865
|
accept.authData = proof.authData.toString(16);
|
|
1779
1866
|
accept.sessionKey = proof.clientSessionKey;
|
|
@@ -1792,9 +1879,20 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1792
1879
|
cnx._socket.enableCompression();
|
|
1793
1880
|
}
|
|
1794
1881
|
|
|
1882
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1883
|
+
console.log('[fb-debug] auth: %s received plugin=%s proto=%d t=%dms',
|
|
1884
|
+
r === Const.op_cond_accept ? 'op_cond_accept' : r === Const.op_accept_data ? 'op_accept_data' : 'op_accept',
|
|
1885
|
+
accept.pluginName, accept.protocolVersion,
|
|
1886
|
+
cnx._authStartTime ? Date.now() - cnx._authStartTime : -1);
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1795
1889
|
// For op_cond_accept: send op_cont_auth and wait for response
|
|
1796
1890
|
if (r === Const.op_cond_accept && accept.authData) {
|
|
1797
1891
|
cnx._pendingAccept = accept;
|
|
1892
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1893
|
+
console.log('[fb-debug] auth: sending op_cont_auth plugin=%s t=%dms',
|
|
1894
|
+
accept.pluginName, cnx._authStartTime ? Date.now() - cnx._authStartTime : -1);
|
|
1895
|
+
}
|
|
1798
1896
|
cnx.sendOpContAuth(
|
|
1799
1897
|
accept.authData,
|
|
1800
1898
|
Const.DEFAULT_ENCODING,
|
|
@@ -1810,8 +1908,43 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1810
1908
|
data.readString(Const.DEFAULT_ENCODING); // plist
|
|
1811
1909
|
data.readString(Const.DEFAULT_ENCODING); // pkey
|
|
1812
1910
|
|
|
1911
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1912
|
+
console.log('[fb-debug] auth: op_cont_auth received plugin=%s pendingAccept=%s t=%dms',
|
|
1913
|
+
pluginName,
|
|
1914
|
+
cnx._pendingAccept ? cnx._pendingAccept.pluginName : 'none',
|
|
1915
|
+
cnx._authStartTime ? Date.now() - cnx._authStartTime : -1);
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
// During SRP mutual authentication, the server sends op_cont_auth
|
|
1919
|
+
// with its proof (M2) after receiving the client's proof (M1).
|
|
1920
|
+
// When we have an active auth exchange for this plugin, just wait
|
|
1921
|
+
// for the subsequent op_accept instead of treating it as an error.
|
|
1922
|
+
if (cnx._pendingAccept && cnx._pendingAccept.pluginName === pluginName) {
|
|
1923
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1924
|
+
console.log('[fb-debug] auth: server SRP proof (M2) received, waiting for op_accept t=%dms',
|
|
1925
|
+
cnx._authStartTime ? Date.now() - cnx._authStartTime : -1);
|
|
1926
|
+
}
|
|
1927
|
+
return; // Server SRP proof received - wait for op_accept
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
// Firebird 4/5 (protocols 16/17) chained-auth: after the client sends
|
|
1931
|
+
// the SRP M1 proof, the server sends op_cont_auth with Legacy_Auth.
|
|
1932
|
+
// SRP has already established the session key; the server additionally
|
|
1933
|
+
// requires a Legacy_Auth verification step before sending op_accept.
|
|
1934
|
+
// Respond with Legacy_Auth credentials and wait for op_accept.
|
|
1935
|
+
if (cnx._pendingAccept && pluginName === Const.AUTH_PLUGIN_LEGACY) {
|
|
1936
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1937
|
+
console.log('[fb-debug] auth: SRP+Legacy_Auth chained-auth (proto %d), sending Legacy_Auth credentials t=%dms',
|
|
1938
|
+
cnx._pendingAccept.protocolVersion,
|
|
1939
|
+
cnx._authStartTime ? Date.now() - cnx._authStartTime : -1);
|
|
1940
|
+
}
|
|
1941
|
+
var legacyAuthData = crypt.crypt(cnx.options.password, Const.LEGACY_AUTH_SALT).substring(2);
|
|
1942
|
+
cnx.sendOpContAuth(legacyAuthData, Const.DEFAULT_ENCODING, pluginName);
|
|
1943
|
+
return; // wait for op_accept
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1813
1946
|
if (!cnx.options.pluginName) {
|
|
1814
|
-
if (cnx.accept.pluginName === pluginName) {
|
|
1947
|
+
if (cnx.accept && cnx.accept.pluginName === pluginName) {
|
|
1815
1948
|
// Erreur plugin not able to connect
|
|
1816
1949
|
return cb(new Error("Unable to connect with plugin " + cnx.accept.pluginName));
|
|
1817
1950
|
}
|
|
@@ -1830,7 +1963,15 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1830
1963
|
}
|
|
1831
1964
|
}
|
|
1832
1965
|
|
|
1833
|
-
|
|
1966
|
+
// Server sent op_cont_auth but we don't know how to handle it.
|
|
1967
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
1968
|
+
console.warn('[fb-debug] auth: op_cont_auth unhandled plugin=%s pendingAccept=%s options.plugin=%s t=%dms',
|
|
1969
|
+
pluginName,
|
|
1970
|
+
cnx._pendingAccept ? cnx._pendingAccept.pluginName : 'none',
|
|
1971
|
+
cnx.options.pluginName || 'none',
|
|
1972
|
+
cnx._authStartTime ? Date.now() - cnx._authStartTime : -1);
|
|
1973
|
+
}
|
|
1974
|
+
return cb(new Error('Unhandled server op_cont_auth for plugin: ' + pluginName));
|
|
1834
1975
|
case Const.op_crypt_key_callback:
|
|
1835
1976
|
// Database encryption key callback
|
|
1836
1977
|
// Read server data (plugin data sent by server)
|
|
@@ -1849,10 +1990,64 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1849
1990
|
|
|
1850
1991
|
// Don't call cb - wait for next operation (likely op_response or another op_crypt_key_callback)
|
|
1851
1992
|
return;
|
|
1993
|
+
case Const.op_event:
|
|
1994
|
+
// op_event may occasionally arrive on the main connection
|
|
1995
|
+
// (e.g. Firebird routing an async notification here instead of
|
|
1996
|
+
// the dedicated aux socket). Consume all its fields so the
|
|
1997
|
+
// buffer position advances correctly, then signal the data
|
|
1998
|
+
// handler to skip queue manipulation for this frame.
|
|
1999
|
+
//
|
|
2000
|
+
// Firebird wire protocol – op_event payload (remote protocol):
|
|
2001
|
+
// p_event_database : Int32 – database handle
|
|
2002
|
+
// p_event_items : Array – event parameter block (EPB)
|
|
2003
|
+
// p_event_ast : Int64 – AST routine pointer (0 for remote)
|
|
2004
|
+
// p_event_rid : Int32 – remote event ID
|
|
2005
|
+
{
|
|
2006
|
+
const evtDb = data.readInt(); // p_event_database
|
|
2007
|
+
data.readArray(); // p_event_items (EPB buffer)
|
|
2008
|
+
data.readInt64(); // p_event_ast
|
|
2009
|
+
const evtRid = data.readInt(); // p_event_rid
|
|
2010
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
2011
|
+
console.log('[fb-debug] op_event on main connection: db=%d rid=%d (consumed, not queued)', evtDb, evtRid);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
return cb(null, { _isOpEvent: true });
|
|
2015
|
+
case Const.op_response_piggyback:
|
|
2016
|
+
// Firebird 5 (Protocol 16/17) sends op_response_piggyback (72)
|
|
2017
|
+
// as an unsolicited cleanup notification after certain operations
|
|
2018
|
+
// (e.g. after the EventConnection aux socket is torn down).
|
|
2019
|
+
// It has the same wire layout as op_response but does NOT
|
|
2020
|
+
// correspond to any queued client request. Parse and discard it
|
|
2021
|
+
// so that the xdr buffer position advances correctly, then signal
|
|
2022
|
+
// the data handler to skip queue manipulation.
|
|
2023
|
+
//
|
|
2024
|
+
// Wire layout (identical to op_response):
|
|
2025
|
+
// handle : Int32
|
|
2026
|
+
// object : Quad (2x Int32)
|
|
2027
|
+
// data : Array
|
|
2028
|
+
// status : status-vector ending with isc_arg_end
|
|
2029
|
+
parseOpResponse(data, {}, function(err) {
|
|
2030
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
2031
|
+
if (err) {
|
|
2032
|
+
console.warn('[fb-debug] op_response_piggyback parse error:', err.message);
|
|
2033
|
+
} else {
|
|
2034
|
+
console.log('[fb-debug] op_response_piggyback consumed (unsolicited Firebird 5 cleanup)');
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
});
|
|
2038
|
+
return cb(null, { _isOpEvent: true });
|
|
1852
2039
|
default:
|
|
2040
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
2041
|
+
console.warn('[fb-debug] unknown opcode=%d at pos=%d buflen=%d queue=%d',
|
|
2042
|
+
r, data.pos, data.buffer.length, cnx && cnx._queue ? cnx._queue.length : 0);
|
|
2043
|
+
}
|
|
1853
2044
|
return cb(new Error('Unexpected:' + r));
|
|
1854
2045
|
}
|
|
1855
2046
|
} catch (err) {
|
|
2047
|
+
if (process.env.FIREBIRD_DEBUG) {
|
|
2048
|
+
console.warn('[fb-debug] decodeResponse exception: %s (RangeError=%s) pos=%d buflen=%d',
|
|
2049
|
+
err.message, err instanceof RangeError, data.pos, data.buffer.length);
|
|
2050
|
+
}
|
|
1856
2051
|
if (err instanceof RangeError) {
|
|
1857
2052
|
return cb(err);
|
|
1858
2053
|
}
|
|
@@ -1980,6 +2175,10 @@ function describe(buff, statement) {
|
|
|
1980
2175
|
case Const.SQL_TYPE_DATE: param = new Xsql.SQLVarDate(); break;
|
|
1981
2176
|
case Const.SQL_TYPE_TIME: param = new Xsql.SQLVarTime(); break;
|
|
1982
2177
|
case Const.SQL_TIMESTAMP: param = new Xsql.SQLVarTimeStamp(); break;
|
|
2178
|
+
case Const.SQL_TIME_TZ: param = new Xsql.SQLVarTimeTz(); break;
|
|
2179
|
+
case Const.SQL_TIME_TZ_EX: param = new Xsql.SQLVarTimeTzEx(); break;
|
|
2180
|
+
case Const.SQL_TIMESTAMP_TZ: param = new Xsql.SQLVarTimeStampTz(); break;
|
|
2181
|
+
case Const.SQL_TIMESTAMP_TZ_EX: param = new Xsql.SQLVarTimeStampTzEx(); break;
|
|
1983
2182
|
case Const.SQL_BLOB: param = new Xsql.SQLVarBlob(); break;
|
|
1984
2183
|
case Const.SQL_ARRAY: param = new Xsql.SQLVarArray(); break;
|
|
1985
2184
|
case Const.SQL_QUAD: param = new Xsql.SQLVarQuad(); break;
|
|
@@ -1987,6 +2186,8 @@ function describe(buff, statement) {
|
|
|
1987
2186
|
case Const.SQL_SHORT: param = new Xsql.SQLVarShort(); break;
|
|
1988
2187
|
case Const.SQL_INT64: param = new Xsql.SQLVarInt64(); break;
|
|
1989
2188
|
case Const.SQL_INT128: param = new Xsql.SQLVarInt128(); break;
|
|
2189
|
+
case Const.SQL_DEC16: param = new Xsql.SQLVarDecFloat16(); break;
|
|
2190
|
+
case Const.SQL_DEC34: param = new Xsql.SQLVarDecFloat34(); break;
|
|
1990
2191
|
case Const.SQL_BOOLEAN: param = new Xsql.SQLVarBoolean(); break;
|
|
1991
2192
|
default:
|
|
1992
2193
|
throw new Error('Unexpected');
|