parser.js
1 "use strict"; 2 Object.defineProperty(exports, "__esModule", { value: true }); 3 exports.Parser = void 0; 4 const messages_1 = require("./messages"); 5 const buffer_reader_1 = require("./buffer-reader"); 6 // every message is prefixed with a single bye 7 const CODE_LENGTH = 1; 8 // every message has an int32 length which includes itself but does 9 // NOT include the code in the length 10 const LEN_LENGTH = 4; 11 const HEADER_LENGTH = CODE_LENGTH + LEN_LENGTH; 12 const emptyBuffer = Buffer.allocUnsafe(0); 13 class Parser { 14 constructor(opts) { 15 this.buffer = emptyBuffer; 16 this.bufferLength = 0; 17 this.bufferOffset = 0; 18 this.reader = new buffer_reader_1.BufferReader(); 19 if ((opts === null || opts === void 0 ? void 0 : opts.mode) === 'binary') { 20 throw new Error('Binary mode not supported yet'); 21 } 22 this.mode = (opts === null || opts === void 0 ? void 0 : opts.mode) || 'text'; 23 } 24 parse(buffer, callback) { 25 this.mergeBuffer(buffer); 26 const bufferFullLength = this.bufferOffset + this.bufferLength; 27 let offset = this.bufferOffset; 28 while (offset + HEADER_LENGTH <= bufferFullLength) { 29 // code is 1 byte long - it identifies the message type 30 const code = this.buffer[offset]; 31 // length is 1 Uint32BE - it is the length of the message EXCLUDING the code 32 const length = this.buffer.readUInt32BE(offset + CODE_LENGTH); 33 const fullMessageLength = CODE_LENGTH + length; 34 if (fullMessageLength + offset <= bufferFullLength) { 35 const message = this.handlePacket(offset + HEADER_LENGTH, code, length, this.buffer); 36 callback(message); 37 offset += fullMessageLength; 38 } 39 else { 40 break; 41 } 42 } 43 if (offset === bufferFullLength) { 44 // No more use for the buffer 45 this.buffer = emptyBuffer; 46 this.bufferLength = 0; 47 this.bufferOffset = 0; 48 } 49 else { 50 // Adjust the cursors of remainingBuffer 51 this.bufferLength = bufferFullLength - offset; 52 this.bufferOffset = offset; 53 } 54 } 55 mergeBuffer(buffer) { 56 if (this.bufferLength > 0) { 57 const newLength = this.bufferLength + buffer.byteLength; 58 const newFullLength = newLength + this.bufferOffset; 59 if (newFullLength > this.buffer.byteLength) { 60 // We can't concat the new buffer with the remaining one 61 let newBuffer; 62 if (newLength <= this.buffer.byteLength && this.bufferOffset >= this.bufferLength) { 63 // We can move the relevant part to the beginning of the buffer instead of allocating a new buffer 64 newBuffer = this.buffer; 65 } 66 else { 67 // Allocate a new larger buffer 68 let newBufferLength = this.buffer.byteLength * 2; 69 while (newLength >= newBufferLength) { 70 newBufferLength *= 2; 71 } 72 newBuffer = Buffer.allocUnsafe(newBufferLength); 73 } 74 // Move the remaining buffer to the new one 75 this.buffer.copy(newBuffer, 0, this.bufferOffset, this.bufferOffset + this.bufferLength); 76 this.buffer = newBuffer; 77 this.bufferOffset = 0; 78 } 79 // Concat the new buffer with the remaining one 80 buffer.copy(this.buffer, this.bufferOffset + this.bufferLength); 81 this.bufferLength = newLength; 82 } 83 else { 84 this.buffer = buffer; 85 this.bufferOffset = 0; 86 this.bufferLength = buffer.byteLength; 87 } 88 } 89 handlePacket(offset, code, length, bytes) { 90 switch (code) { 91 case 50 /* MessageCodes.BindComplete */: 92 return messages_1.bindComplete; 93 case 49 /* MessageCodes.ParseComplete */: 94 return messages_1.parseComplete; 95 case 51 /* MessageCodes.CloseComplete */: 96 return messages_1.closeComplete; 97 case 110 /* MessageCodes.NoData */: 98 return messages_1.noData; 99 case 115 /* MessageCodes.PortalSuspended */: 100 return messages_1.portalSuspended; 101 case 99 /* MessageCodes.CopyDone */: 102 return messages_1.copyDone; 103 case 87 /* MessageCodes.ReplicationStart */: 104 return messages_1.replicationStart; 105 case 73 /* MessageCodes.EmptyQuery */: 106 return messages_1.emptyQuery; 107 case 68 /* MessageCodes.DataRow */: 108 return this.parseDataRowMessage(offset, length, bytes); 109 case 67 /* MessageCodes.CommandComplete */: 110 return this.parseCommandCompleteMessage(offset, length, bytes); 111 case 90 /* MessageCodes.ReadyForQuery */: 112 return this.parseReadyForQueryMessage(offset, length, bytes); 113 case 65 /* MessageCodes.NotificationResponse */: 114 return this.parseNotificationMessage(offset, length, bytes); 115 case 82 /* MessageCodes.AuthenticationResponse */: 116 return this.parseAuthenticationResponse(offset, length, bytes); 117 case 83 /* MessageCodes.ParameterStatus */: 118 return this.parseParameterStatusMessage(offset, length, bytes); 119 case 75 /* MessageCodes.BackendKeyData */: 120 return this.parseBackendKeyData(offset, length, bytes); 121 case 69 /* MessageCodes.ErrorMessage */: 122 return this.parseErrorMessage(offset, length, bytes, 'error'); 123 case 78 /* MessageCodes.NoticeMessage */: 124 return this.parseErrorMessage(offset, length, bytes, 'notice'); 125 case 84 /* MessageCodes.RowDescriptionMessage */: 126 return this.parseRowDescriptionMessage(offset, length, bytes); 127 case 116 /* MessageCodes.ParameterDescriptionMessage */: 128 return this.parseParameterDescriptionMessage(offset, length, bytes); 129 case 71 /* MessageCodes.CopyIn */: 130 return this.parseCopyInMessage(offset, length, bytes); 131 case 72 /* MessageCodes.CopyOut */: 132 return this.parseCopyOutMessage(offset, length, bytes); 133 case 100 /* MessageCodes.CopyData */: 134 return this.parseCopyData(offset, length, bytes); 135 default: 136 return new messages_1.DatabaseError('received invalid response: ' + code.toString(16), length, 'error'); 137 } 138 } 139 parseReadyForQueryMessage(offset, length, bytes) { 140 this.reader.setBuffer(offset, bytes); 141 const status = this.reader.string(1); 142 return new messages_1.ReadyForQueryMessage(length, status); 143 } 144 parseCommandCompleteMessage(offset, length, bytes) { 145 this.reader.setBuffer(offset, bytes); 146 const text = this.reader.cstring(); 147 return new messages_1.CommandCompleteMessage(length, text); 148 } 149 parseCopyData(offset, length, bytes) { 150 const chunk = bytes.slice(offset, offset + (length - 4)); 151 return new messages_1.CopyDataMessage(length, chunk); 152 } 153 parseCopyInMessage(offset, length, bytes) { 154 return this.parseCopyMessage(offset, length, bytes, 'copyInResponse'); 155 } 156 parseCopyOutMessage(offset, length, bytes) { 157 return this.parseCopyMessage(offset, length, bytes, 'copyOutResponse'); 158 } 159 parseCopyMessage(offset, length, bytes, messageName) { 160 this.reader.setBuffer(offset, bytes); 161 const isBinary = this.reader.byte() !== 0; 162 const columnCount = this.reader.int16(); 163 const message = new messages_1.CopyResponse(length, messageName, isBinary, columnCount); 164 for (let i = 0; i < columnCount; i++) { 165 message.columnTypes[i] = this.reader.int16(); 166 } 167 return message; 168 } 169 parseNotificationMessage(offset, length, bytes) { 170 this.reader.setBuffer(offset, bytes); 171 const processId = this.reader.int32(); 172 const channel = this.reader.cstring(); 173 const payload = this.reader.cstring(); 174 return new messages_1.NotificationResponseMessage(length, processId, channel, payload); 175 } 176 parseRowDescriptionMessage(offset, length, bytes) { 177 this.reader.setBuffer(offset, bytes); 178 const fieldCount = this.reader.int16(); 179 const message = new messages_1.RowDescriptionMessage(length, fieldCount); 180 for (let i = 0; i < fieldCount; i++) { 181 message.fields[i] = this.parseField(); 182 } 183 return message; 184 } 185 parseField() { 186 const name = this.reader.cstring(); 187 const tableID = this.reader.uint32(); 188 const columnID = this.reader.int16(); 189 const dataTypeID = this.reader.uint32(); 190 const dataTypeSize = this.reader.int16(); 191 const dataTypeModifier = this.reader.int32(); 192 const mode = this.reader.int16() === 0 ? 'text' : 'binary'; 193 return new messages_1.Field(name, tableID, columnID, dataTypeID, dataTypeSize, dataTypeModifier, mode); 194 } 195 parseParameterDescriptionMessage(offset, length, bytes) { 196 this.reader.setBuffer(offset, bytes); 197 const parameterCount = this.reader.int16(); 198 const message = new messages_1.ParameterDescriptionMessage(length, parameterCount); 199 for (let i = 0; i < parameterCount; i++) { 200 message.dataTypeIDs[i] = this.reader.int32(); 201 } 202 return message; 203 } 204 parseDataRowMessage(offset, length, bytes) { 205 this.reader.setBuffer(offset, bytes); 206 const fieldCount = this.reader.int16(); 207 const fields = new Array(fieldCount); 208 for (let i = 0; i < fieldCount; i++) { 209 const len = this.reader.int32(); 210 // a -1 for length means the value of the field is null 211 fields[i] = len === -1 ? null : this.reader.string(len); 212 } 213 return new messages_1.DataRowMessage(length, fields); 214 } 215 parseParameterStatusMessage(offset, length, bytes) { 216 this.reader.setBuffer(offset, bytes); 217 const name = this.reader.cstring(); 218 const value = this.reader.cstring(); 219 return new messages_1.ParameterStatusMessage(length, name, value); 220 } 221 parseBackendKeyData(offset, length, bytes) { 222 this.reader.setBuffer(offset, bytes); 223 const processID = this.reader.int32(); 224 const secretKey = this.reader.int32(); 225 return new messages_1.BackendKeyDataMessage(length, processID, secretKey); 226 } 227 parseAuthenticationResponse(offset, length, bytes) { 228 this.reader.setBuffer(offset, bytes); 229 const code = this.reader.int32(); 230 // TODO(bmc): maybe better types here 231 const message = { 232 name: 'authenticationOk', 233 length, 234 }; 235 switch (code) { 236 case 0: // AuthenticationOk 237 break; 238 case 3: // AuthenticationCleartextPassword 239 if (message.length === 8) { 240 message.name = 'authenticationCleartextPassword'; 241 } 242 break; 243 case 5: // AuthenticationMD5Password 244 if (message.length === 12) { 245 message.name = 'authenticationMD5Password'; 246 const salt = this.reader.bytes(4); 247 return new messages_1.AuthenticationMD5Password(length, salt); 248 } 249 break; 250 case 10: // AuthenticationSASL 251 { 252 message.name = 'authenticationSASL'; 253 message.mechanisms = []; 254 let mechanism; 255 do { 256 mechanism = this.reader.cstring(); 257 if (mechanism) { 258 message.mechanisms.push(mechanism); 259 } 260 } while (mechanism); 261 } 262 break; 263 case 11: // AuthenticationSASLContinue 264 message.name = 'authenticationSASLContinue'; 265 message.data = this.reader.string(length - 8); 266 break; 267 case 12: // AuthenticationSASLFinal 268 message.name = 'authenticationSASLFinal'; 269 message.data = this.reader.string(length - 8); 270 break; 271 default: 272 throw new Error('Unknown authenticationOk message type ' + code); 273 } 274 return message; 275 } 276 parseErrorMessage(offset, length, bytes, name) { 277 this.reader.setBuffer(offset, bytes); 278 const fields = {}; 279 let fieldType = this.reader.string(1); 280 while (fieldType !== '\0') { 281 fields[fieldType] = this.reader.cstring(); 282 fieldType = this.reader.string(1); 283 } 284 const messageValue = fields.M; 285 const message = name === 'notice' ? new messages_1.NoticeMessage(length, messageValue) : new messages_1.DatabaseError(messageValue, length, name); 286 message.severity = fields.S; 287 message.code = fields.C; 288 message.detail = fields.D; 289 message.hint = fields.H; 290 message.position = fields.P; 291 message.internalPosition = fields.p; 292 message.internalQuery = fields.q; 293 message.where = fields.W; 294 message.schema = fields.s; 295 message.table = fields.t; 296 message.column = fields.c; 297 message.dataType = fields.d; 298 message.constraint = fields.n; 299 message.file = fields.F; 300 message.line = fields.L; 301 message.routine = fields.R; 302 return message; 303 } 304 } 305 exports.Parser = Parser; 306 //# sourceMappingURL=parser.js.map