"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const test_buffers_1 = __importDefault(require("./testing/test-buffers")); const buffer_list_1 = __importDefault(require("./testing/buffer-list")); const _1 = require("./"); const assert_1 = __importDefault(require("assert")); var authOkBuffer = test_buffers_1.default.authenticationOk(); var paramStatusBuffer = test_buffers_1.default.parameterStatus('client_encoding', 'UTF8'); var readyForQueryBuffer = test_buffers_1.default.readyForQuery(); var backendKeyDataBuffer = test_buffers_1.default.backendKeyData(1, 2); var commandCompleteBuffer = test_buffers_1.default.commandComplete('SELECT 3'); var parseCompleteBuffer = test_buffers_1.default.parseComplete(); var bindCompleteBuffer = test_buffers_1.default.bindComplete(); var portalSuspendedBuffer = test_buffers_1.default.portalSuspended(); var addRow = function (bufferList, name, offset) { return bufferList.addCString(name) // field name .addInt32(offset++) // table id .addInt16(offset++) // attribute of column number .addInt32(offset++) // objectId of field's data type .addInt16(offset++) // datatype size .addInt32(offset++) // type modifier .addInt16(0); // format code, 0 => text }; var row1 = { name: 'id', tableID: 1, attributeNumber: 2, dataTypeID: 3, dataTypeSize: 4, typeModifier: 5, formatCode: 0 }; var oneRowDescBuff = test_buffers_1.default.rowDescription([row1]); row1.name = 'bang'; var twoRowBuf = test_buffers_1.default.rowDescription([row1, { name: 'whoah', tableID: 10, attributeNumber: 11, dataTypeID: 12, dataTypeSize: 13, typeModifier: 14, formatCode: 0 }]); var emptyRowFieldBuf = new buffer_list_1.default() .addInt16(0) .join(true, 'D'); var emptyRowFieldBuf = test_buffers_1.default.dataRow([]); var oneFieldBuf = new buffer_list_1.default() .addInt16(1) // number of fields .addInt32(5) // length of bytes of fields .addCString('test') .join(true, 'D'); var oneFieldBuf = test_buffers_1.default.dataRow(['test']); var expectedAuthenticationOkayMessage = { name: 'authenticationOk', length: 8 }; var expectedParameterStatusMessage = { name: 'parameterStatus', parameterName: 'client_encoding', parameterValue: 'UTF8', length: 25 }; var expectedBackendKeyDataMessage = { name: 'backendKeyData', processID: 1, secretKey: 2 }; var expectedReadyForQueryMessage = { name: 'readyForQuery', length: 5, status: 'I' }; var expectedCommandCompleteMessage = { name: 'commandComplete', length: 13, text: 'SELECT 3' }; var emptyRowDescriptionBuffer = new buffer_list_1.default() .addInt16(0) // number of fields .join(true, 'T'); var expectedEmptyRowDescriptionMessage = { name: 'rowDescription', length: 6, fieldCount: 0, fields: [], }; var expectedOneRowMessage = { name: 'rowDescription', length: 27, fieldCount: 1, fields: [{ name: 'id', tableID: 1, columnID: 2, dataTypeID: 3, dataTypeSize: 4, dataTypeModifier: 5, format: 'text' }] }; var expectedTwoRowMessage = { name: 'rowDescription', length: 53, fieldCount: 2, fields: [{ name: 'bang', tableID: 1, columnID: 2, dataTypeID: 3, dataTypeSize: 4, dataTypeModifier: 5, format: 'text' }, { name: 'whoah', tableID: 10, columnID: 11, dataTypeID: 12, dataTypeSize: 13, dataTypeModifier: 14, format: 'text' }] }; const concat = (stream) => { return new Promise((resolve) => { const results = []; stream.on('data', item => results.push(item)); stream.on('end', () => resolve(results)); }); }; var testForMessage = function (buffer, expectedMessage) { it('recieves and parses ' + expectedMessage.name, () => __awaiter(this, void 0, void 0, function* () { const parser = new _1.PgPacketStream(); parser.write(buffer); parser.end(); const [lastMessage] = yield concat(parser); for (const key in expectedMessage) { assert_1.default.deepEqual(lastMessage[key], expectedMessage[key]); } })); }; var plainPasswordBuffer = test_buffers_1.default.authenticationCleartextPassword(); var md5PasswordBuffer = test_buffers_1.default.authenticationMD5Password(); var SASLBuffer = test_buffers_1.default.authenticationSASL(); var SASLContinueBuffer = test_buffers_1.default.authenticationSASLContinue(); var SASLFinalBuffer = test_buffers_1.default.authenticationSASLFinal(); var expectedPlainPasswordMessage = { name: 'authenticationCleartextPassword' }; var expectedMD5PasswordMessage = { name: 'authenticationMD5Password', salt: Buffer.from([1, 2, 3, 4]) }; var expectedSASLMessage = { name: 'authenticationSASL', mechanisms: ['SCRAM-SHA-256'] }; var expectedSASLContinueMessage = { name: 'authenticationSASLContinue', data: 'data', }; var expectedSASLFinalMessage = { name: 'authenticationSASLFinal', data: 'data', }; var notificationResponseBuffer = test_buffers_1.default.notification(4, 'hi', 'boom'); var expectedNotificationResponseMessage = { name: 'notification', processId: 4, channel: 'hi', payload: 'boom' }; describe('PgPacketStream', function () { testForMessage(authOkBuffer, expectedAuthenticationOkayMessage); testForMessage(plainPasswordBuffer, expectedPlainPasswordMessage); testForMessage(md5PasswordBuffer, expectedMD5PasswordMessage); testForMessage(SASLBuffer, expectedSASLMessage); testForMessage(SASLContinueBuffer, expectedSASLContinueMessage); testForMessage(SASLFinalBuffer, expectedSASLFinalMessage); testForMessage(paramStatusBuffer, expectedParameterStatusMessage); testForMessage(backendKeyDataBuffer, expectedBackendKeyDataMessage); testForMessage(readyForQueryBuffer, expectedReadyForQueryMessage); testForMessage(commandCompleteBuffer, expectedCommandCompleteMessage); testForMessage(notificationResponseBuffer, expectedNotificationResponseMessage); testForMessage(test_buffers_1.default.emptyQuery(), { name: 'emptyQuery', length: 4, }); testForMessage(Buffer.from([0x6e, 0, 0, 0, 4]), { name: 'noData' }); describe('rowDescription messages', function () { testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage); testForMessage(oneRowDescBuff, expectedOneRowMessage); testForMessage(twoRowBuf, expectedTwoRowMessage); }); describe('parsing rows', function () { describe('parsing empty row', function () { testForMessage(emptyRowFieldBuf, { name: 'dataRow', fieldCount: 0 }); }); describe('parsing data row with fields', function () { testForMessage(oneFieldBuf, { name: 'dataRow', fieldCount: 1, fields: ['test'] }); }); }); describe('notice message', function () { // this uses the same logic as error message var buff = test_buffers_1.default.notice([{ type: 'C', value: 'code' }]); testForMessage(buff, { name: 'notice', code: 'code' }); }); testForMessage(test_buffers_1.default.error([]), { name: 'error' }); describe('with all the fields', function () { var buffer = test_buffers_1.default.error([{ type: 'S', value: 'ERROR' }, { type: 'C', value: 'code' }, { type: 'M', value: 'message' }, { type: 'D', value: 'details' }, { type: 'H', value: 'hint' }, { type: 'P', value: '100' }, { type: 'p', value: '101' }, { type: 'q', value: 'query' }, { type: 'W', value: 'where' }, { type: 'F', value: 'file' }, { type: 'L', value: 'line' }, { type: 'R', value: 'routine' }, { type: 'Z', value: 'alsdkf' }]); testForMessage(buffer, { name: 'error', severity: 'ERROR', code: 'code', message: 'message', detail: 'details', hint: 'hint', position: '100', internalPosition: '101', internalQuery: 'query', where: 'where', file: 'file', line: 'line', routine: 'routine' }); }); testForMessage(parseCompleteBuffer, { name: 'parseComplete' }); testForMessage(bindCompleteBuffer, { name: 'bindComplete' }); testForMessage(bindCompleteBuffer, { name: 'bindComplete' }); testForMessage(test_buffers_1.default.closeComplete(), { name: 'closeComplete' }); describe('parses portal suspended message', function () { testForMessage(portalSuspendedBuffer, { name: 'portalSuspended' }); }); describe('parses replication start message', function () { testForMessage(Buffer.from([0x57, 0x00, 0x00, 0x00, 0x04]), { name: 'replicationStart', length: 4 }); }); describe('copy', () => { testForMessage(test_buffers_1.default.copyIn(0), { name: 'copyInResponse', length: 7, binary: false, columnTypes: [] }); testForMessage(test_buffers_1.default.copyIn(2), { name: 'copyInResponse', length: 11, binary: false, columnTypes: [0, 1] }); testForMessage(test_buffers_1.default.copyOut(0), { name: 'copyOutResponse', length: 7, binary: false, columnTypes: [] }); testForMessage(test_buffers_1.default.copyOut(3), { name: 'copyOutResponse', length: 13, binary: false, columnTypes: [0, 1, 2] }); testForMessage(test_buffers_1.default.copyDone(), { name: 'copyDone', length: 4, }); testForMessage(test_buffers_1.default.copyData(Buffer.from([5, 6, 7])), { name: 'copyData', length: 7, chunk: Buffer.from([5, 6, 7]) }); }); // since the data message on a stream can randomly divide the incomming // tcp packets anywhere, we need to make sure we can parse every single // split on a tcp message describe('split buffer, single message parsing', function () { var fullBuffer = test_buffers_1.default.dataRow([null, 'bang', 'zug zug', null, '!']); const parse = (buffers) => __awaiter(this, void 0, void 0, function* () { const parser = new _1.PgPacketStream(); for (const buffer of buffers) { parser.write(buffer); } parser.end(); const [msg] = yield concat(parser); return msg; }); it('parses when full buffer comes in', function () { return __awaiter(this, void 0, void 0, function* () { const message = yield parse([fullBuffer]); assert_1.default.equal(message.fields.length, 5); assert_1.default.equal(message.fields[0], null); assert_1.default.equal(message.fields[1], 'bang'); assert_1.default.equal(message.fields[2], 'zug zug'); assert_1.default.equal(message.fields[3], null); assert_1.default.equal(message.fields[4], '!'); }); }); var testMessageRecievedAfterSpiltAt = function (split) { return __awaiter(this, void 0, void 0, function* () { var firstBuffer = Buffer.alloc(fullBuffer.length - split); var secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length); fullBuffer.copy(firstBuffer, 0, 0); fullBuffer.copy(secondBuffer, 0, firstBuffer.length); const message = yield parse([firstBuffer, secondBuffer]); assert_1.default.equal(message.fields.length, 5); assert_1.default.equal(message.fields[0], null); assert_1.default.equal(message.fields[1], 'bang'); assert_1.default.equal(message.fields[2], 'zug zug'); assert_1.default.equal(message.fields[3], null); assert_1.default.equal(message.fields[4], '!'); }); }; it('parses when split in the middle', function () { testMessageRecievedAfterSpiltAt(6); }); it('parses when split at end', function () { testMessageRecievedAfterSpiltAt(2); }); it('parses when split at beginning', function () { testMessageRecievedAfterSpiltAt(fullBuffer.length - 2); testMessageRecievedAfterSpiltAt(fullBuffer.length - 1); testMessageRecievedAfterSpiltAt(fullBuffer.length - 5); }); }); describe('split buffer, multiple message parsing', function () { var dataRowBuffer = test_buffers_1.default.dataRow(['!']); var readyForQueryBuffer = test_buffers_1.default.readyForQuery(); var fullBuffer = Buffer.alloc(dataRowBuffer.length + readyForQueryBuffer.length); dataRowBuffer.copy(fullBuffer, 0, 0); readyForQueryBuffer.copy(fullBuffer, dataRowBuffer.length, 0); const parse = (buffers) => { const parser = new _1.PgPacketStream(); for (const buffer of buffers) { parser.write(buffer); } parser.end(); return concat(parser); }; var verifyMessages = function (messages) { assert_1.default.strictEqual(messages.length, 2); assert_1.default.deepEqual(messages[0], { name: 'dataRow', fieldCount: 1, length: 11, fields: ['!'] }); assert_1.default.equal(messages[0].fields[0], '!'); assert_1.default.deepEqual(messages[1], { name: 'readyForQuery', length: 5, status: 'I' }); }; // sanity check it('recieves both messages when packet is not split', function () { return __awaiter(this, void 0, void 0, function* () { const messages = yield parse([fullBuffer]); verifyMessages(messages); }); }); var splitAndVerifyTwoMessages = function (split) { return __awaiter(this, void 0, void 0, function* () { var firstBuffer = Buffer.alloc(fullBuffer.length - split); var secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length); fullBuffer.copy(firstBuffer, 0, 0); fullBuffer.copy(secondBuffer, 0, firstBuffer.length); const messages = yield parse([firstBuffer, secondBuffer]); verifyMessages(messages); }); }; describe('recieves both messages when packet is split', function () { it('in the middle', function () { return splitAndVerifyTwoMessages(11); }); it('at the front', function () { return Promise.all([ splitAndVerifyTwoMessages(fullBuffer.length - 1), splitAndVerifyTwoMessages(fullBuffer.length - 4), splitAndVerifyTwoMessages(fullBuffer.length - 6) ]); }); it('at the end', function () { return Promise.all([ splitAndVerifyTwoMessages(8), splitAndVerifyTwoMessages(1) ]); }); }); }); }); //# sourceMappingURL=inbound-parser.test.js.map