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