inbound-parser.test.ts 14 KB

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