inbound-parser.test.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. import buffers from './testing/test-buffers'
  2. import BufferList from './testing/buffer-list'
  3. import { parse } from '.'
  4. import assert from 'assert'
  5. import { PassThrough } from 'stream'
  6. import { BackendMessage } from './messages'
  7. var authOkBuffer = buffers.authenticationOk()
  8. var paramStatusBuffer = buffers.parameterStatus('client_encoding', 'UTF8')
  9. var readyForQueryBuffer = buffers.readyForQuery()
  10. var backendKeyDataBuffer = buffers.backendKeyData(1, 2)
  11. var commandCompleteBuffer = buffers.commandComplete('SELECT 3')
  12. var parseCompleteBuffer = buffers.parseComplete()
  13. var bindCompleteBuffer = buffers.bindComplete()
  14. var portalSuspendedBuffer = buffers.portalSuspended()
  15. var addRow = function (bufferList: BufferList, name: string, offset: number) {
  16. return bufferList
  17. .addCString(name) // field name
  18. .addInt32(offset++) // table id
  19. .addInt16(offset++) // attribute of column number
  20. .addInt32(offset++) // objectId of field's data type
  21. .addInt16(offset++) // datatype size
  22. .addInt32(offset++) // type modifier
  23. .addInt16(0) // format code, 0 => text
  24. }
  25. var row1 = {
  26. name: 'id',
  27. tableID: 1,
  28. attributeNumber: 2,
  29. dataTypeID: 3,
  30. dataTypeSize: 4,
  31. typeModifier: 5,
  32. formatCode: 0,
  33. }
  34. var oneRowDescBuff = buffers.rowDescription([row1])
  35. row1.name = 'bang'
  36. var twoRowBuf = buffers.rowDescription([
  37. row1,
  38. {
  39. name: 'whoah',
  40. tableID: 10,
  41. attributeNumber: 11,
  42. dataTypeID: 12,
  43. dataTypeSize: 13,
  44. typeModifier: 14,
  45. formatCode: 0,
  46. },
  47. ])
  48. var emptyRowFieldBuf = new BufferList().addInt16(0).join(true, 'D')
  49. var emptyRowFieldBuf = buffers.dataRow([])
  50. var oneFieldBuf = new BufferList()
  51. .addInt16(1) // number of fields
  52. .addInt32(5) // length of bytes of fields
  53. .addCString('test')
  54. .join(true, 'D')
  55. var oneFieldBuf = buffers.dataRow(['test'])
  56. var expectedAuthenticationOkayMessage = {
  57. name: 'authenticationOk',
  58. length: 8,
  59. }
  60. var expectedParameterStatusMessage = {
  61. name: 'parameterStatus',
  62. parameterName: 'client_encoding',
  63. parameterValue: 'UTF8',
  64. length: 25,
  65. }
  66. var expectedBackendKeyDataMessage = {
  67. name: 'backendKeyData',
  68. processID: 1,
  69. secretKey: 2,
  70. }
  71. var expectedReadyForQueryMessage = {
  72. name: 'readyForQuery',
  73. length: 5,
  74. status: 'I',
  75. }
  76. var expectedCommandCompleteMessage = {
  77. name: 'commandComplete',
  78. length: 13,
  79. text: 'SELECT 3',
  80. }
  81. var emptyRowDescriptionBuffer = new BufferList()
  82. .addInt16(0) // number of fields
  83. .join(true, 'T')
  84. var expectedEmptyRowDescriptionMessage = {
  85. name: 'rowDescription',
  86. length: 6,
  87. fieldCount: 0,
  88. fields: [],
  89. }
  90. var expectedOneRowMessage = {
  91. name: 'rowDescription',
  92. length: 27,
  93. fieldCount: 1,
  94. fields: [
  95. {
  96. name: 'id',
  97. tableID: 1,
  98. columnID: 2,
  99. dataTypeID: 3,
  100. dataTypeSize: 4,
  101. dataTypeModifier: 5,
  102. format: 'text',
  103. },
  104. ],
  105. }
  106. var expectedTwoRowMessage = {
  107. name: 'rowDescription',
  108. length: 53,
  109. fieldCount: 2,
  110. fields: [
  111. {
  112. name: 'bang',
  113. tableID: 1,
  114. columnID: 2,
  115. dataTypeID: 3,
  116. dataTypeSize: 4,
  117. dataTypeModifier: 5,
  118. format: 'text',
  119. },
  120. {
  121. name: 'whoah',
  122. tableID: 10,
  123. columnID: 11,
  124. dataTypeID: 12,
  125. dataTypeSize: 13,
  126. dataTypeModifier: 14,
  127. format: 'text',
  128. },
  129. ],
  130. }
  131. var emptyParameterDescriptionBuffer = new BufferList()
  132. .addInt16(0) // number of parameters
  133. .join(true, 't')
  134. var oneParameterDescBuf = buffers.parameterDescription([1111])
  135. var twoParameterDescBuf = buffers.parameterDescription([2222, 3333])
  136. var expectedEmptyParameterDescriptionMessage = {
  137. name: 'parameterDescription',
  138. length: 6,
  139. parameterCount: 0,
  140. dataTypeIDs: [],
  141. }
  142. var expectedOneParameterMessage = {
  143. name: 'parameterDescription',
  144. length: 10,
  145. parameterCount: 1,
  146. dataTypeIDs: [1111],
  147. }
  148. var expectedTwoParameterMessage = {
  149. name: 'parameterDescription',
  150. length: 14,
  151. parameterCount: 2,
  152. dataTypeIDs: [2222, 3333],
  153. }
  154. var testForMessage = function (buffer: Buffer, expectedMessage: any) {
  155. it('recieves and parses ' + expectedMessage.name, async () => {
  156. const messages = await parseBuffers([buffer])
  157. const [lastMessage] = messages
  158. for (const key in expectedMessage) {
  159. assert.deepEqual((lastMessage as any)[key], expectedMessage[key])
  160. }
  161. })
  162. }
  163. var plainPasswordBuffer = buffers.authenticationCleartextPassword()
  164. var md5PasswordBuffer = buffers.authenticationMD5Password()
  165. var SASLBuffer = buffers.authenticationSASL()
  166. var SASLContinueBuffer = buffers.authenticationSASLContinue()
  167. var SASLFinalBuffer = buffers.authenticationSASLFinal()
  168. var expectedPlainPasswordMessage = {
  169. name: 'authenticationCleartextPassword',
  170. }
  171. var expectedMD5PasswordMessage = {
  172. name: 'authenticationMD5Password',
  173. salt: Buffer.from([1, 2, 3, 4]),
  174. }
  175. var expectedSASLMessage = {
  176. name: 'authenticationSASL',
  177. mechanisms: ['SCRAM-SHA-256'],
  178. }
  179. var expectedSASLContinueMessage = {
  180. name: 'authenticationSASLContinue',
  181. data: 'data',
  182. }
  183. var expectedSASLFinalMessage = {
  184. name: 'authenticationSASLFinal',
  185. data: 'data',
  186. }
  187. var notificationResponseBuffer = buffers.notification(4, 'hi', 'boom')
  188. var expectedNotificationResponseMessage = {
  189. name: 'notification',
  190. processId: 4,
  191. channel: 'hi',
  192. payload: 'boom',
  193. }
  194. const parseBuffers = async (buffers: Buffer[]): Promise<BackendMessage[]> => {
  195. const stream = new PassThrough()
  196. for (const buffer of buffers) {
  197. stream.write(buffer)
  198. }
  199. stream.end()
  200. const msgs: BackendMessage[] = []
  201. await parse(stream, (msg) => msgs.push(msg))
  202. return msgs
  203. }
  204. describe('PgPacketStream', function () {
  205. testForMessage(authOkBuffer, expectedAuthenticationOkayMessage)
  206. testForMessage(plainPasswordBuffer, expectedPlainPasswordMessage)
  207. testForMessage(md5PasswordBuffer, expectedMD5PasswordMessage)
  208. testForMessage(SASLBuffer, expectedSASLMessage)
  209. testForMessage(SASLContinueBuffer, expectedSASLContinueMessage)
  210. // this exercises a found bug in the parser:
  211. // https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084
  212. // and adds a test which is deterministic, rather than relying on network packet chunking
  213. const extendedSASLContinueBuffer = Buffer.concat([SASLContinueBuffer, Buffer.from([1, 2, 3, 4])])
  214. testForMessage(extendedSASLContinueBuffer, expectedSASLContinueMessage)
  215. testForMessage(SASLFinalBuffer, expectedSASLFinalMessage)
  216. // this exercises a found bug in the parser:
  217. // https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084
  218. // and adds a test which is deterministic, rather than relying on network packet chunking
  219. const extendedSASLFinalBuffer = Buffer.concat([SASLFinalBuffer, Buffer.from([1, 2, 4, 5])])
  220. testForMessage(extendedSASLFinalBuffer, expectedSASLFinalMessage)
  221. testForMessage(paramStatusBuffer, expectedParameterStatusMessage)
  222. testForMessage(backendKeyDataBuffer, expectedBackendKeyDataMessage)
  223. testForMessage(readyForQueryBuffer, expectedReadyForQueryMessage)
  224. testForMessage(commandCompleteBuffer, expectedCommandCompleteMessage)
  225. testForMessage(notificationResponseBuffer, expectedNotificationResponseMessage)
  226. testForMessage(buffers.emptyQuery(), {
  227. name: 'emptyQuery',
  228. length: 4,
  229. })
  230. testForMessage(Buffer.from([0x6e, 0, 0, 0, 4]), {
  231. name: 'noData',
  232. })
  233. describe('rowDescription messages', function () {
  234. testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage)
  235. testForMessage(oneRowDescBuff, expectedOneRowMessage)
  236. testForMessage(twoRowBuf, expectedTwoRowMessage)
  237. })
  238. describe('parameterDescription messages', function () {
  239. testForMessage(emptyParameterDescriptionBuffer, expectedEmptyParameterDescriptionMessage)
  240. testForMessage(oneParameterDescBuf, expectedOneParameterMessage)
  241. testForMessage(twoParameterDescBuf, expectedTwoParameterMessage)
  242. })
  243. describe('parsing rows', function () {
  244. describe('parsing empty row', function () {
  245. testForMessage(emptyRowFieldBuf, {
  246. name: 'dataRow',
  247. fieldCount: 0,
  248. })
  249. })
  250. describe('parsing data row with fields', function () {
  251. testForMessage(oneFieldBuf, {
  252. name: 'dataRow',
  253. fieldCount: 1,
  254. fields: ['test'],
  255. })
  256. })
  257. })
  258. describe('notice message', function () {
  259. // this uses the same logic as error message
  260. var buff = buffers.notice([{ type: 'C', value: 'code' }])
  261. testForMessage(buff, {
  262. name: 'notice',
  263. code: 'code',
  264. })
  265. })
  266. testForMessage(buffers.error([]), {
  267. name: 'error',
  268. })
  269. describe('with all the fields', function () {
  270. var buffer = buffers.error([
  271. {
  272. type: 'S',
  273. value: 'ERROR',
  274. },
  275. {
  276. type: 'C',
  277. value: 'code',
  278. },
  279. {
  280. type: 'M',
  281. value: 'message',
  282. },
  283. {
  284. type: 'D',
  285. value: 'details',
  286. },
  287. {
  288. type: 'H',
  289. value: 'hint',
  290. },
  291. {
  292. type: 'P',
  293. value: '100',
  294. },
  295. {
  296. type: 'p',
  297. value: '101',
  298. },
  299. {
  300. type: 'q',
  301. value: 'query',
  302. },
  303. {
  304. type: 'W',
  305. value: 'where',
  306. },
  307. {
  308. type: 'F',
  309. value: 'file',
  310. },
  311. {
  312. type: 'L',
  313. value: 'line',
  314. },
  315. {
  316. type: 'R',
  317. value: 'routine',
  318. },
  319. {
  320. type: 'Z', // ignored
  321. value: 'alsdkf',
  322. },
  323. ])
  324. testForMessage(buffer, {
  325. name: 'error',
  326. severity: 'ERROR',
  327. code: 'code',
  328. message: 'message',
  329. detail: 'details',
  330. hint: 'hint',
  331. position: '100',
  332. internalPosition: '101',
  333. internalQuery: 'query',
  334. where: 'where',
  335. file: 'file',
  336. line: 'line',
  337. routine: 'routine',
  338. })
  339. })
  340. testForMessage(parseCompleteBuffer, {
  341. name: 'parseComplete',
  342. })
  343. testForMessage(bindCompleteBuffer, {
  344. name: 'bindComplete',
  345. })
  346. testForMessage(bindCompleteBuffer, {
  347. name: 'bindComplete',
  348. })
  349. testForMessage(buffers.closeComplete(), {
  350. name: 'closeComplete',
  351. })
  352. describe('parses portal suspended message', function () {
  353. testForMessage(portalSuspendedBuffer, {
  354. name: 'portalSuspended',
  355. })
  356. })
  357. describe('parses replication start message', function () {
  358. testForMessage(Buffer.from([0x57, 0x00, 0x00, 0x00, 0x04]), {
  359. name: 'replicationStart',
  360. length: 4,
  361. })
  362. })
  363. describe('copy', () => {
  364. testForMessage(buffers.copyIn(0), {
  365. name: 'copyInResponse',
  366. length: 7,
  367. binary: false,
  368. columnTypes: [],
  369. })
  370. testForMessage(buffers.copyIn(2), {
  371. name: 'copyInResponse',
  372. length: 11,
  373. binary: false,
  374. columnTypes: [0, 1],
  375. })
  376. testForMessage(buffers.copyOut(0), {
  377. name: 'copyOutResponse',
  378. length: 7,
  379. binary: false,
  380. columnTypes: [],
  381. })
  382. testForMessage(buffers.copyOut(3), {
  383. name: 'copyOutResponse',
  384. length: 13,
  385. binary: false,
  386. columnTypes: [0, 1, 2],
  387. })
  388. testForMessage(buffers.copyDone(), {
  389. name: 'copyDone',
  390. length: 4,
  391. })
  392. testForMessage(buffers.copyData(Buffer.from([5, 6, 7])), {
  393. name: 'copyData',
  394. length: 7,
  395. chunk: Buffer.from([5, 6, 7]),
  396. })
  397. })
  398. // since the data message on a stream can randomly divide the incomming
  399. // tcp packets anywhere, we need to make sure we can parse every single
  400. // split on a tcp message
  401. describe('split buffer, single message parsing', function () {
  402. var fullBuffer = buffers.dataRow([null, 'bang', 'zug zug', null, '!'])
  403. it('parses when full buffer comes in', async function () {
  404. const messages = await parseBuffers([fullBuffer])
  405. const message = messages[0] as any
  406. assert.equal(message.fields.length, 5)
  407. assert.equal(message.fields[0], null)
  408. assert.equal(message.fields[1], 'bang')
  409. assert.equal(message.fields[2], 'zug zug')
  410. assert.equal(message.fields[3], null)
  411. assert.equal(message.fields[4], '!')
  412. })
  413. var testMessageRecievedAfterSpiltAt = async function (split: number) {
  414. var firstBuffer = Buffer.alloc(fullBuffer.length - split)
  415. var secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length)
  416. fullBuffer.copy(firstBuffer, 0, 0)
  417. fullBuffer.copy(secondBuffer, 0, firstBuffer.length)
  418. const messages = await parseBuffers([fullBuffer])
  419. const message = messages[0] as any
  420. assert.equal(message.fields.length, 5)
  421. assert.equal(message.fields[0], null)
  422. assert.equal(message.fields[1], 'bang')
  423. assert.equal(message.fields[2], 'zug zug')
  424. assert.equal(message.fields[3], null)
  425. assert.equal(message.fields[4], '!')
  426. }
  427. it('parses when split in the middle', function () {
  428. testMessageRecievedAfterSpiltAt(6)
  429. })
  430. it('parses when split at end', function () {
  431. testMessageRecievedAfterSpiltAt(2)
  432. })
  433. it('parses when split at beginning', function () {
  434. testMessageRecievedAfterSpiltAt(fullBuffer.length - 2)
  435. testMessageRecievedAfterSpiltAt(fullBuffer.length - 1)
  436. testMessageRecievedAfterSpiltAt(fullBuffer.length - 5)
  437. })
  438. })
  439. describe('split buffer, multiple message parsing', function () {
  440. var dataRowBuffer = buffers.dataRow(['!'])
  441. var readyForQueryBuffer = buffers.readyForQuery()
  442. var fullBuffer = Buffer.alloc(dataRowBuffer.length + readyForQueryBuffer.length)
  443. dataRowBuffer.copy(fullBuffer, 0, 0)
  444. readyForQueryBuffer.copy(fullBuffer, dataRowBuffer.length, 0)
  445. var verifyMessages = function (messages: any[]) {
  446. assert.strictEqual(messages.length, 2)
  447. assert.deepEqual(messages[0], {
  448. name: 'dataRow',
  449. fieldCount: 1,
  450. length: 11,
  451. fields: ['!'],
  452. })
  453. assert.equal(messages[0].fields[0], '!')
  454. assert.deepEqual(messages[1], {
  455. name: 'readyForQuery',
  456. length: 5,
  457. status: 'I',
  458. })
  459. }
  460. // sanity check
  461. it('recieves both messages when packet is not split', async function () {
  462. const messages = await parseBuffers([fullBuffer])
  463. verifyMessages(messages)
  464. })
  465. var splitAndVerifyTwoMessages = async function (split: number) {
  466. var firstBuffer = Buffer.alloc(fullBuffer.length - split)
  467. var secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length)
  468. fullBuffer.copy(firstBuffer, 0, 0)
  469. fullBuffer.copy(secondBuffer, 0, firstBuffer.length)
  470. const messages = await parseBuffers([firstBuffer, secondBuffer])
  471. verifyMessages(messages)
  472. }
  473. describe('recieves both messages when packet is split', function () {
  474. it('in the middle', function () {
  475. return splitAndVerifyTwoMessages(11)
  476. })
  477. it('at the front', function () {
  478. return Promise.all([
  479. splitAndVerifyTwoMessages(fullBuffer.length - 1),
  480. splitAndVerifyTwoMessages(fullBuffer.length - 4),
  481. splitAndVerifyTwoMessages(fullBuffer.length - 6),
  482. ])
  483. })
  484. it('at the end', function () {
  485. return Promise.all([splitAndVerifyTwoMessages(8), splitAndVerifyTwoMessages(1)])
  486. })
  487. })
  488. })
  489. })