'use strict'; const Errors = require('../misc/errors'); const Iconv = require('iconv-lite'); const Long = require('long'); const moment = require('moment-timezone'); /** * Object to easily parse buffer. * */ class Packet { constructor(buf, pos, end) { this.buf = buf; this.pos = pos; this.end = end; } skip(n) { this.pos += n; } readGeometry(dataTypeName) { const geoBuf = this.readBufferLengthEncoded(); if (geoBuf === null || geoBuf.length === 0) { if (dataTypeName) { switch (dataTypeName) { case 'point': return { type: 'Point' }; case 'linestring': return { type: 'LineString' }; case 'polygon': return { type: 'Polygon' }; case 'multipoint': return { type: 'MultiPoint' }; case 'multilinestring': return { type: 'MultiLineString' }; case 'multipolygon': return { type: 'MultiPolygon' }; default: return { type: dataTypeName }; } } return null; } let geoPos = 4; return readGeometryObject(false); function parseCoordinates(byteOrder) { geoPos += 16; const x = byteOrder ? geoBuf.readDoubleLE(geoPos - 16) : geoBuf.readDoubleBE(geoPos - 16); const y = byteOrder ? geoBuf.readDoubleLE(geoPos - 8) : geoBuf.readDoubleBE(geoPos - 8); return [x, y]; } function readGeometryObject(inner) { const byteOrder = geoBuf[geoPos++]; const wkbType = byteOrder ? geoBuf.readInt32LE(geoPos) : geoBuf.readInt32BE(geoPos); geoPos += 4; switch (wkbType) { case 1: //wkbPoint const coords = parseCoordinates(byteOrder); if (inner) return coords; return { type: 'Point', coordinates: coords }; case 2: //wkbLineString const pointNumber = byteOrder ? geoBuf.readInt32LE(geoPos) : geoBuf.readInt32BE(geoPos); geoPos += 4; let coordinates = []; for (let i = 0; i < pointNumber; i++) { coordinates.push(parseCoordinates(byteOrder)); } if (inner) return coordinates; return { type: 'LineString', coordinates: coordinates }; case 3: //wkbPolygon let polygonCoordinates = []; const numRings = byteOrder ? geoBuf.readInt32LE(geoPos) : geoBuf.readInt32BE(geoPos); geoPos += 4; for (let ring = 0; ring < numRings; ring++) { const pointNumber = byteOrder ? geoBuf.readInt32LE(geoPos) : geoBuf.readInt32BE(geoPos); geoPos += 4; let linesCoordinates = []; for (let i = 0; i < pointNumber; i++) { linesCoordinates.push(parseCoordinates(byteOrder)); } polygonCoordinates.push(linesCoordinates); } if (inner) return polygonCoordinates; return { type: 'Polygon', coordinates: polygonCoordinates }; case 4: //wkbMultiPoint return { type: 'MultiPoint', coordinates: parseGeomArray(byteOrder, true) }; case 5: //wkbMultiLineString return { type: 'MultiLineString', coordinates: parseGeomArray(byteOrder, true) }; case 6: //wkbMultiPolygon return { type: 'MultiPolygon', coordinates: parseGeomArray(byteOrder, true) }; case 7: //wkbGeometryCollection return { type: 'GeometryCollection', geometries: parseGeomArray(byteOrder, false) }; } return null; } function parseGeomArray(byteOrder, inner) { let coordinates = []; const number = byteOrder ? geoBuf.readInt32LE(geoPos) : geoBuf.readInt32BE(geoPos); geoPos += 4; for (let i = 0; i < number; i++) { coordinates.push(readGeometryObject(inner)); } return coordinates; } } peek() { return this.buf[this.pos]; } remaining() { return this.end - this.pos > 0; } readUInt8() { return this.buf[this.pos++]; } readUInt16() { return this.buf[this.pos++] + (this.buf[this.pos++] << 8); } readUInt24() { return this.buf[this.pos++] + (this.buf[this.pos++] << 8) + (this.buf[this.pos++] << 16); } readUInt32() { return ( this.buf[this.pos++] + (this.buf[this.pos++] << 8) + (this.buf[this.pos++] << 16) + this.buf[this.pos++] * 0x1000000 ); } readInt32() { return ( this.buf[this.pos++] + (this.buf[this.pos++] << 8) + (this.buf[this.pos++] << 16) + (this.buf[this.pos++] << 24) ); } readInt32LE() { return ( (this.buf[this.pos++] << 24) + (this.buf[this.pos++] << 16) + (this.buf[this.pos++] << 8) + this.buf[this.pos++] ); } readInt64() { // could use readBigInt64LE when support would be 10.20+ const val = this.buf[this.pos + 4] + this.buf[this.pos + 5] * 2 ** 8 + this.buf[this.pos + 6] * 2 ** 16 + (this.buf[this.pos + 7] << 24); const vv = (BigInt(val) << BigInt(32)) + BigInt( this.buf[this.pos] + this.buf[this.pos + 1] * 2 ** 8 + this.buf[this.pos + 2] * 2 ** 16 + this.buf[this.pos + 3] * 2 ** 24 ); this.pos += 8; return vv; } readUnsignedLength() { const type = this.buf[this.pos++] & 0xff; switch (type) { case 0xfb: return null; case 0xfc: return this.readUInt16(); case 0xfd: return this.readUInt24(); case 0xfe: // limitation to BigInt signed value return Number(this.readInt64()); default: return type; } } readBuffer(len) { this.pos += len; return this.buf.slice(this.pos - len, this.pos); } readBufferRemaining() { let b = this.buf.slice(this.pos, this.end); this.pos = this.end; return b; } readBufferLengthEncoded() { const len = this.readUnsignedLength(); if (len === null) return null; this.pos += len; return this.buf.slice(this.pos - len, this.pos); } readStringNullEnded() { let initialPosition = this.pos; let cnt = 0; while (this.remaining() > 0 && this.buf[this.pos++] !== 0) { cnt++; } return this.buf.toString('utf8', initialPosition, initialPosition + cnt); } readSignedLength() { const type = this.buf[this.pos++]; switch (type) { case 0xfb: return null; case 0xfc: return this.readUInt16(); case 0xfd: return this.readUInt24(); case 0xfe: return Number(this.readInt64()); default: return type; } } readSignedLengthBigInt() { const type = this.buf[this.pos++]; switch (type) { case 0xfb: return null; case 0xfc: return BigInt(this.readUInt16()); case 0xfd: return BigInt(this.readUInt24()); case 0xfe: return this.readInt64(); default: return BigInt(type); } } readAsciiStringLengthEncoded() { const len = this.readUnsignedLength(); if (len === null) return null; this.pos += len; return this.buf.toString('ascii', this.pos - len, this.pos); } readStringLengthEncoded(encoding) { const len = this.readUnsignedLength(); if (len === null) return null; this.pos += len; if (Buffer.isEncoding(encoding)) { return this.buf.toString(encoding, this.pos - len, this.pos); } return Iconv.decode(this.buf.slice(this.pos - len, this.pos), encoding); } readLongLengthEncoded(supportBigInt, supportBigNumbers, bigNumberStrings, unsigned) { const len = this.readUnsignedLength(); if (len === null) return null; if (supportBigInt) { const str = this.buf.toString('ascii', this.pos, this.pos + len); this.pos += len; return BigInt(str); } let result = 0; let negate = false; let begin = this.pos; //minus sign if (len > 0 && this.buf[begin] === 45) { negate = true; begin++; } for (; begin < this.pos + len; begin++) { result = result * 10 + (this.buf[begin] - 48); } let val = negate ? -1 * result : result; this.pos += len; if (!Number.isSafeInteger(val)) { const str = this.buf.toString('ascii', this.pos - len, this.pos); if (bigNumberStrings) return str; if (supportBigNumbers) { return Long.fromString(str, unsigned, 10); } } return val; } readDecimalLengthEncoded(bigNumberStrings) { const len = this.readUnsignedLength(); if (len === null) return null; this.pos += len; let str = this.buf.toString('ascii', this.pos - len, this.pos); return bigNumberStrings ? str : +str; } readDate() { const len = this.readUnsignedLength(); if (len === null) return null; let res = []; let value = 0; let initPos = this.pos; this.pos += len; while (initPos < this.pos) { const char = this.buf[initPos++]; if (char === 45) { //minus separator res.push(value); value = 0; } else { value = value * 10 + char - 48; } } res.push(value); //handle zero-date as null if (res[0] === 0 && res[1] === 0 && res[2] === 0) return null; return new Date(res[0], res[1] - 1, res[2]); } readDateTime(opts) { const len = this.readUnsignedLength(); if (len === null) return null; this.pos += len; const str = this.buf.toString('ascii', this.pos - len, this.pos); if (str.startsWith('0000-00-00 00:00:00')) return null; if (opts.tz) { return new Date( moment.tz(str, opts.tz).clone().tz(opts.localTz).format('YYYY-MM-DD HH:mm:ss.SSSSSS') ); } return new Date(str); } readIntLengthEncoded() { const len = this.readUnsignedLength(); if (len === null) return null; let result = 0; let negate = false; let begin = this.pos; if (len > 0 && this.buf[begin] === 45) { //minus sign negate = true; begin++; } for (; begin < this.pos + len; begin++) { result = result * 10 + (this.buf[begin] - 48); } this.pos += len; return negate ? -1 * result : result; } readFloatLengthCoded() { const len = this.readUnsignedLength(); if (len === null) return null; this.pos += len; return +this.buf.toString('ascii', this.pos - len, this.pos); } skipLengthCodedNumber() { const type = this.buf[this.pos++] & 0xff; switch (type) { case 251: return; case 252: this.pos += 2 + (0xffff & ((this.buf[this.pos] & 0xff) + ((this.buf[this.pos + 1] & 0xff) << 8))); return; case 253: this.pos += 3 + (0xffffff & ((this.buf[this.pos] & 0xff) + ((this.buf[this.pos + 1] & 0xff) << 8) + ((this.buf[this.pos + 2] & 0xff) << 16))); return; case 254: this.pos += 8 + ((this.buf[this.pos] & 0xff) + ((this.buf[this.pos + 1] & 0xff) << 8) + ((this.buf[this.pos + 2] & 0xff) << 16) + ((this.buf[this.pos + 3] & 0xff) << 24) + ((this.buf[this.pos + 4] & 0xff) << 32) + ((this.buf[this.pos + 5] & 0xff) << 40) + ((this.buf[this.pos + 6] & 0xff) << 48) + ((this.buf[this.pos + 7] & 0xff) << 56)); return; default: this.pos += type; return; } } positionFromEnd(num) { this.pos = this.end - num; } /** * For testing purpose only */ _toBuf() { return this.buf.slice(this.pos, this.end); } forceOffset(off) { this.pos = off; } length() { return this.end - this.pos; } subPacketLengthEncoded() { const len = this.readUnsignedLength(); this.skip(len); return new Packet(this.buf, this.pos - len, this.pos); } /** * Parse ERR_Packet : https://mariadb.com/kb/en/library/err_packet/ * * @param info current connection info * @param sql command sql * @param stack additional stack trace * @returns {Error} */ readError(info, sql, stack) { this.skip(1); let errorCode = this.readUInt16(); let sqlState = ''; if (this.peek() === 0x23) { this.skip(6); sqlState = this.buf.toString('utf8', this.pos - 5, this.pos); } let msg = this.buf.toString('utf8', this.pos, this.end); if (sql) msg += '\n' + sql; let fatal = sqlState.startsWith('08') || sqlState === '70100'; if (fatal) { const packetMsgs = info.getLastPackets(); if (packetMsgs !== '') return Errors.createError( msg + '\nlast received packets:\n' + packetMsgs, fatal, info, sqlState, errorCode, stack ); } return Errors.createError(msg, fatal, info, sqlState, errorCode, stack); } } module.exports = Packet;