From b9002121aff55c30a99a415cb1a9490c0f38e689 Mon Sep 17 00:00:00 2001 From: he-is-harry Date: Fri, 31 Jan 2025 10:21:42 -0500 Subject: [PATCH] Added packet tracing - Packet tracing can be turned on by setting the HDB_TRACE environment variable - To turn off packet tracing unset the HDB_TRACE environment variable (`unset HDB_TRACE`) - Trace files are saved with the name 'hdb.trace.{timestamp / 1000}.log' Changes - Added the PacketTracer.js file to parse packets and return the message to log in the trace file - Rearranged the locations of traces in Connection.js to trace with the raw packet instead of segment objects - Added two unit tests to check the packet tracer --- lib/protocol/Connection.js | 11 +- lib/protocol/MessageBuffer.js | 15 ++ lib/util/index.js | 22 ++- lib/util/trace/PacketTracer.js | 315 +++++++++++++++++++++++++++++++ test/fixtures/packetTraceData.js | 181 ++++++++++++++++++ test/util.index.js | 4 + test/util.trace.js | 60 ++++++ 7 files changed, 597 insertions(+), 11 deletions(-) create mode 100644 lib/util/trace/PacketTracer.js create mode 100644 test/fixtures/packetTraceData.js create mode 100644 test/util.trace.js diff --git a/lib/protocol/Connection.js b/lib/protocol/Connection.js index 1b998e1..b005438 100644 --- a/lib/protocol/Connection.js +++ b/lib/protocol/Connection.js @@ -218,6 +218,7 @@ Connection.prototype.open = function open(options, cb) { function ondata(chunk) { cleanup(); + trace('INITIALIZATION REPLY', chunk); if (!chunk || chunk.length < InitializationReply.LENGTH) { return cb(invalidInitializationReply()); } @@ -233,6 +234,7 @@ Connection.prototype.open = function open(options, cb) { self.port = options['port']; timeoutObject = setTimeout(onerror, self.initializationTimeout, initializationTimeoutError()); socket.write(initializationRequestBuffer); + trace('INITIALIZATION REQUEST', initializationRequestBuffer); }); socket.once('error', onerror); socket.on('data', ondata); @@ -254,6 +256,8 @@ Connection.prototype._addListeners = function _addListeners(socket) { function ondata(chunk) { packet.push(chunk); if (packet.isReady()) { + trace('REPLY', packet.getTraceData()); + if (self._state.sessionId !== packet.header.sessionId) { self._state.sessionId = packet.header.sessionId; self._state.packetCount = -1; @@ -321,9 +325,6 @@ Connection.prototype.send = function send(message, receive) { message.add(PartKind.CLIENT_INFO, this._clientInfo.getUpdatedProperties()); } - debug('send', message); - trace('REQUEST', message); - var size = this.packetSizeLimit - PACKET_HEADER_LENGTH; var buffer = message.toBuffer(size); if(buffer.length > size) { @@ -354,6 +355,9 @@ Connection.prototype.send = function send(message, receive) { if (this._socket) { this._socket.write(packet); } + + debug('send', message); + trace('REQUEST', packet, this.connectOptions.connectionId); }; @@ -389,7 +393,6 @@ Connection.prototype.setTransactionFlags = function setTransactionFlags(flags) { Connection.prototype._parseReplySegment = function _parseReplySegment(buffer) { var segment = ReplySegment.create(buffer, 0); - trace(segment.kind === SegmentKind.ERROR ? 'ERROR' : 'REPLY', segment); return segment.getReply(); }; diff --git a/lib/protocol/MessageBuffer.js b/lib/protocol/MessageBuffer.js index 84f2603..a36549a 100644 --- a/lib/protocol/MessageBuffer.js +++ b/lib/protocol/MessageBuffer.js @@ -24,6 +24,7 @@ function MessageBuffer() { this.length = 0; this.header = undefined; this.data = undefined; + this.headerBuffer = undefined; } MessageBuffer.prototype.isReady = function () { @@ -61,12 +62,26 @@ MessageBuffer.prototype.readHeader = function readHeader() { packetCount: buffer.readUInt32LE(8), length: buffer.readUInt32LE(12) }; + if (util.isTraceOn()) { + this.headerBuffer = buffer.slice(0, PACKET_HEADER_LENGTH); + } this.data = buffer.slice(PACKET_HEADER_LENGTH); this.length -= PACKET_HEADER_LENGTH; }; +MessageBuffer.prototype.getTraceData = function getTraceData() { + if (util.isTraceOn()) { + if (Array.isArray(this.data)) { + return Buffer.concat([this.headerBuffer].concat(this.data), this.length + PACKET_HEADER_LENGTH); + } + return Buffer.concat([this.headerBuffer, this.data], this.length + PACKET_HEADER_LENGTH); + } + return undefined; +} + MessageBuffer.prototype.clear = function clear() { this.length = 0; this.header = undefined; this.data = undefined; + this.headerBuffer = undefined; }; \ No newline at end of file diff --git a/lib/util/index.js b/lib/util/index.js index 870d301..e6b985c 100644 --- a/lib/util/index.js +++ b/lib/util/index.js @@ -20,6 +20,7 @@ var stream = require('stream'); var path = require('path'); var os = require('os'); var fs = require('fs'); +var packetTracer = require('./trace/PacketTracer'); Object.defineProperties(exports, { setImmediate: { @@ -53,10 +54,13 @@ var debug = exports.debuglog('hdb_util'); exports.tracefile = function tracefile() { var timestamp = Math.floor(Date.now() / 1000); var filename = 'hdb.trace.' + timestamp + '.log'; - return path.join(os.tmpdir(), filename); + return filename; }; exports._appendFileSync = fs.appendFileSync; +exports.isTraceOn = function isTraceOn() { + return !!process.env.HDB_TRACE; +} exports.tracelog = function tracelog() { if (!process.env.HDB_TRACE) { return function dummyTracelog() {}; @@ -64,12 +68,16 @@ exports.tracelog = function tracelog() { var filename = exports.tracefile(); debug('Trace to file', filename); - return function tracelog(kind, segment) { - exports._appendFileSync(filename, - kind + '\n' + - util.inspect(segment, { - depth: 9 - })); + return function tracelog(kind, buffer, connectionId) { + if (kind === 'INITIALIZATION REQUEST') { + exports._appendFileSync(filename, packetTracer.parseInitializationRequest(buffer)); + } else if (kind === 'INITIALIZATION REPLY') { + exports._appendFileSync(filename, packetTracer.parseInitializationReply(buffer)); + } else if (kind === 'REQUEST') { + exports._appendFileSync(filename, packetTracer.parseRequest(buffer, connectionId)); + } else if (kind === 'ERROR' || kind === 'REPLY') { + exports._appendFileSync(filename, packetTracer.parseReply(buffer)); + } }; }; diff --git a/lib/util/trace/PacketTracer.js b/lib/util/trace/PacketTracer.js new file mode 100644 index 0000000..5ede4ce --- /dev/null +++ b/lib/util/trace/PacketTracer.js @@ -0,0 +1,315 @@ +// Copyright 2013 SAP AG. +// +// 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 +// +// http: //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'; + +var zeropad = require('../zeropad'); +var bignum = require('../bignum'); +var MessageType = require('../../protocol/common/MessageType'); +var SegmentKind = require('../../protocol/common/SegmentKind'); +var CommandOption = require('../../protocol/common/CommandOption'); +var PartKind = require('../../protocol/common/PartKind'); +var PartAttributes = require('../../protocol/common/ResultSetAttributes'); +var FunctionCode = require('../../protocol/common/FunctionCode'); + +var MessageTypeMap = invertToMap(MessageType); +var SegmentKindMap = invertToMap(SegmentKind); +var CommandOptionMap = invertToMap(CommandOption); +var PartKindMap = invertToMap(PartKind); +var PartAttributesMap = invertToMap(PartAttributes); +var FunctionCodeMap = invertToMap(FunctionCode); + +function invertToMap(object) { + var map = new Map(); + Object.keys(object).forEach(key => { + map.set(object[key], key); + }); + return map; +} + +/* + The initializationRequestBuffer is formatted as + 0xff, 0xff, 0xff, 0xff, // preamble + 4, 20, 0, // client version + 4, // request major version - 0x04 + 1, 0, // request minor version - 0x01 + 0, // reserved + 1, // number of options + 1, 1 // option_id: 1 - endian, value: 01 - LE (00 - BE) +*/ +function parseInitializationRequest(buffer) { + var traceOutput = new LogWrapper(); + var log = traceOutput.log.bind(traceOutput); + var logData = traceOutput.logData.bind(traceOutput); + log(0, " " + getLocalTimestamp()); + log(4, `INFO REQUEST (${buffer.length} BYTES)`); + var requestMajorVersion = buffer.readInt8(7); + var requestMinorVersion = buffer.readInt16LE(8); + log(2, `REQUEST PROTOCOL VERSION: ${requestMajorVersion}.${requestMinorVersion}`); + var numOptions = buffer.readInt8(11); + if (numOptions >= 1) { + for (var i = 12; i < 12 + 2 * numOptions; i += 2) { + if (buffer.readInt8(i) === 1) { + if (buffer.readInt8(i + 1) === 1) { + log(0, "ENDIAN: LE"); + } else if (buffer.readInt8(i + 1) === 0) { + log(0, "ENDIAN: BE"); + } + } + } + } + traceOutput.indent += 2; + logData(formatHexData(traceOutput.indent, buffer, 0, buffer.length)); + traceOutput.clearIndentLog(''); + return traceOutput.logStr; +} +exports.parseInitializationRequest = parseInitializationRequest; + +function parseInitializationReply(buffer) { + var traceOutput = new LogWrapper(); + var log = traceOutput.log.bind(traceOutput); + var logData = traceOutput.logData.bind(traceOutput); + log(0, " " + getLocalTimestamp()); + log(4, `INFO REQUEST REPLY (${buffer.length} BYTES)`); + traceOutput.indent += 4; + logData(formatHexData(traceOutput.indent, buffer, 0, buffer.length)); + traceOutput.clearIndentLog(''); + return traceOutput.logStr; +} +exports.parseInitializationReply = parseInitializationReply; + +function parseRequest(buffer, connectionId) { + var traceOutput = new LogWrapper(); + var log = traceOutput.log.bind(traceOutput); + var logData = traceOutput.logData.bind(traceOutput); + + var sessionId = bignum.readInt64LE(buffer, 0); + var packetCount = buffer.readUInt32LE(8); + var varPartLength = buffer.readUInt32LE(12); + var varPartSize = buffer.readUInt32LE(16); + var numSegments = buffer.readUInt16LE(20); + var packetOptions = buffer.readInt8(22); + + if (connectionId) { + log(0, `CONNECTION ID: ${connectionId}`); + } + log(0, " " + getLocalTimestamp()); + // Shift indent up 2 spaces + traceOutput.indent += 2; + if (packetOptions === 2) { + var compressionVarpartLength = buffer.readUInt32LE(24); + log(0, `COMPRESSED PACKET DECOMPRESSED VARPART LENGTH: ${compressionVarpartLength}`); + } else if (packetOptions === 3) { + log(0, "SUPPORTS REATTACH"); + } + log(0, `SESSION ID: ${sessionId} PACKET COUNT: ${packetCount}`); + log(0, `VARPART LENGTH: ${varPartLength} VARPART SIZE: ${varPartSize}`); + log(0, `NO OF SEGMENTS: ${numSegments}`); + + var curSegmentOffset = 32; // Message header is 32 bytes + for (var segIndex = 1; segIndex <= numSegments; segIndex++) { + var segmentLength = buffer.readInt32LE(curSegmentOffset); + var segmentOffset = buffer.readInt32LE(curSegmentOffset + 4); + var numParts = buffer.readInt16LE(curSegmentOffset + 8); + var segmentNumber = buffer.readInt16LE(curSegmentOffset + 10); + var segmentKind = SegmentKindMap.get(buffer.readInt8(curSegmentOffset + 12)); + var messageType = MessageTypeMap.get(buffer.readInt8(curSegmentOffset + 13)); + var autoCommit = buffer.readInt8(curSegmentOffset + 14); + var commandOptions = readBitOptions(buffer.readInt8(curSegmentOffset + 15), CommandOptionMap); + + log(2, `SEGMENT ${segIndex} OF ${numSegments} MESSAGE TYPE: ${messageType}`); + log(2, `LENGTH: ${segmentLength} OFFSET: ${segmentOffset}`); + log(0, `NO OF PARTS: ${numParts} NUMBER: ${segmentNumber}`); + log(0, `KIND: ${segmentKind} AUTOCOMMIT: ${autoCommit}`); + log(0, `OPTIONS: ${commandOptions}`); + + parseParts(buffer, curSegmentOffset, numParts, traceOutput, log, logData); + + curSegmentOffset += segmentLength; + } + + traceOutput.clearIndentLog(''); + return traceOutput.logStr; +} +exports.parseRequest = parseRequest; + +function parseReply(buffer) { + var traceOutput = new LogWrapper(); + var log = traceOutput.log.bind(traceOutput); + var logData = traceOutput.logData.bind(traceOutput); + + var sessionId = bignum.readInt64LE(buffer, 0); + var packetCount = buffer.readUInt32LE(8); + var varPartLength = buffer.readUInt32LE(12); + var varPartSize = buffer.readUInt32LE(16); + var numSegments = buffer.readUInt16LE(20); + var packetOptions = buffer.readInt8(22); + + log(0, " " + getLocalTimestamp()); + // Shift indent up 2 spaces + traceOutput.indent += 2; + if (packetOptions === 2) { + var compressionVarpartLength = buffer.readUInt32LE(24); + log(0, `COMPRESSED PACKET DECOMPRESSED VARPART LENGTH: ${compressionVarpartLength}`); + } else if (packetOptions === 3) { + log(0, "INITIATING SESSION REATTACH"); + } + log(0, `SESSION ID: ${sessionId} PACKET COUNT: ${packetCount}`); + log(0, `VARPART LENGTH: ${varPartLength} VARPART SIZE: ${varPartSize}`); + log(0, `NO OF SEGMENTS: ${numSegments}`); + + var curSegmentOffset = 32; // Message header is 32 bytes + for (var segIndex = 1; segIndex <= numSegments; segIndex++) { + var segmentLength = buffer.readInt32LE(curSegmentOffset); + var segmentOffset = buffer.readInt32LE(curSegmentOffset + 4); + var numParts = buffer.readInt16LE(curSegmentOffset + 8); + var segmentNumber = buffer.readInt16LE(curSegmentOffset + 10); + var segmentKind = SegmentKindMap.get(buffer.readInt8(curSegmentOffset + 12)); + var functionCode = FunctionCodeMap.get(buffer.readInt16LE(curSegmentOffset + 14)); + + log(2, `SEGMENT ${segIndex}`); + log(2, `LENGTH: ${segmentLength} OFFSET: ${segmentOffset}`); + log(0, `NO OF PARTS: ${numParts} NUMBER: ${segmentNumber}`); + log(0, `KIND: ${segmentKind}`); + log(0, `FUNCTION CODE: ${functionCode}`); + + parseParts(buffer, curSegmentOffset, numParts, traceOutput, log, logData); + + curSegmentOffset += segmentLength; + } + + traceOutput.clearIndentLog(''); + return traceOutput.logStr; +} +exports.parseReply = parseReply; + +function parseParts(buffer, curSegmentOffset, numParts, traceOutput, log, logData) { + var curPartOffset = curSegmentOffset + 24; + for (var partIndex = 1; partIndex <= numParts; partIndex++) { + var partKind = PartKindMap.get(buffer.readInt8(curPartOffset)); + var partAttributes = readBitOptions(buffer.readInt8(curPartOffset + 1), PartAttributesMap); + var argumentCount = buffer.readInt16LE(curPartOffset + 2); + if (argumentCount === -1) { + argumentCount = buffer.readInt32LE(curPartOffset + 4); + } + var bufferLength = buffer.readInt32LE(curPartOffset + 8); + var alignedBufferLength = alignLength(bufferLength, 8); + var bufferSize = buffer.readInt32LE(curPartOffset + 12); + + log(0, `PART ${partIndex} ${partKind}`); + log(2, `LENGTH: ${bufferLength} SIZE: ${bufferSize}`); + log(0, `ARGUMENTS: ${argumentCount}`); + log(0, `ATTRIBUTES: ${partAttributes}`); + log(0, "DATA:"); + + if (buffer.readInt8(curPartOffset) === PartKind.AUTHENTICATION) { + // Do not log authentication information + log(0, "[AUTHENTICATION INFORMATION]"); + } else if (bufferLength > 0) { + // Part header is 16 bytes + logData(formatHexData(traceOutput.indent, buffer, curPartOffset + 16, curPartOffset + 16 + bufferLength)); + } + + // Shift indent back 2 spaces + traceOutput.indent -= 2; + curPartOffset += 16 + alignedBufferLength; + } +} + +function formatBufferRow(indent, buffer, curIndex, offset, end) { + var calcEnd = offset + curIndex + 16; + var trueEnd; + if (end < calcEnd) trueEnd = end; + else trueEnd = calcEnd; + var hexEncodingStr = ""; + var humanReadableStr = ""; + + for (var i = offset + curIndex; i < trueEnd; i++) { + hexEncodingStr += zeropad.lpad(2, buffer[i].toString(16).toUpperCase()); + if (i < trueEnd - 1) hexEncodingStr += " "; + + if (buffer[i] >= 32 && buffer[i] <= 127) { + humanReadableStr += String.fromCharCode(buffer[i]); + } else { + humanReadableStr += '.'; + } + } + + var indexStr = curIndex.toString(16).toUpperCase() + "|"; + indexStr = indexStr.padStart(indent, ' '); + + // Pad the end of the hex encoding to be 47 in length, the length when + // all 16 bytes are set. Similarly, pad the humanReadableStr to 16 + return indexStr + hexEncodingStr.padEnd(47, ' ') + "|" + humanReadableStr.padEnd(16) + "|"; +} + +function formatHexData(indent, buffer, offset, end) { + // Split the data into rows of 16 bytes + var hexDataStr = ""; + var endIndex = end - offset; + for (var i = 0; i < endIndex; i += 16) { + hexDataStr += formatBufferRow(indent, buffer, i, offset, end) + "\n"; + } + return hexDataStr; +} + +function getLocalTimestamp() { + var curDate = new Date(); + return curDate.getFullYear() + '-' + + zeropad.lpad(2, curDate.getMonth() + 1) + '-' + + zeropad.lpad(2, curDate.getDate()) + ' ' + + zeropad.lpad(2, curDate.getHours()) + ':' + + zeropad.lpad(2, curDate.getMinutes()) + ':' + + zeropad.lpad(2, curDate.getSeconds()) + '.' + + zeropad.lpad(3, curDate.getMilliseconds()); +} + +function readBitOptions(options, optionMap) { + var firstOption = true; + var result = "("; + optionMap.forEach((value, key) => { + if (options & key) { + if (firstOption) firstOption = false; + else result += "|"; + result += value; + } + }); + result += ")"; + return result; +} + +function alignLength(length, alignment) { + if (length % alignment === 0) { + return length; + } + return length + alignment - length % alignment; +} + +function LogWrapper() { + this.logStr = ""; + this.indent = 0; +} + +LogWrapper.prototype.log = function log(indentChange, message) { + this.indent += indentChange; + this.logStr += ' '.repeat(this.indent) + message + "\n"; +} + +LogWrapper.prototype.clearIndentLog = function log(message) { + this.indent = 0; + this.logStr += message + "\n\n"; +} + +LogWrapper.prototype.logData = function logData(dataStr) { + this.logStr += dataStr; +} diff --git a/test/fixtures/packetTraceData.js b/test/fixtures/packetTraceData.js new file mode 100644 index 0000000..6b03cf3 --- /dev/null +++ b/test/fixtures/packetTraceData.js @@ -0,0 +1,181 @@ +// Copyright 2013 SAP AG. +// +// 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 +// +// http: //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'; + +exports.AUTHREQUEST = { + packet: new Buffer( + '0000000000000000' + // Session id + '00000000' + // Packet count + '70010000' + // Varpart length + 'e0ff0f00' + // Varpart size + '0100' + // Number of segments + '00000000000000000000' + // Extra options + '70010000' + // Segment length + '00000000' + // Segment offset + '0300' + // Number of parts + '0100' + // Segment number + '01' + // Segment kind + '41' + // Message type + '00' + // Auto commit + '00' + // Command options + '0000000000000000' + // Filler + '1d' + // Part kind + '00' + // Part attributes + '0300' + // Argument count + '00000000' + // Big argument count + '28000000' + // Part buffer length + 'b8ff0f00' + // Buffer size + '011d1200322e32332e32342e31373333353138383135021d060053514c444243031d04006e6f6465' + // Part data + '43' + // Part kind + '00' + // Part attributes + '0000' + // Argument count + '00000000' + // Big argument count + '00000000' + // Part buffer length + '80ff0f00' + // Buffer size + '21' + // Part kind + '00' + // Part attributes + '0100' + // Argument count + '00000000' + // Big argument count + 'fb000000' + // Part buffer length + '70ff0f00' + // Buffer size + '00000000000000000000000000000000000000000000000000000000000000000000000000000000' + + '00000000000000000000000000000000000000000000000000000000000000000000000000000000' + + '00000000000000000000000000000000000000000000000000000000000000000000000000000000' + + '00000000000000000000000000000000000000000000000000000000000000000000000000000000' + + '00000000000000000000000000000000000000000000000000000000000000000000000000000000' + + '00000000000000000000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000', 'hex'), // Part data + trace: [ + "", + " SESSION ID: 0 PACKET COUNT: 0", + " VARPART LENGTH: 368 VARPART SIZE: 1048544", + " NO OF SEGMENTS: 1", + " SEGMENT 1 OF 1 MESSAGE TYPE: AUTHENTICATE", + " LENGTH: 368 OFFSET: 0", + " NO OF PARTS: 3 NUMBER: 1", + " KIND: REQUEST AUTOCOMMIT: 0", + " OPTIONS: ()", + " PART 1 CLIENT_CONTEXT", + " LENGTH: 40 SIZE: 1048504", + " ARGUMENTS: 3", + " ATTRIBUTES: ()", + " DATA:", + " 0|01 1D 12 00 32 2E 32 33 2E 32 34 2E 31 37 33 33|....2.23.24.1733|", + " 10|35 31 38 38 31 35 02 1D 06 00 53 51 4C 44 42 43|518815....SQLDBC|", + " 20|03 1D 04 00 6E 6F 64 65 |....node |", + " PART 2 DB_CONNECT_INFO", + " LENGTH: 0 SIZE: 1048448", + " ARGUMENTS: 0", + " ATTRIBUTES: ()", + " DATA:", + " PART 3 AUTHENTICATION", + " LENGTH: 251 SIZE: 1048432", + " ARGUMENTS: 1", + " ATTRIBUTES: ()", + " DATA:", + " [AUTHENTICATION INFORMATION]", + "" + ], +}; + +exports.REPLY = { + packet: new Buffer( + '54d871a5a3960300' + // Session id + '05000000' + // Packet count + '68010000' + // Varpart length + '10750000' + // Varpart size + '0100' + // Number of segments + '00000000000000000000' + // Extra options + '68010000' + // Segment length + '00000000' + // Segment offset + '0300' + // Number of parts + '0100' + // Segment number + '02' + // Segment kind + '00' + // Filler + '0500' + // Function code + '0000000000000000' + // Filler + '0d' + // Part kind + '00' + // Part attributes + '0100' + // Argument count + '00000000' + // Big argument count + '08000000' + // Part buffer length + '40010000' + // Buffer size + 'faa8000046170300' + // Part data + '27' + // Part kind + '00' + // Part attributes + '0400' + // Argument count + '00000000' + // Big argument count + 'd6000000' + // Part buffer length + '28010000' + // Buffer size + '0121b4000100000000000fd0920b0000000000002373000000000000a43d00000000000000000000' + + '00000000000000000000000000000000000000000000000000000000000000000000000000000000' + + '00000000000000000000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000fd0edfe0fd0ffffffffffffff7f000000000000000000000000000040b001000000' + + '000000000000000000000000fce37115b7620100851299140204b6050000000000000704e1030000' + + '00000000080468aa0000000000000000' + // Part data + '05' + // Part kind + '11' + // Part attributes + '0200' + // Argument count + '00000000' + // Big argument count + '3a000000' + // Part buffer length + '40000000' + // Buffer size + '1a4865726520697320736f6d6520737472696e6720746f206164641e486572652069732061207365' + + '636f6e6420737472696e6720746f20616464000000000000', 'hex'), // Part data + trace: [ + "", + " SESSION ID: 1010054529669204 PACKET COUNT: 5", + " VARPART LENGTH: 360 VARPART SIZE: 29968", + " NO OF SEGMENTS: 1", + " SEGMENT 1", + " LENGTH: 360 OFFSET: 0", + " NO OF PARTS: 3 NUMBER: 1", + " KIND: REPLY", + " FUNCTION CODE: SELECT", + " PART 1 RESULT_SET_ID", + " LENGTH: 8 SIZE: 320", + " ARGUMENTS: 1", + " ATTRIBUTES: ()", + " DATA:", + " 0|FA A8 00 00 46 17 03 00 |....F... |", + " PART 2 STATEMENT_CONTEXT", + " LENGTH: 214 SIZE: 296", + " ARGUMENTS: 4", + " ATTRIBUTES: ()", + " DATA:", + " 0|01 21 B4 00 01 00 00 00 00 00 0F D0 92 0B 00 00|.!..............|", + " 10|00 00 00 00 23 73 00 00 00 00 00 00 A4 3D 00 00|....#s.......=..|", + " 20|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................|", + " 30|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................|", + " 40|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................|", + " 50|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................|", + " 60|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................|", + " 70|00 00 00 00 00 00 00 00 00 00 00 00 00 00 0F D0|................|", + " 80|ED FE 0F D0 FF FF FF FF FF FF FF 7F 00 00 00 00|...........\x7F....|", + " 90|00 00 00 00 00 00 00 00 00 00 40 B0 01 00 00 00|..........@.....|", + " A0|00 00 00 00 00 00 00 00 00 00 00 00 FC E3 71 15|..............q.|", + " B0|B7 62 01 00 85 12 99 14 02 04 B6 05 00 00 00 00|.b..............|", + " C0|00 00 07 04 E1 03 00 00 00 00 00 00 08 04 68 AA|..............h.|", + " D0|00 00 00 00 00 00 |...... |", + " PART 3 RESULT_SET", + " LENGTH: 58 SIZE: 64", + " ARGUMENTS: 2", + " ATTRIBUTES: (LAST|CLOSED)", + " DATA:", + " 0|1A 48 65 72 65 20 69 73 20 73 6F 6D 65 20 73 74|.Here is some st|", + " 10|72 69 6E 67 20 74 6F 20 61 64 64 1E 48 65 72 65|ring to add.Here|", + " 20|20 69 73 20 61 20 73 65 63 6F 6E 64 20 73 74 72| is a second str|", + " 30|69 6E 67 20 74 6F 20 61 64 64 |ing to add |", + "", + ] +} diff --git a/test/util.index.js b/test/util.index.js index 23c9e23..80dea0b 100644 --- a/test/util.index.js +++ b/test/util.index.js @@ -99,7 +99,11 @@ describe('Util', function () { }); it('should return a dummy tracelog function', function () { + // Temporarily unset the HDB_TRACE environment variable + var hdbTrace = process.env.HDB_TRACE; + delete process.env.HDB_TRACE; var tracelog = util.tracelog(); + process.env.HDB_TRACE = hdbTrace; (!tracelog()).should.be.ok; tracelog.name.should.equal('dummyTracelog'); }); diff --git a/test/util.trace.js b/test/util.trace.js new file mode 100644 index 0000000..9f0389d --- /dev/null +++ b/test/util.trace.js @@ -0,0 +1,60 @@ +// Copyright 2013 SAP AG. +// +// 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 +// +// http: //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'; + +var packetTracer = require('../lib/util/trace/PacketTracer') + +var data = require('./fixtures/packetTraceData'); + +describe('Util', function () { + + describe('#trace', function () { + + it('should trace an authenticate request', function (done) { + var test = data.AUTHREQUEST; + var traceStr = packetTracer.parseRequest(test.packet); + var traceLines = traceStr.split('\n'); + // Remove trailing new lines + while(traceLines.length > 0 && traceLines[traceLines.length - 1] === '') { + traceLines.pop(); + } + traceLines.length.should.eql(test.trace.length); + // Check without timestamp next to + traceLines[0].should.startWith(test.trace[0]); + for (var i = 1; i < test.trace.length; i++) { + traceLines[i].should.eql(test.trace[i]); + } + done(); + }); + + it('should trace a select reply', function (done) { + var test = data.REPLY; + var traceStr = packetTracer.parseReply(test.packet); + var traceLines = traceStr.split('\n'); + // Remove trailing new lines + while(traceLines.length > 0 && traceLines[traceLines.length - 1] === '') { + traceLines.pop(); + } + traceLines.length.should.eql(test.trace.length); + // Check without timestamp next to + traceLines[0].should.startWith(test.trace[0]); + for (var i = 1; i < test.trace.length; i++) { + traceLines[i].should.eql(test.trace[i]); + } + done(); + }); + + }); + +});