652 lines
22 KiB
JavaScript
652 lines
22 KiB
JavaScript
|
|
// Copyright (c) 2022, 2024, Oracle and/or its affiliates.
|
||
|
|
|
||
|
|
//-----------------------------------------------------------------------------
|
||
|
|
//
|
||
|
|
// This software is dual-licensed to you under the Universal Permissive License
|
||
|
|
// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
||
|
|
// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
|
||
|
|
// either license.
|
||
|
|
//
|
||
|
|
// If you elect to accept the software under the Apache License, Version 2.0,
|
||
|
|
// the following applies:
|
||
|
|
//
|
||
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
|
// you may not use this file except in compliance with the License.
|
||
|
|
// You may obtain a copy of the License at
|
||
|
|
//
|
||
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
//
|
||
|
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
|
// See the License for the specific language governing permissions and
|
||
|
|
// limitations under the License.
|
||
|
|
//
|
||
|
|
//-----------------------------------------------------------------------------
|
||
|
|
|
||
|
|
'use strict';
|
||
|
|
|
||
|
|
const Packet = require("./packet.js");
|
||
|
|
const NTTCP = require("./ntTcp.js");
|
||
|
|
const SessionAtts = require("./sessionAtts.js");
|
||
|
|
const constants = require("./constants.js");
|
||
|
|
const { createNode } = require('./connStrategy.js');
|
||
|
|
const errors = require("../../errors.js");
|
||
|
|
const { findNVPairRecurse, createNVPair, findValue } = require("./nvStrToNvPair.js");
|
||
|
|
const { Buffer } = require('buffer');
|
||
|
|
const EzConnect = require("./ezConnectResolver.js");
|
||
|
|
const { NLParamParser, tnsnamesFilePath } = require("./paramParser.js");
|
||
|
|
const process = require('process');
|
||
|
|
const downHostInstance = require("./connStrategy.js").SOLE_INST_DHCACHE;
|
||
|
|
/**
|
||
|
|
*
|
||
|
|
* @param {string} userConfig
|
||
|
|
* @returns serverinfo
|
||
|
|
*/
|
||
|
|
|
||
|
|
async function getConnectionInfo(userConfig) {
|
||
|
|
const connStr = await resolveConnectStr(userConfig.connectString, userConfig.configDir);
|
||
|
|
const addressNode = await createNode(connStr);
|
||
|
|
let nvpair;
|
||
|
|
if (typeof connStr === 'string')
|
||
|
|
nvpair = createNVPair(connStr);
|
||
|
|
else
|
||
|
|
nvpair = connStr;//Already a NVPair
|
||
|
|
|
||
|
|
const serverVal = findValue(nvpair, ["DESCRIPTION", "CONNECT_DATA", "SERVER"]);
|
||
|
|
const connClass = findValue(nvpair, ["DESCRIPTION", "CONNECT_DATA", "POOL_CONNECTION_CLASS"]);
|
||
|
|
const svcname = findValue(nvpair, ["DESCRIPTION", "CONNECT_DATA", "SERVICE_NAME"]);
|
||
|
|
const sid = findValue(nvpair, ["DESCRIPTION", "CONNECT_DATA", "SID"]);
|
||
|
|
const poolPurity = findValue(nvpair, ["DESCRIPTION", "CONNECT_DATA", "POOL_PURITY"]);
|
||
|
|
|
||
|
|
return [serverVal, connClass, svcname, poolPurity, sid, addressNode];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Resolve the connect string to a NV format address
|
||
|
|
* @param {String} connStr Connect string
|
||
|
|
* @returns AddressNode
|
||
|
|
*/
|
||
|
|
const nlParamParser = new NLParamParser;
|
||
|
|
async function resolveConnectStr(connectString, configDir) {
|
||
|
|
const connStr = connectString.trim();
|
||
|
|
let resolvedVal = connStr;
|
||
|
|
if ((connStr.indexOf(')') == -1) || (connStr.indexOf('(') != 0)) {
|
||
|
|
if ((connStr.indexOf(':') != -1) || (connStr.indexOf('/') != -1)) {
|
||
|
|
const ezcnObj = new EzConnect(connStr);
|
||
|
|
resolvedVal = ezcnObj.getResolvedUrl();
|
||
|
|
return resolvedVal;
|
||
|
|
} else {
|
||
|
|
//try tns alias
|
||
|
|
const namesFilePath = tnsnamesFilePath(configDir);
|
||
|
|
const p = await nlParamParser.initializeNlpa(namesFilePath);
|
||
|
|
resolvedVal = p.get(connStr.toUpperCase());
|
||
|
|
if (!resolvedVal)
|
||
|
|
errors.throwErr(errors.ERR_TNS_ENTRY_NOT_FOUND, connStr, configDir ? configDir + '/tnsnames.ora' : process.env.TNS_ADMIN + '/tnsnames.ora');
|
||
|
|
if (resolvedVal.rhsType == 1) {
|
||
|
|
const rString = resolvedVal.atom;
|
||
|
|
if ((rString.indexOf(':') != -1) || (rString.indexOf('/') != -1)) {
|
||
|
|
return new EzConnect(rString).getResolvedUrl();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
resolvedVal = resolvedVal.getListElement(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
return resolvedVal;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
async function resolveAddress(connStr, configDir) {
|
||
|
|
const connstr = await resolveConnectStr(connStr, configDir);
|
||
|
|
return createNode(connstr);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Timeout function
|
||
|
|
* @param {Promise} asyncPromise input promise
|
||
|
|
* @param {int} timeVal timeout value
|
||
|
|
* @returns resolved value of input promise
|
||
|
|
*/
|
||
|
|
function timeout(asyncPromise, timeVal, oper, address, connID) {
|
||
|
|
let timer;
|
||
|
|
|
||
|
|
const timeoutPromise = new Promise((resolve, reject) => {
|
||
|
|
// max possible value for 32-bit integer
|
||
|
|
if (timeVal > 2147483647)
|
||
|
|
timeVal = 2147483647;
|
||
|
|
|
||
|
|
timer = setTimeout(() => reject(errors.getErr(errors.ERR_CONNECTION_TIMEDOUT, address.host, address.port, oper, timeVal / 1000, connID)), timeVal);
|
||
|
|
});
|
||
|
|
return Promise.race([asyncPromise, timeoutPromise]).then((result) => {
|
||
|
|
clearTimeout(timer);
|
||
|
|
return result;
|
||
|
|
}).catch((err) => {
|
||
|
|
clearTimeout(timer);
|
||
|
|
throw err;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Network Session. This will be used for communication with the server.
|
||
|
|
* @param {object} userConfig Connection options
|
||
|
|
*/
|
||
|
|
class NetworkSession {
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
this.connected = false;
|
||
|
|
this.isBreak = false;
|
||
|
|
this.isReset = false;
|
||
|
|
this.breakPosted = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
async getAddress(addressNode, userConfig) {
|
||
|
|
/* Get the next address */
|
||
|
|
const address = await addressNode.execute(userConfig);
|
||
|
|
/* Prepare connection attributes */
|
||
|
|
const uuid = this.sAtts ? this.sAtts.uuid : null;
|
||
|
|
this.sAtts = new SessionAtts(uuid);
|
||
|
|
this.sAtts.setFrom(userConfig);
|
||
|
|
this.sAtts.setFrom(address.desc.params); /* Resolve attributes from Connect String */
|
||
|
|
await this.sAtts.prepare(address.protocol);
|
||
|
|
|
||
|
|
/* Insert Connection ID */
|
||
|
|
const rootNVPair = createNVPair(address.CNdata.join(""));
|
||
|
|
const cdataNVPair = findNVPairRecurse(rootNVPair, "CONNECT_DATA");
|
||
|
|
const connidStr = `(CONNECTION_ID=${this.sAtts.connectionId})`;
|
||
|
|
const childNVPair = createNVPair(connidStr);
|
||
|
|
cdataNVPair.addListElement(childNVPair);
|
||
|
|
|
||
|
|
this.cData = Buffer.from(rootNVPair.toString(), 'ascii');
|
||
|
|
this.cDataNVPair = rootNVPair;
|
||
|
|
this.sAtts.nt.cDataNVPair = rootNVPair;
|
||
|
|
|
||
|
|
return (address);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Make the transport level connection
|
||
|
|
*/
|
||
|
|
async transportConnect(address) {
|
||
|
|
if (address.protocol.toUpperCase() == 'TCP' && address.httpsProxy) {
|
||
|
|
errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_PARAMETERS, 'https proxy requires protocol as', 'tcps ');
|
||
|
|
}
|
||
|
|
if (address.protocol && (address.protocol.toUpperCase() == 'TCP' || address.protocol.toUpperCase() == 'TCPS')) {
|
||
|
|
this.ntAdapter = new NTTCP(this.sAtts.nt);
|
||
|
|
} else {
|
||
|
|
errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_PARAMETERS, address.protocol + " protocol not supported");
|
||
|
|
}
|
||
|
|
await this.ntAdapter.connect(address);
|
||
|
|
this.ntAdapter.startRead();
|
||
|
|
this.sAtts.ntCha = this.ntAdapter.cha;
|
||
|
|
this.sndDatapkt = new Packet.DataPacket(this.sAtts.largeSDU);
|
||
|
|
this.rcvDatapkt = new Packet.DataPacket(this.sAtts.largeSDU);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Send the NSPTCN(connect) packet
|
||
|
|
* @param {object} connectPkt Connect Packet
|
||
|
|
*/
|
||
|
|
_sendConnect(connectPkt) {
|
||
|
|
this._sendPacket(connectPkt.buf);
|
||
|
|
if (connectPkt.overflow) {
|
||
|
|
this._send(connectPkt.connectData, 0, connectPkt.connectDataLen);
|
||
|
|
this.flush();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Establish network session .Make transport level connection, send NSPTCN(connect packet) and read the response.
|
||
|
|
* @returns NetError.(connection successfully established(NetError.CONNECTED) or reason for failure)
|
||
|
|
*/
|
||
|
|
async connect2(address) {
|
||
|
|
/* Sanitise SDU */
|
||
|
|
if (this.sAtts.sdu) {
|
||
|
|
if (this.sAtts.sdu > constants.NSPABSSDULN) {
|
||
|
|
this.sAtts.sdu = constants.NSPABSSDULN;
|
||
|
|
} else if (this.sAtts.sdu < constants.NSPMNSDULN) {
|
||
|
|
this.sAtts.sdu = constants.NSPMNSDULN;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
this.sAtts.sdu = constants.NSPDFSDULN;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Transport connect */
|
||
|
|
if (this.sAtts.transportConnectTimeout) {
|
||
|
|
const asyncPromise = this.transportConnect(address);
|
||
|
|
await timeout(asyncPromise, this.sAtts.transportConnectTimeout, "transportConnectTimeout", address, this.sAtts.connectionId);
|
||
|
|
} else {
|
||
|
|
await this.transportConnect(address);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Send the connect packet */
|
||
|
|
let connectPkt = new Packet.ConnectPacket(this.cData, this.sAtts);
|
||
|
|
this._sendConnect(connectPkt);
|
||
|
|
|
||
|
|
/* Read the response */
|
||
|
|
while (true) { // eslint-disable-line
|
||
|
|
const packet = await this._recvPacket();
|
||
|
|
if (packet.type === constants.NSPTAC) /* ACCEPT */
|
||
|
|
break;
|
||
|
|
if (packet.type === constants.NSPTRF) { /* REFUSE */
|
||
|
|
if (this.refusePkt.overflow) {
|
||
|
|
await this._recvPacket();
|
||
|
|
this.refusePkt.dataBuf = this.rcvDatapkt.buf.subarray(this.rcvDatapkt.offset, this.rcvDatapkt.len).toString();
|
||
|
|
}
|
||
|
|
const nvpair = createNVPair(this.refusePkt.dataBuf);
|
||
|
|
this.refusePkt = null;
|
||
|
|
const err = findValue(nvpair, ["DESCRIPTION", "ERR"]);
|
||
|
|
if (err == "12514") {
|
||
|
|
errors.throwErr(errors.ERR_INVALID_SERVICE_NAME, this.getOption(constants.SVCNAME), address.host, address.port, this.sAtts.connectionId);
|
||
|
|
} else if (err == "12505") {
|
||
|
|
errors.throwErr(errors.ERR_INVALID_SID, this.getOption(constants.SID), address.host, address.port, this.sAtts.connectionId);
|
||
|
|
} else if (err) {
|
||
|
|
errors.throwErr(errors.ERR_CONNECTION_REFUSED, address.host, address.port, this.sAtts.connectionId, "ORA-" + err);
|
||
|
|
} else {
|
||
|
|
errors.throwErr(errors.ERR_CONNECTION_REFUSED, address.host, address.port, this.sAtts.connectionId, "refused");
|
||
|
|
}
|
||
|
|
} else if (packet.type === constants.NSPTRS) { /* RESEND */
|
||
|
|
if ((packet.flags & constants.NSPFSRN) == constants.NSPFSRN) {
|
||
|
|
await this.ntAdapter.renegTLS();
|
||
|
|
this.ntAdapter.startRead();
|
||
|
|
}
|
||
|
|
this._sendConnect(connectPkt);
|
||
|
|
} else if (packet.type === constants.NSPTRD) { /* REDIRECT */
|
||
|
|
let adrLen, adrStr, redirConnData;
|
||
|
|
|
||
|
|
/* Read and connect to Redirect address */
|
||
|
|
if (this.redirectPkt.overflow) {
|
||
|
|
await this._recvPacket();
|
||
|
|
this.redirectPkt.dataBuf = this.rcvDatapkt.buf.subarray(this.rcvDatapkt.offset, this.rcvDatapkt.len);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this.redirectPkt.flags & constants.NSPFRDS) {
|
||
|
|
adrLen = this.redirectPkt.dataBuf.indexOf('\0', 0, 'ascii');
|
||
|
|
adrStr = this.redirectPkt.dataBuf.toString('ascii', 0, adrLen);
|
||
|
|
redirConnData = this.redirectPkt.dataBuf.subarray(adrLen + 1, this.redirectPkt.dataLen);
|
||
|
|
} else {
|
||
|
|
adrStr = this.redirectPkt.dataBuf.toString('ascii');
|
||
|
|
redirConnData = this.cData;
|
||
|
|
}
|
||
|
|
|
||
|
|
const redirAddressNode = await resolveAddress(adrStr);
|
||
|
|
const host = address.hostname;
|
||
|
|
address = await redirAddressNode.execute();
|
||
|
|
if (address.desc)
|
||
|
|
this.sAtts.setFrom(address.desc.params); /* Add on attributes from redirect connect String */
|
||
|
|
address.originHost = host;
|
||
|
|
this.redirectPkt = null;
|
||
|
|
this.ntAdapter.disconnect(constants.NSFIMM);
|
||
|
|
|
||
|
|
if (this.sAtts.transportConnectTimeout) {
|
||
|
|
const asyncPromise = this.transportConnect(address);
|
||
|
|
await timeout(asyncPromise, this.sAtts.transportConnectTimeout, "transportConnectTimeout", address, this.sAtts.connectionId);
|
||
|
|
} else {
|
||
|
|
await this.transportConnect(address);
|
||
|
|
}
|
||
|
|
|
||
|
|
connectPkt = new Packet.ConnectPacket(redirConnData, this.sAtts, constants.NSPFRDR);
|
||
|
|
this.sndDatapkt = new Packet.DataPacket(this.sAtts.largeSDU);
|
||
|
|
this._sendConnect(connectPkt);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Accepted */
|
||
|
|
this.connected = true;
|
||
|
|
this.cData = null;
|
||
|
|
this.sndDatapkt = new Packet.DataPacket(this.sAtts.largeSDU);
|
||
|
|
this.sndDatapkt.createPacket(constants.NSPDADAT); //Currently only used for disconnect
|
||
|
|
this.sndDatapkt.offset = this.sndDatapkt.dataPtr;
|
||
|
|
this.sndDatapkt.len = this.sndDatapkt.bufLen;
|
||
|
|
this.markerPkt = new Packet.MarkerPacket(this.sAtts.largeSDU);
|
||
|
|
this.controlPkt = new Packet.ControlPacket();
|
||
|
|
this.ntAdapter.largeSDU = this.sAtts.largeSDU;
|
||
|
|
this.sAtts.clearWallet();
|
||
|
|
this.sAtts.nt.walletPassword = null;
|
||
|
|
return (true);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Try all available addresses for connection establishment
|
||
|
|
*/
|
||
|
|
async connect1(address, addressNode, userConfig) {
|
||
|
|
let connected, savedErr;
|
||
|
|
do {
|
||
|
|
try {
|
||
|
|
if (this.sAtts.connectTimeout) {
|
||
|
|
const asyncPromise = this.connect2(address);
|
||
|
|
connected = await timeout(asyncPromise, this.sAtts.connectTimeout, "connectTimeout", address, this.sAtts.connectionId);
|
||
|
|
} else {
|
||
|
|
connected = await this.connect2(address);
|
||
|
|
}
|
||
|
|
} catch (err) {
|
||
|
|
if (err.message.startsWith('NJS-510') && !this.ntAdapter.connected) {
|
||
|
|
downHostInstance.markDownHost(address.host, Date.now()); // mark the host as down
|
||
|
|
this.ntAdapter.connected = true; // Pretend as connected
|
||
|
|
}
|
||
|
|
if (this.ntAdapter) {
|
||
|
|
this.ntAdapter.disconnect(constants.NSFIMM);
|
||
|
|
this.ntAdapter = null;
|
||
|
|
}
|
||
|
|
this.sAtts.clearWallet();
|
||
|
|
connected = false;
|
||
|
|
savedErr = err;
|
||
|
|
try {
|
||
|
|
address = await this.getAddress(addressNode, userConfig);
|
||
|
|
} catch (err) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} while (!connected);
|
||
|
|
if (connected) {
|
||
|
|
return;
|
||
|
|
} else {
|
||
|
|
throw (savedErr);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Process packet (Internal)
|
||
|
|
*/
|
||
|
|
_processPacket(packet) {
|
||
|
|
switch (packet.type) {
|
||
|
|
case constants.NSPTDA: /* DATA packet */
|
||
|
|
this.rcvDatapkt.fromPacket(packet);
|
||
|
|
break;
|
||
|
|
case constants.NSPTMK: /* MARKER packet */
|
||
|
|
this.markerPkt.fromPacket(packet, this);
|
||
|
|
break;
|
||
|
|
case constants.NSPTCNL: /* CONTROL packet */
|
||
|
|
this.controlPkt.fromPacket(packet);
|
||
|
|
break;
|
||
|
|
case constants.NSPTAC: /* ACCEPT */
|
||
|
|
Packet.AcceptPacket(packet, this.sAtts);
|
||
|
|
if (this.sAtts.version >= constants.TNS_VERSION_MIN_END_OF_RESPONSE
|
||
|
|
&& (packet.flags & constants.TNS_ACCEPT_FLAG_HAS_END_OF_REQUEST)) {
|
||
|
|
this.endOfRequestSupport = true;
|
||
|
|
}
|
||
|
|
if (packet.flags & constants.TNS_ACCEPT_FLAG_FAST_AUTH) {
|
||
|
|
this.supportsFastAuth = true;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case constants.NSPTRF: /* REFUSE */
|
||
|
|
this.refusePkt = new Packet.RefusePacket(packet);
|
||
|
|
break;
|
||
|
|
case constants.NSPTRS: /* RESEND */
|
||
|
|
break;
|
||
|
|
case constants.NSPTRD: /* REDIRECT */
|
||
|
|
this.redirectPkt = new Packet.RedirectPacket(packet);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
errors.throwErr(errors.ERR_CONNECTION_INVALID_PACKET);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Receive packet (Internal)
|
||
|
|
* Control packets are consumed internally and discarded
|
||
|
|
*/
|
||
|
|
async _recvPacket() {
|
||
|
|
while (true) { // eslint-disable-line
|
||
|
|
const packet = await this.ntAdapter.receive();
|
||
|
|
if (!packet)
|
||
|
|
break;
|
||
|
|
this._processPacket(packet);
|
||
|
|
if (packet.type !== constants.NSPTCNL)
|
||
|
|
return packet;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Send data (Internal)
|
||
|
|
*/
|
||
|
|
_sendPacket(buf) {
|
||
|
|
this.ntAdapter.send(buf);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Break ongoing operation
|
||
|
|
*/
|
||
|
|
sendBreak() {
|
||
|
|
if (this.isBreak)
|
||
|
|
return; /* Already in a break */
|
||
|
|
|
||
|
|
if (!this.connected) {
|
||
|
|
this.isBreak = true; /* Not yet connected. Post the break */
|
||
|
|
this.breakPosted = true;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.isBreak = true;
|
||
|
|
this.markerPkt.prepare(constants.NSPMKTD1, constants.NIQIMARK);
|
||
|
|
this._sendPacket(this.markerPkt.buf);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Reset the connection
|
||
|
|
*/
|
||
|
|
async reset() {
|
||
|
|
/* If posted send Break */
|
||
|
|
if (this.breakPosted) {
|
||
|
|
this.markerPkt.prepare(constants.NSPMKTD1, constants.NIQBMARK);
|
||
|
|
this._sendPacket(this.markerPkt.buf);
|
||
|
|
this.breakPosted = false;
|
||
|
|
}
|
||
|
|
/* Send Reset */
|
||
|
|
this.markerPkt.prepare(constants.NSPMKTD1, constants.NIQRMARK);
|
||
|
|
this._sendPacket(this.markerPkt.buf);
|
||
|
|
|
||
|
|
/* Await Reset */
|
||
|
|
while (!this.isReset) {
|
||
|
|
await this._recvPacket();
|
||
|
|
}
|
||
|
|
|
||
|
|
/* reset packet buffers */
|
||
|
|
this.sndDatapkt.dataPtr = this.sndDatapkt.dataLen = constants.NSPDADAT;
|
||
|
|
this.sndDatapkt.offset = this.sndDatapkt.dataPtr;
|
||
|
|
this.sndDatapkt.len = this.sndDatapkt.bufLen;
|
||
|
|
|
||
|
|
this.isBreak = this.isReset = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Receive packet
|
||
|
|
*/
|
||
|
|
async recvPacket() {
|
||
|
|
return await this._recvPacket();
|
||
|
|
}
|
||
|
|
|
||
|
|
syncRecvPacket() {
|
||
|
|
while (this.ntAdapter.packets.length > 0) {
|
||
|
|
const packet = this.ntAdapter.syncReceive();
|
||
|
|
if (!packet)
|
||
|
|
break;
|
||
|
|
this._processPacket(packet);
|
||
|
|
if (packet.type !== constants.NSPTCNL)
|
||
|
|
return packet;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Send data
|
||
|
|
* @param {Buffer} userBuf User provided buffer
|
||
|
|
* @param {*} offset from which to send data
|
||
|
|
* @param {*} len number of bytes to send
|
||
|
|
*/
|
||
|
|
_send(userBuf, offset, len) {
|
||
|
|
if (this.isBreak) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
let bytesCopied = 0;
|
||
|
|
|
||
|
|
this.sndDatapkt.dataLen = this.sndDatapkt.offset;
|
||
|
|
if (this.sndDatapkt.dataLen < this.sndDatapkt.bufLen || !this.sndDatapkt.bufLen) {
|
||
|
|
bytesCopied = this.sndDatapkt.fillBuf(userBuf, offset, len);
|
||
|
|
len -= bytesCopied;
|
||
|
|
offset += bytesCopied;
|
||
|
|
this.sndDatapkt.offset = this.sndDatapkt.dataLen;
|
||
|
|
}
|
||
|
|
|
||
|
|
while (len) {
|
||
|
|
this._sendPacket(this.sndDatapkt.dataBuf);
|
||
|
|
|
||
|
|
/* If break throw error now */
|
||
|
|
if (this.isBreak) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.sndDatapkt.dataLen = this.sndDatapkt.dataPtr;
|
||
|
|
this.sndDatapkt.offset = this.sndDatapkt.dataPtr;
|
||
|
|
bytesCopied = this.sndDatapkt.fillBuf(userBuf, offset, len);
|
||
|
|
len -= bytesCopied;
|
||
|
|
offset += bytesCopied;
|
||
|
|
this.sndDatapkt.offset = this.sndDatapkt.dataLen;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Flush send buffers
|
||
|
|
*/
|
||
|
|
flush() {
|
||
|
|
if (this.isBreak) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.sndDatapkt.dataLen = this.sndDatapkt.offset;
|
||
|
|
this.sndDatapkt.prepare2Send();
|
||
|
|
this._sendPacket(Buffer.from(this.sndDatapkt.dataBuf));
|
||
|
|
this.sndDatapkt.dataLen = this.sndDatapkt.dataPtr;
|
||
|
|
this.sndDatapkt.offset = this.sndDatapkt.dataPtr;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Establish network connection
|
||
|
|
*/
|
||
|
|
async connect(userConfig) {
|
||
|
|
const connStr = userConfig.connectString ? userConfig.connectString : userConfig.connectionString;
|
||
|
|
let addressNode;
|
||
|
|
if (userConfig._connInfo) {
|
||
|
|
addressNode = userConfig._connInfo[5];
|
||
|
|
addressNode.reset();
|
||
|
|
} else {
|
||
|
|
addressNode = await resolveAddress(connStr, userConfig.configDir);
|
||
|
|
}
|
||
|
|
let address;
|
||
|
|
try {
|
||
|
|
address = await this.getAddress(addressNode, userConfig);
|
||
|
|
} catch (err) {
|
||
|
|
if (err.message == "All options tried") /* Not even one valid Address */
|
||
|
|
errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_PARAMETERS, "Ensure the ADDRESS parameters have been entered correctly, the most likely incorrect parameter is the host name");
|
||
|
|
else
|
||
|
|
errors.throwErr(errors.ERR_INVALID_CONNECT_STRING_PARAMETERS, err.message);
|
||
|
|
}
|
||
|
|
await this.connect1(address, addressNode, userConfig);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Force Disconnect the stream, primarily used
|
||
|
|
* to disconnect dead/hung connections.
|
||
|
|
*/
|
||
|
|
forceDisconnect(err) {
|
||
|
|
if (!this.connected) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.ntAdapter.stream.destroy(err);
|
||
|
|
this.connected = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Disconnect
|
||
|
|
* @param {int} type of disconnect
|
||
|
|
*/
|
||
|
|
disconnect(type) {
|
||
|
|
if (!this.connected) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (type != constants.NSFIMM && !this.ntAdapter.err) {
|
||
|
|
/* Send EOF packet */
|
||
|
|
this.sndDatapkt.dataLen = this.sndDatapkt.offset;
|
||
|
|
this.sndDatapkt.prepare2Send(constants.NSPDAFEOF);
|
||
|
|
this._sendPacket(this.sndDatapkt.dataBuf);
|
||
|
|
}
|
||
|
|
this.ntAdapter.disconnect(type);
|
||
|
|
this.ntAdapter = null;
|
||
|
|
this.connected = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get connection attributes
|
||
|
|
* @param {int} opcode type of attribute
|
||
|
|
* @returns attribute value
|
||
|
|
*/
|
||
|
|
getOption(opcode) {
|
||
|
|
switch (opcode) {
|
||
|
|
|
||
|
|
case constants.NS_MOREDATA: /* Is there more data in read buffers */
|
||
|
|
return (this.ntAdapter.packets.length > 0);
|
||
|
|
|
||
|
|
case constants.SVCNAME: /* Service name */
|
||
|
|
return findValue(this.cDataNVPair, ["DESCRIPTION", "CONNECT_DATA", "SERVICE_NAME"]);
|
||
|
|
|
||
|
|
case constants.SID: /* Service name */
|
||
|
|
return findValue(this.cDataNVPair, ["DESCRIPTION", "CONNECT_DATA", "SID"]);
|
||
|
|
|
||
|
|
case constants.SERVERTYPE: /* Server type */
|
||
|
|
return findValue(this.cDataNVPair, ["DESCRIPTION", "CONNECT_DATA", "SERVER"]);
|
||
|
|
|
||
|
|
case constants.REMOTEADDR: /* Peer address */
|
||
|
|
if (this.ntAdapter) {
|
||
|
|
return this.ntAdapter.getOption(opcode); // Pass through to NT
|
||
|
|
} else {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
case constants.CONNCLASS: /* Connection Class */
|
||
|
|
return findValue(this.cDataNVPair, ["DESCRIPTION", "CONNECT_DATA", "POOL_CONNECTION_CLASS"]);
|
||
|
|
|
||
|
|
case constants.PURITY: /* Purity */
|
||
|
|
return findValue(this.cDataNVPair, ["DESCRIPTION", "CONNECT_DATA", "POOL_PURITY"]);
|
||
|
|
|
||
|
|
case constants.HEALTHCHECK: /* Is connection healthy */
|
||
|
|
return (this.connected && this.ntAdapter.connected && !this.ntAdapter.err);
|
||
|
|
|
||
|
|
default:
|
||
|
|
errors.throwErr(errors.ERR_INTERNAL, "getOption not supported for opcode " + opcode);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* receive inband notification
|
||
|
|
* @param {Object} obj Return the notification into user provided object
|
||
|
|
* @returns Error number sent from server, or error on the connection. returns 0 if healthy connection
|
||
|
|
*/
|
||
|
|
recvInbandNotif() {
|
||
|
|
let error = 0;
|
||
|
|
if (this.controlPkt.errno) { /* Control pkt already read */
|
||
|
|
error = this.controlPkt.errno;
|
||
|
|
return (error);
|
||
|
|
} else if (!this.getOption(constants.HEALTHCHECK)) {
|
||
|
|
return errors.ERR_CONNECTION_CLOSED;
|
||
|
|
} else {
|
||
|
|
if (this.getOption(constants.NS_MOREDATA)) { //More data available
|
||
|
|
const packet = this.ntAdapter.syncReceive();
|
||
|
|
|
||
|
|
if (packet.type == constants.NSPTCNL) {
|
||
|
|
this.controlPkt.fromPacket(packet);
|
||
|
|
error = this.controlPkt.errno;
|
||
|
|
return (error);
|
||
|
|
} else {
|
||
|
|
this.ntAdapter.packets.unshift(packet); /* Push packet back */
|
||
|
|
return (0);
|
||
|
|
}
|
||
|
|
} else
|
||
|
|
return (0);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = {
|
||
|
|
NetworkSession,
|
||
|
|
resolveAddress,
|
||
|
|
getConnectionInfo
|
||
|
|
};
|