source: node_modules/pg-protocol/src/parser.ts@ 62b2964

finki-main main
Last change on this file since 62b2964 was 62b2964, checked in by Klimentina Efremova <klimentina08642@…>, 2 weeks ago

Project Handcraft Marketplace

  • Property mode set to 100644
File size: 12.8 KB
Line 
1import { TransformOptions } from 'stream'
2import {
3 Mode,
4 bindComplete,
5 parseComplete,
6 closeComplete,
7 noData,
8 portalSuspended,
9 copyDone,
10 replicationStart,
11 emptyQuery,
12 ReadyForQueryMessage,
13 CommandCompleteMessage,
14 CopyDataMessage,
15 CopyResponse,
16 NotificationResponseMessage,
17 RowDescriptionMessage,
18 ParameterDescriptionMessage,
19 Field,
20 DataRowMessage,
21 ParameterStatusMessage,
22 BackendKeyDataMessage,
23 DatabaseError,
24 BackendMessage,
25 MessageName,
26 AuthenticationMD5Password,
27 NoticeMessage,
28} from './messages'
29import { BufferReader } from './buffer-reader'
30
31// every message is prefixed with a single byte
32const CODE_LENGTH = 1
33// every message has an int32 length which includes itself but does
34// NOT include the code in the length
35const LEN_LENGTH = 4
36
37const HEADER_LENGTH = CODE_LENGTH + LEN_LENGTH
38
39// A placeholder for a `BackendMessage`’s length value that will be set after construction.
40const LATEINIT_LENGTH = -1
41
42export type Packet = {
43 code: number
44 packet: Buffer
45}
46
47const emptyBuffer = Buffer.allocUnsafe(0)
48
49type StreamOptions = TransformOptions & {
50 mode: Mode
51}
52
53const enum MessageCodes {
54 DataRow = 0x44, // D
55 ParseComplete = 0x31, // 1
56 BindComplete = 0x32, // 2
57 CloseComplete = 0x33, // 3
58 CommandComplete = 0x43, // C
59 ReadyForQuery = 0x5a, // Z
60 NoData = 0x6e, // n
61 NotificationResponse = 0x41, // A
62 AuthenticationResponse = 0x52, // R
63 ParameterStatus = 0x53, // S
64 BackendKeyData = 0x4b, // K
65 ErrorMessage = 0x45, // E
66 NoticeMessage = 0x4e, // N
67 RowDescriptionMessage = 0x54, // T
68 ParameterDescriptionMessage = 0x74, // t
69 PortalSuspended = 0x73, // s
70 ReplicationStart = 0x57, // W
71 EmptyQuery = 0x49, // I
72 CopyIn = 0x47, // G
73 CopyOut = 0x48, // H
74 CopyDone = 0x63, // c
75 CopyData = 0x64, // d
76}
77
78export type MessageCallback = (msg: BackendMessage) => void
79
80export class Parser {
81 private buffer: Buffer = emptyBuffer
82 private bufferLength: number = 0
83 private bufferOffset: number = 0
84 private reader = new BufferReader()
85 private mode: Mode
86
87 constructor(opts?: StreamOptions) {
88 if (opts?.mode === 'binary') {
89 throw new Error('Binary mode not supported yet')
90 }
91 this.mode = opts?.mode || 'text'
92 }
93
94 public parse(buffer: Buffer, callback: MessageCallback) {
95 this.mergeBuffer(buffer)
96 const bufferFullLength = this.bufferOffset + this.bufferLength
97 let offset = this.bufferOffset
98 while (offset + HEADER_LENGTH <= bufferFullLength) {
99 // code is 1 byte long - it identifies the message type
100 const code = this.buffer[offset]
101 // length is 1 Uint32BE - it is the length of the message EXCLUDING the code
102 const length = this.buffer.readUInt32BE(offset + CODE_LENGTH)
103 const fullMessageLength = CODE_LENGTH + length
104 if (fullMessageLength + offset <= bufferFullLength) {
105 const message = this.handlePacket(offset + HEADER_LENGTH, code, length, this.buffer)
106 callback(message)
107 offset += fullMessageLength
108 } else {
109 break
110 }
111 }
112 if (offset === bufferFullLength) {
113 // No more use for the buffer
114 this.buffer = emptyBuffer
115 this.bufferLength = 0
116 this.bufferOffset = 0
117 } else {
118 // Adjust the cursors of remainingBuffer
119 this.bufferLength = bufferFullLength - offset
120 this.bufferOffset = offset
121 }
122 }
123
124 private mergeBuffer(buffer: Buffer): void {
125 if (this.bufferLength > 0) {
126 const newLength = this.bufferLength + buffer.byteLength
127 const newFullLength = newLength + this.bufferOffset
128 if (newFullLength > this.buffer.byteLength) {
129 // We can't concat the new buffer with the remaining one
130 let newBuffer: Buffer
131 if (newLength <= this.buffer.byteLength && this.bufferOffset >= this.bufferLength) {
132 // We can move the relevant part to the beginning of the buffer instead of allocating a new buffer
133 newBuffer = this.buffer
134 } else {
135 // Allocate a new larger buffer
136 let newBufferLength = this.buffer.byteLength * 2
137 while (newLength >= newBufferLength) {
138 newBufferLength *= 2
139 }
140 newBuffer = Buffer.allocUnsafe(newBufferLength)
141 }
142 // Move the remaining buffer to the new one
143 this.buffer.copy(newBuffer, 0, this.bufferOffset, this.bufferOffset + this.bufferLength)
144 this.buffer = newBuffer
145 this.bufferOffset = 0
146 }
147 // Concat the new buffer with the remaining one
148 buffer.copy(this.buffer, this.bufferOffset + this.bufferLength)
149 this.bufferLength = newLength
150 } else {
151 this.buffer = buffer
152 this.bufferOffset = 0
153 this.bufferLength = buffer.byteLength
154 }
155 }
156
157 private handlePacket(offset: number, code: number, length: number, bytes: Buffer): BackendMessage {
158 const { reader } = this
159
160 // NOTE: This undesirably retains the buffer in `this.reader` if the `parse*Message` calls below throw. However, those should only throw in the case of a protocol error, which normally results in the reader being discarded.
161 reader.setBuffer(offset, bytes)
162
163 let message: BackendMessage
164
165 switch (code) {
166 case MessageCodes.BindComplete:
167 message = bindComplete
168 break
169 case MessageCodes.ParseComplete:
170 message = parseComplete
171 break
172 case MessageCodes.CloseComplete:
173 message = closeComplete
174 break
175 case MessageCodes.NoData:
176 message = noData
177 break
178 case MessageCodes.PortalSuspended:
179 message = portalSuspended
180 break
181 case MessageCodes.CopyDone:
182 message = copyDone
183 break
184 case MessageCodes.ReplicationStart:
185 message = replicationStart
186 break
187 case MessageCodes.EmptyQuery:
188 message = emptyQuery
189 break
190 case MessageCodes.DataRow:
191 message = parseDataRowMessage(reader)
192 break
193 case MessageCodes.CommandComplete:
194 message = parseCommandCompleteMessage(reader)
195 break
196 case MessageCodes.ReadyForQuery:
197 message = parseReadyForQueryMessage(reader)
198 break
199 case MessageCodes.NotificationResponse:
200 message = parseNotificationMessage(reader)
201 break
202 case MessageCodes.AuthenticationResponse:
203 message = parseAuthenticationResponse(reader, length)
204 break
205 case MessageCodes.ParameterStatus:
206 message = parseParameterStatusMessage(reader)
207 break
208 case MessageCodes.BackendKeyData:
209 message = parseBackendKeyData(reader)
210 break
211 case MessageCodes.ErrorMessage:
212 message = parseErrorMessage(reader, 'error')
213 break
214 case MessageCodes.NoticeMessage:
215 message = parseErrorMessage(reader, 'notice')
216 break
217 case MessageCodes.RowDescriptionMessage:
218 message = parseRowDescriptionMessage(reader)
219 break
220 case MessageCodes.ParameterDescriptionMessage:
221 message = parseParameterDescriptionMessage(reader)
222 break
223 case MessageCodes.CopyIn:
224 message = parseCopyInMessage(reader)
225 break
226 case MessageCodes.CopyOut:
227 message = parseCopyOutMessage(reader)
228 break
229 case MessageCodes.CopyData:
230 message = parseCopyData(reader, length)
231 break
232 default:
233 return new DatabaseError('received invalid response: ' + code.toString(16), length, 'error')
234 }
235
236 reader.setBuffer(0, emptyBuffer)
237
238 message.length = length
239 return message
240 }
241}
242
243const parseReadyForQueryMessage = (reader: BufferReader) => {
244 const status = reader.string(1)
245 return new ReadyForQueryMessage(LATEINIT_LENGTH, status)
246}
247
248const parseCommandCompleteMessage = (reader: BufferReader) => {
249 const text = reader.cstring()
250 return new CommandCompleteMessage(LATEINIT_LENGTH, text)
251}
252
253const parseCopyData = (reader: BufferReader, length: number) => {
254 const chunk = reader.bytes(length - 4)
255 return new CopyDataMessage(LATEINIT_LENGTH, chunk)
256}
257
258const parseCopyInMessage = (reader: BufferReader) => parseCopyMessage(reader, 'copyInResponse')
259
260const parseCopyOutMessage = (reader: BufferReader) => parseCopyMessage(reader, 'copyOutResponse')
261
262const parseCopyMessage = (reader: BufferReader, messageName: MessageName) => {
263 const isBinary = reader.byte() !== 0
264 const columnCount = reader.int16()
265 const message = new CopyResponse(LATEINIT_LENGTH, messageName, isBinary, columnCount)
266 for (let i = 0; i < columnCount; i++) {
267 message.columnTypes[i] = reader.int16()
268 }
269 return message
270}
271
272const parseNotificationMessage = (reader: BufferReader) => {
273 const processId = reader.int32()
274 const channel = reader.cstring()
275 const payload = reader.cstring()
276 return new NotificationResponseMessage(LATEINIT_LENGTH, processId, channel, payload)
277}
278
279const parseRowDescriptionMessage = (reader: BufferReader) => {
280 const fieldCount = reader.int16()
281 const message = new RowDescriptionMessage(LATEINIT_LENGTH, fieldCount)
282 for (let i = 0; i < fieldCount; i++) {
283 message.fields[i] = parseField(reader)
284 }
285 return message
286}
287
288const parseField = (reader: BufferReader) => {
289 const name = reader.cstring()
290 const tableID = reader.uint32()
291 const columnID = reader.int16()
292 const dataTypeID = reader.uint32()
293 const dataTypeSize = reader.int16()
294 const dataTypeModifier = reader.int32()
295 const mode = reader.int16() === 0 ? 'text' : 'binary'
296 return new Field(name, tableID, columnID, dataTypeID, dataTypeSize, dataTypeModifier, mode)
297}
298
299const parseParameterDescriptionMessage = (reader: BufferReader) => {
300 const parameterCount = reader.int16()
301 const message = new ParameterDescriptionMessage(LATEINIT_LENGTH, parameterCount)
302 for (let i = 0; i < parameterCount; i++) {
303 // OIDs are unsigned, same as dataTypeID in parseField above
304 message.dataTypeIDs[i] = reader.uint32()
305 }
306 return message
307}
308
309const parseDataRowMessage = (reader: BufferReader) => {
310 const fieldCount = reader.int16()
311 const fields: any[] = new Array(fieldCount)
312 for (let i = 0; i < fieldCount; i++) {
313 const len = reader.int32()
314 // a -1 for length means the value of the field is null
315 fields[i] = len === -1 ? null : reader.string(len)
316 }
317 return new DataRowMessage(LATEINIT_LENGTH, fields)
318}
319
320const parseParameterStatusMessage = (reader: BufferReader) => {
321 const name = reader.cstring()
322 const value = reader.cstring()
323 return new ParameterStatusMessage(LATEINIT_LENGTH, name, value)
324}
325
326const parseBackendKeyData = (reader: BufferReader) => {
327 const processID = reader.int32()
328 const secretKey = reader.int32()
329 return new BackendKeyDataMessage(LATEINIT_LENGTH, processID, secretKey)
330}
331
332const parseAuthenticationResponse = (reader: BufferReader, length: number) => {
333 const code = reader.int32()
334 // TODO(bmc): maybe better types here
335 const message: BackendMessage & any = {
336 name: 'authenticationOk',
337 length,
338 }
339
340 switch (code) {
341 case 0: // AuthenticationOk
342 break
343 case 3: // AuthenticationCleartextPassword
344 if (message.length === 8) {
345 message.name = 'authenticationCleartextPassword'
346 }
347 break
348 case 5: // AuthenticationMD5Password
349 if (message.length === 12) {
350 message.name = 'authenticationMD5Password'
351 const salt = reader.bytes(4)
352 return new AuthenticationMD5Password(LATEINIT_LENGTH, salt)
353 }
354 break
355 case 10: // AuthenticationSASL
356 {
357 message.name = 'authenticationSASL'
358 message.mechanisms = []
359 let mechanism: string
360 do {
361 mechanism = reader.cstring()
362 if (mechanism) {
363 message.mechanisms.push(mechanism)
364 }
365 } while (mechanism)
366 }
367 break
368 case 11: // AuthenticationSASLContinue
369 message.name = 'authenticationSASLContinue'
370 message.data = reader.string(length - 8)
371 break
372 case 12: // AuthenticationSASLFinal
373 message.name = 'authenticationSASLFinal'
374 message.data = reader.string(length - 8)
375 break
376 default:
377 throw new Error('Unknown authenticationOk message type ' + code)
378 }
379 return message
380}
381
382const parseErrorMessage = (reader: BufferReader, name: MessageName) => {
383 const fields: Record<string, string> = {}
384 let fieldType = reader.string(1)
385 while (fieldType !== '\0') {
386 fields[fieldType] = reader.cstring()
387 fieldType = reader.string(1)
388 }
389
390 const messageValue = fields.M
391
392 const message =
393 name === 'notice'
394 ? new NoticeMessage(LATEINIT_LENGTH, messageValue)
395 : new DatabaseError(messageValue, LATEINIT_LENGTH, name)
396
397 message.severity = fields.S
398 message.code = fields.C
399 message.detail = fields.D
400 message.hint = fields.H
401 message.position = fields.P
402 message.internalPosition = fields.p
403 message.internalQuery = fields.q
404 message.where = fields.W
405 message.schema = fields.s
406 message.table = fields.t
407 message.column = fields.c
408 message.dataType = fields.d
409 message.constraint = fields.n
410 message.file = fields.F
411 message.line = fields.L
412 message.routine = fields.R
413 return message
414}
Note: See TracBrowser for help on using the repository browser.