477 lines
17 KiB
JavaScript
477 lines
17 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 { Buffer } = require('buffer');
|
|
const constants = require("../constants.js");
|
|
const errors = require("../../../errors.js");
|
|
|
|
/**
|
|
*
|
|
* Base class for all the RPC messages to support encode/decode functions
|
|
*/
|
|
class Message {
|
|
constructor(connection) {
|
|
this.errorInfo = {};
|
|
this.connection = connection;
|
|
this.messageType = constants.TNS_MSG_TYPE_FUNCTION;
|
|
this.functionCode = 0;
|
|
this.callStatus = 0;
|
|
this.flushOutBinds = false;
|
|
this.endOfResponse = false;
|
|
this.endToEndSeqNum = 0;
|
|
this.errorOccurred = false;
|
|
this.warning = undefined;
|
|
}
|
|
|
|
preProcess() { }
|
|
async postProcess() { }
|
|
|
|
writeFunctionHeader(buf) {
|
|
buf.writeUInt8(this.messageType);
|
|
buf.writeUInt8(this.functionCode);
|
|
buf.writeSeqNum();
|
|
if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_23_1_EXT_1) {
|
|
buf.writeUB8(0); // token number
|
|
}
|
|
}
|
|
|
|
processErrorInfo(buf) {
|
|
this.callStatus = buf.readUB4(); // end of call status
|
|
buf.skipUB2(); // end to end seq number
|
|
buf.skipUB4(); // current row number
|
|
buf.skipUB2(); // error number
|
|
buf.skipUB2(); // array elem error
|
|
buf.skipUB2(); // array elem error
|
|
this.errorInfo.cursorId = buf.readUB2(); // cursor id
|
|
const errorPos = buf.readSB2(); // error position
|
|
buf.skipUB1(); // sql type (19c and earlier)
|
|
buf.skipUB1(); // fatal ?
|
|
buf.skipUB1(); // flags
|
|
buf.skipUB1(); // user cursor options
|
|
buf.skipUB1(); // UPI parameter
|
|
const warnFlag = buf.readUInt8(); // warning flag
|
|
if (warnFlag & constants.TNS_WARN_COMPILATION_CREATE) {
|
|
this.warning = errors.getErr(errors.WRN_COMPILATION_CREATE);
|
|
}
|
|
this.errorInfo.rowID = buf.readRowID(); // rowid
|
|
buf.skipUB4(); // OS error
|
|
buf.skipUB1(); // statement error
|
|
buf.skipUB1(); // call number
|
|
buf.skipUB2(); // padding
|
|
buf.skipUB4(); // success iters
|
|
const numBytes = buf.readUB4(); // oerrdd (logical rowid)
|
|
if (numBytes > 0) {
|
|
buf.skipBytesChunked();
|
|
}
|
|
// batch error codes
|
|
const numErrors = buf.readUB2(); // batch error codes array
|
|
if (numErrors > 0) {
|
|
this.errorInfo.batchErrors = [];
|
|
const firstByte = buf.readUInt8();
|
|
for (let i = 0; i < numErrors; i++) {
|
|
if (firstByte === constants.TNS_LONG_LENGTH_INDICATOR) {
|
|
buf.skipUB4(); // chunk length ignored
|
|
}
|
|
const errorCode = buf.readUB2();
|
|
this.errorInfo.batchErrors.push(new Error(errorCode));
|
|
}
|
|
if (firstByte === constants.TNS_LONG_LENGTH_INDICATOR) {
|
|
buf.skipBytes(1); // ignore end marker
|
|
}
|
|
}
|
|
|
|
// batch error offset
|
|
const numOffsets = buf.readUB4(); // batch error row offset array
|
|
if (numOffsets > 0) {
|
|
if (numOffsets > 65535) {
|
|
errors.throwErr(errors.ERR_TOO_MANY_BATCH_ERRORS);
|
|
}
|
|
const firstByte = buf.readUInt8();
|
|
let offset;
|
|
for (let i = 0; i < numOffsets; i++) {
|
|
if (firstByte === constants.TNS_LONG_LENGTH_INDICATOR) {
|
|
buf.skipUB4(); // chunk length ignored
|
|
}
|
|
offset = buf.readUB4();
|
|
if (i < numErrors) {
|
|
this.errorInfo.batchErrors[i].offset = offset;
|
|
}
|
|
}
|
|
if (firstByte === constants.TNS_LONG_LENGTH_INDICATOR) {
|
|
buf.skipBytes(1); // ignore end marker
|
|
}
|
|
}
|
|
|
|
// batch error messages
|
|
const errMsgArr = buf.readUB2(); // batch error messages array
|
|
if (errMsgArr > 0) {
|
|
buf.skipBytes(1); // ignore packed size
|
|
for (let i = 0; i < errMsgArr; i++) {
|
|
buf.skipUB2(); // skip chunk length
|
|
|
|
this.errorInfo.batchErrors[i].message = buf.readStr(constants.CSFRM_IMPLICIT);
|
|
buf.skipBytes(2); // ignore end marker
|
|
}
|
|
}
|
|
|
|
this.errorInfo.num = buf.readUB4(); // error number (extended)
|
|
this.errorInfo.rowCount = buf.readUB8(); // row number (extended)
|
|
|
|
// fields added in Oracle Database 20c
|
|
if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_20_1) {
|
|
buf.skipUB4(); // sql type
|
|
buf.skipUB4(); // server checksum
|
|
}
|
|
|
|
// error message
|
|
if (this.errorInfo.num !== 0) {
|
|
this.errorOccurred = true;
|
|
if (errorPos >= 0) {
|
|
this.errorInfo.pos = errorPos;
|
|
}
|
|
this.errorInfo.message = buf.readStr(constants.CSFRM_IMPLICIT);
|
|
/*
|
|
* Remove ending newline from ORA error message
|
|
*/
|
|
this.errorInfo.message = this.errorInfo.message.trim();
|
|
}
|
|
this.endOfResponse = !this.connection.nscon.endOfRequestSupport;
|
|
}
|
|
|
|
processReturnParameter() { }
|
|
|
|
processWarningInfo(buf) {
|
|
const errNum = buf.readUB2(); // warning number
|
|
const numBytes = buf.readUB2(); // length of warning message
|
|
buf.skipUB2(); // flags
|
|
if (errNum != 0 && numBytes > 0) {
|
|
// get message string and remove the ending newline.
|
|
const message = buf.readStr(constants.CSFRM_IMPLICIT).trim();
|
|
this.warning = new Error(message);
|
|
this.warning.errorNum = errNum;
|
|
}
|
|
}
|
|
|
|
decode(buf) {
|
|
this.process(buf);
|
|
}
|
|
|
|
process(buf) {
|
|
this.endOfResponse = false;
|
|
this.flushOutBinds = false;
|
|
do {
|
|
this.savePoint(buf);
|
|
const messageType = buf.readUInt8();
|
|
this.processMessage(buf, messageType);
|
|
} while (!this.endOfResponse);
|
|
}
|
|
|
|
savePoint(buf) {
|
|
buf.savePoint();
|
|
}
|
|
|
|
processMessage(buf, messageType) {
|
|
if (messageType === constants.TNS_MSG_TYPE_ERROR) {
|
|
this.processErrorInfo(buf);
|
|
} else if (messageType === constants.TNS_MSG_TYPE_WARNING) {
|
|
this.processWarningInfo(buf);
|
|
} else if (messageType === constants.TNS_MSG_TYPE_STATUS) {
|
|
this.callStatus = buf.readUB4();
|
|
this.endToEndSeqNum = buf.readUB2();
|
|
this.endOfResponse = !this.connection.nscon.endOfRequestSupport;
|
|
} else if (messageType === constants.TNS_MSG_TYPE_PARAMETER) {
|
|
this.processReturnParameter(buf);
|
|
} else if (messageType === constants.TNS_MSG_TYPE_SERVER_SIDE_PIGGYBACK) {
|
|
this.processServerSidePiggyBack(buf);
|
|
} else if (messageType === constants.TNS_MSG_TYPE_END_OF_REQUEST) {
|
|
this.endOfResponse = true;
|
|
} else {
|
|
errors.throwErr(errors.ERR_UNEXPECTED_MESSAGE_TYPE, messageType, buf.pos, buf.packetNum);
|
|
}
|
|
}
|
|
|
|
processServerSidePiggyBack(buf) {
|
|
const opcode = buf.readUInt8();
|
|
if (opcode === constants.TNS_SERVER_PIGGYBACK_LTXID) {
|
|
const num_bytes = buf.readUB4();
|
|
if (num_bytes > 0) {
|
|
buf.skipBytesChunked();
|
|
}
|
|
} else if ((opcode === constants.TNS_SERVER_PIGGYBACK_QUERY_CACHE_INVALIDATION)
|
|
|| (opcode === constants.TNS_SERVER_PIGGYBACK_TRACE_EVENT)) {
|
|
// pass
|
|
} else if (opcode === constants.TNS_SERVER_PIGGYBACK_OS_PID_MTS) {
|
|
const numDtys = buf.readUB2();
|
|
buf.skipUB1();
|
|
buf.skipBytes(numDtys);
|
|
} else if (opcode === constants.TNS_SERVER_PIGGYBACK_SYNC) {
|
|
buf.skipUB2(); // skip number of DTYs
|
|
buf.skipUB1(); // skip length of DTYs
|
|
const num_elements = buf.readUB4();
|
|
buf.skipBytes(1); // skip length
|
|
for (let i = 0; i < num_elements; i++) {
|
|
let temp16 = buf.readUB2();
|
|
if (temp16 > 0) { // skip key
|
|
buf.skipBytesChunked();
|
|
}
|
|
temp16 = buf.readUB2();
|
|
if (temp16 > 0) { // skip value
|
|
buf.skipBytesChunked();
|
|
}
|
|
buf.skipUB2(); // skip flags
|
|
}
|
|
buf.skipUB4(); // skip overall flags
|
|
} else if (opcode === constants.TNS_SERVER_PIGGYBACK_EXT_SYNC) {
|
|
buf.skipUB2();
|
|
buf.skipUB1();
|
|
} else if (opcode === constants.TNS_SERVER_PIGGYBACK_AC_REPLAY_CONTEXT) {
|
|
buf.skipUB2(); // skip number of DTYs
|
|
buf.skipUB1(); // skip length of DTYs
|
|
buf.skipUB4(); // skip flags
|
|
buf.skipUB4(); // skip error code
|
|
buf.skipUB1(); // skip queue
|
|
const num_bytes = buf.readUB4(); // skip replay context
|
|
if (num_bytes > 0) {
|
|
buf.skipBytesChunked();
|
|
}
|
|
} else if (opcode === constants.TNS_SERVER_PIGGYBACK_SESS_RET) {
|
|
buf.skipUB2();
|
|
buf.skipUB1();
|
|
const num_elements = buf.readUB2();
|
|
if (num_elements > 0) {
|
|
buf.skipUB1();
|
|
for (let i = 0; i < num_elements; ++i) {
|
|
let temp16 = buf.readUB2();
|
|
if (temp16 > 0) { // skip key
|
|
buf.skipBytesChunked();
|
|
}
|
|
temp16 = buf.readUB2();
|
|
if (temp16 > 0) { // skip value
|
|
buf.skipBytesChunked();
|
|
}
|
|
buf.skipUB2(); // skip flags
|
|
}
|
|
}
|
|
const flags = buf.readUB4(); // session flags
|
|
if (flags & constants.TNS_SESSGET_SESSION_CHANGED) {
|
|
if (this.connection._drcpEstablishSession) {
|
|
this.connection.statementCache.clearCursors();
|
|
}
|
|
}
|
|
this.connection._drcpEstablishSession = false;
|
|
buf.skipUB4(); // session id
|
|
buf.skipUB2(); // serial number
|
|
} else {
|
|
errors.throwErr(errors.ERR_UNKOWN_SERVER_SIDE_PIGGYBACK, opcode);
|
|
}
|
|
}
|
|
|
|
writePiggybacks(buf) {
|
|
if (this.connection._currentSchemaModified) {
|
|
this._writeCurrentSchemaPiggyback(buf);
|
|
}
|
|
if (this.connection.statementCache._cursorsToClose.size > 0 && !this.connection._drcpEstablishSession) {
|
|
this.writeCloseCursorsPiggyBack(buf);
|
|
}
|
|
if (
|
|
this.connection._actionModified ||
|
|
this.connection._clientIdentifierModified ||
|
|
this.connection._dbopModified ||
|
|
this.connection._clientInfoModified ||
|
|
this.connection._moduleModified
|
|
) {
|
|
this._writeEndToEndPiggybacks(buf);
|
|
}
|
|
if (this.connection._tempLobsTotalSize > 0) {
|
|
this.writeCloseTempLobsPiggyback(buf);
|
|
}
|
|
}
|
|
|
|
writePiggybackHeader(buf, functionCode) {
|
|
buf.writeUInt8(constants.TNS_MSG_TYPE_PIGGYBACK);
|
|
buf.writeUInt8(functionCode);
|
|
buf.writeSeqNum();
|
|
if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_23_1_EXT_1) {
|
|
buf.writeUB8(0); // token number
|
|
}
|
|
}
|
|
|
|
writeCloseCursorsPiggyBack(buf) {
|
|
this.writePiggybackHeader(buf, constants.TNS_FUNC_CLOSE_CURSORS);
|
|
buf.writeUInt8(1);
|
|
this.connection.statementCache.writeCursorsToClose(buf);
|
|
}
|
|
|
|
writeCloseTempLobsPiggyback(buf) {
|
|
const lobsToClose = this.connection._tempLobsToClose;
|
|
const opCode = constants.TNS_LOB_OP_FREE_TEMP | constants.TNS_LOB_OP_ARRAY;
|
|
|
|
this.writePiggybackHeader(buf, constants.TNS_FUNC_LOB_OP);
|
|
|
|
buf.writeUInt8(1); // pointer
|
|
buf.writeUB4(this.connection._tempLobsTotalSize);
|
|
buf.writeUInt8(0); // dest LOB locator
|
|
buf.writeUB4(0);
|
|
buf.writeUB4(0); // source LOB locator
|
|
buf.writeUB4(0);
|
|
buf.writeUInt8(0); // source LOB offset
|
|
buf.writeUInt8(0); // dest LOB offset
|
|
buf.writeUInt8(0); // charset
|
|
buf.writeUB4(opCode);
|
|
buf.writeUInt8(0); // scn
|
|
buf.writeUB4(0); // LOB scn
|
|
buf.writeUB8(0); // LOB scnl
|
|
buf.writeUB8(0);
|
|
buf.writeUInt8(0);
|
|
|
|
// array LOB fields
|
|
buf.writeUInt8(0);
|
|
buf.writeUB4(0);
|
|
buf.writeUInt8(0);
|
|
buf.writeUB4(0);
|
|
buf.writeUInt8(0);
|
|
buf.writeUB4(0);
|
|
for (const val of lobsToClose) {
|
|
buf.writeBytes(val);
|
|
}
|
|
|
|
// Reset Values
|
|
this.connection._tempLobsToClose = [];
|
|
this.connection._tempLobsTotalSize = 0;
|
|
}
|
|
|
|
_writeCurrentSchemaPiggyback(buf) {
|
|
this.writePiggybackHeader(buf, constants.TNS_FUNC_SET_SCHEMA);
|
|
buf.writeUInt8(1);
|
|
const bytes = Buffer.byteLength(this.connection.currentSchema);
|
|
buf.writeUB4(bytes);
|
|
buf.writeBytesWithLength(Buffer.from(this.connection.currentSchema));
|
|
}
|
|
|
|
_writeEndToEndPiggybacks(buf) {
|
|
let flags = 0;
|
|
|
|
// determine which flags to send
|
|
if (this.connection._actionModified) {
|
|
flags |= constants.TNS_END_TO_END_ACTION;
|
|
}
|
|
if (this.connection._clientIdentifierModified) {
|
|
flags |= constants.TNS_END_TO_END_CLIENT_IDENTIFIER;
|
|
}
|
|
if (this.connection._clientInfoModified) {
|
|
flags |= constants.TNS_END_TO_END_CLIENT_INFO;
|
|
}
|
|
if (this.connection._moduleModified) {
|
|
flags |= constants.TNS_END_TO_END_MODULE;
|
|
}
|
|
if (this.connection._dbOpModified) {
|
|
flags |= constants.TNS_END_TO_END_DBOP;
|
|
}
|
|
|
|
// write initial packet data
|
|
this.writePiggybackHeader(buf, constants.TNS_FUNC_SET_END_TO_END_ATTR);
|
|
buf.writeUInt8(0); // pointer (cidnam)
|
|
buf.writeUInt8(0); // pointer (cidser)
|
|
buf.writeUB4(flags);
|
|
|
|
const clientIdentifierBytes = this.writeEndEndTraceValue(buf, this.connection._clientIdentifier, this.connection._clientIdentifierModified);
|
|
const moduleBytes = this.writeEndEndTraceValue(buf, this.connection._module, this.connection._moduleModified);
|
|
const actionBytes = this.writeEndEndTraceValue(buf, this.connection._action, this.connection._actionModified);
|
|
|
|
// write unsupported bits
|
|
buf.writeUInt8(0); // pointer (cideci)
|
|
buf.writeUB4(0); // length (cideci)
|
|
buf.writeUInt8(0); // cidcct
|
|
buf.writeUB4(0); // cidecs
|
|
|
|
const clientInfoBytes = this.writeEndEndTraceValue(buf, this.connection._clientInfo, this.connection._clientInfoModified);
|
|
// write unsupported bits
|
|
buf.writeUInt8(0); // pointer (cideci)
|
|
buf.writeUB4(0); // length (cideci)
|
|
buf.writeUInt8(0); // cidcct
|
|
buf.writeUB4(0); // cidecs
|
|
const dbOpBytes = this.writeEndEndTraceValue(buf, this.connection._dbOp, this.connection._dbOpModified);
|
|
|
|
// write strings
|
|
if (this.connection._clientIdentifierModified && this.connection._clientIdentifier) {
|
|
buf.writeBytesWithLength(clientIdentifierBytes);
|
|
}
|
|
if (this.connection._moduleModified && this.connection._module) {
|
|
buf.writeBytesWithLength(moduleBytes);
|
|
}
|
|
if (this.connection._actionModified && this.connection._action) {
|
|
buf.writeBytesWithLength(actionBytes);
|
|
}
|
|
if (this.connection._clientInfoModified && this.connection._clientInfo) {
|
|
buf.writeBytesWithLength(clientInfoBytes);
|
|
}
|
|
if (this.connection._dbOpModified && this.connection._dbOp) {
|
|
buf.writeBytesWithLength(dbOpBytes);
|
|
}
|
|
|
|
// reset flags and values
|
|
this.connection._actionModified = false;
|
|
this.connection._action = "";
|
|
this.connection._clientIdentifierModified = false;
|
|
this.connection._clientIdentifier = "";
|
|
this.connection._clientInfoModified = false;
|
|
this.connection._clientInfo = "";
|
|
this.connection._dbOpModified = false;
|
|
this.connection._dbOp = "";
|
|
this.connection._moduleModified = false;
|
|
this.connection._module = "";
|
|
}
|
|
|
|
writeEndEndTraceValue(buf, value, modified) {
|
|
// write client identifier header info
|
|
let writtenBytes;
|
|
if (modified) {
|
|
buf.writeUInt8(1); // pointer (client identifier)
|
|
if (value) {
|
|
writtenBytes = Buffer.from(value);
|
|
buf.writeUB4(writtenBytes.length);
|
|
} else {
|
|
buf.writeUB4(0);
|
|
}
|
|
} else {
|
|
buf.writeUInt8(0); // pointer (client identifier)
|
|
buf.writeUB4(0); // length of client identifier
|
|
}
|
|
return writtenBytes;
|
|
}
|
|
|
|
// Called when an error is encountered during decode of RPC
|
|
saveDeferredErr() {
|
|
if (!this.deferredErr) {
|
|
this.deferredErr = errors.getErr(...arguments);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = Message;
|