node-query/node_modules/mysql2/lib/packets/packet.js

607 lines
16 KiB
JavaScript

//var BigNumber = require("bignumber.js");
var ErrorCodeToName = require('../constants/errors').codeToName;
var bn = require('bn.js');
function Packet(id, buffer, start, end)
{
this.sequenceId = id;
this.buffer = buffer;
this.offset = start || 0;
this.start = start || 0;
this.end = end || this.buffer.length;
}
// ==============================
// readers
// ==============================
Packet.prototype.reset = function() {
this.offset = this.start;
};
Packet.prototype.length = function() {
return this.end - this.start;
};
Packet.prototype.slice = function() {
return this.buffer.slice(this.start, this.end);
};
Packet.prototype.dump = function() {
console.log([this.buffer.asciiSlice(this.start, this.end)], this.buffer.slice(this.start, this.end), this.length(), this.sequenceId);
};
Packet.prototype.haveMoreData = function() {
return this.end > this.offset;
};
Packet.prototype.skip = function(num) {
if (!num)
throw "Bad param in skip!"; // for some reason I keep doing skip(0) to indicate "skip one byte which should be 0"
this.offset += num;
};
Packet.prototype.readInt8 = function()
{
return this.buffer[this.offset++];
};
Packet.prototype.readInt16 = function()
{
this.offset += 2;
return this.buffer.readUInt16LE(this.offset - 2, true);
};
Packet.prototype.readInt24 = function() {
return this.readInt16() + (this.readInt8() << 16);
};
Packet.prototype.readInt32 = function()
{
this.offset += 4;
return this.buffer.readUInt32LE(this.offset - 4, true);
};
Packet.prototype.readSInt8 = function()
{
return this.buffer.readInt8(this.offset++, true);
};
Packet.prototype.readSInt16 = function()
{
this.offset += 2;
return this.buffer.readInt16LE(this.offset - 2, true);
};
Packet.prototype.readSInt32 = function()
{
this.offset += 4;
return this.buffer.readInt32LE(this.offset - 4, true);
};
Packet.prototype.readInt64 = function() {
return this.readInt32() + 0x100000000*this.readInt32();
};
Packet.prototype.readSInt64 = function() {
var word0 = this.readInt32();
var word1 = this.readInt32();
if (!(word1 & 0x80000000))
return word0 + 0x100000000*word1;
return -((((~word1)>>>0) * 0x100000000) + ((~word0)>>>0) + 1);
};
Packet.prototype.isEOF = function() {
return this.buffer[this.offset] == 0xfe && this.length() < 9;
};
Packet.prototype.eofStatusFlags = function() {
return this.buffer.readInt16LE(this.offset + 3);
};
Packet.prototype.eofWarningCount = function() {
return this.buffer.readInt16LE(this.offset + 1);
};
Packet.prototype.readLengthCodedNumber = function(bigNumberStrings) {
var word0, word1;
var res;
var byte1 = this.readInt8();
if (byte1 < 0xfb)
return byte1;
if (byte1 == 0xfc) {
return this.readInt8() + (this.readInt8() << 8);
}
if (byte1 == 0xfd) {
return this.readInt8() + (this.readInt8() << 8) + (this.readInt8() << 16);
}
if (byte1 == 0xfe) {
// TODO: check version
// Up to MySQL 3.22, 0xfe was followed by a 4-byte integer.
word0 = this.readInt32();
word1 = this.readInt32();
if (word1 === 0)
return word0; // don't convert to float if possible
if (word1 < 2097152) // max exact float point int, 2^52 / 2^32
return word1*0x100000000 + word0;
res = (new bn(word1)).ishln(32).iaddn(word0);
return bigNumberStrings ? res.toString() : res;
}
if (byte1 == 0xfb)
return null;
console.trace();
throw "Should not reach here: " + byte1;
};
Packet.prototype.readFloat = function() {
var res = this.buffer.readFloatLE(this.offset);
this.offset += 4;
return res;
};
Packet.prototype.readDouble = function() {
var res = this.buffer.readDoubleLE(this.offset);
this.offset += 8;
return res;
};
Packet.prototype.readBuffer = function(len) {
if (typeof len == 'undefined')
len = this.end - this.offset;
this.offset += len;
return this.buffer.slice(this.offset - len, this.offset);
};
var INVALID_DATE = new Date(NaN);
// DATE, DATETIME and TIMESTAMP
Packet.prototype.readDateTime = function() {
var length = this.readInt8();
var y = 0;
var m = 0;
var d = 0;
var H = 0;
var M = 0;
var S = 0;
var ms = 0;
if (length > 3) {
y = this.readInt16();
m = this.readInt8();
d = this.readInt8();
}
if (length > 6) {
H = this.readInt8();
M = this.readInt8();
S = this.readInt8();
}
if (length > 11)
ms = this.readInt32();
if ((y + m + d + H + M + S + ms) === 0) {
return INVALID_DATE;
}
return new Date(y, m-1, d, H, M, S, ms);
};
// this is nearly duplicate of previous function so generated code is not slower
// due to "if (dateStrings)" branching
var pad = "000000000000";
function leftPad(num, value) {
var s = value.toString();
// if we don't need to pad
if (s.length >= num)
return s;
return (pad + s).slice(-num);
}
Packet.prototype.readDateTimeString = function() {
var length = this.readInt8();
var y = 0;
var m = 0;
var d = 0;
var H = 0;
var M = 0;
var S = 0;
var ms = 0;
var str;
if (length > 3) {
y = this.readInt16();
m = this.readInt8();
d = this.readInt8();
str = [leftPad(4, y), leftPad(2, m), leftPad(2, d)].join('-');
}
if (length > 6) {
H = this.readInt8();
M = this.readInt8();
S = this.readInt8();
str += ' ' + [leftPad(2, H), leftPad(2, M), leftPad(2, S)].join(':');
}
/* in text protocol you don't see microseconds as DATETIME/TIMESTAMP result.
instead you need to use MICROSECOND() function
if (length > 11) {
ms = this.readInt32();
}
*/
return str;
};
// TIME - value as a string, Can be negative
Packet.prototype.readTimeString = function(convertTtoMs) {
var length = this.readInt8();
if (length === 0)
return 0;
var result = 0;
var sign = this.readInt8() ? -1 : 1; // 'isNegative' flag byte
var d = 0;
var H = 0;
var M = 0;
var S = 0;
var ms = 0;
if (length > 7) {
d = this.readInt32();
H = this.readInt8();
M = this.readInt8();
S = this.readInt8();
}
if (length > 11)
ms = this.readInt32();
if (convertTtoMs) {
H += d * 24;
M += H * 60;
S += M * 60;
ms += S * 1000;
ms *= sign;
return ms;
}
return ( sign === -1 ? '-' : '' ) + [( d ? (d*24) + H : H ), leftPad(2, M), leftPad(2, S)].join(':') + ( ms ? '.'+ ms : '' );
};
Packet.prototype.readLengthCodedString = function() {
var len = this.readLengthCodedNumber();
// TODO: check manually first byte here to avoid polymorphic return type?
if (len === null)
return null;
this.offset += len;
return this.buffer.utf8Slice(this.offset - len, this.offset);
};
Packet.prototype.readLengthCodedBuffer = function() {
var len = this.readLengthCodedNumber();
return this.readBuffer(len);
};
Packet.prototype.readNullTerminatedString = function() {
var start = this.offset;
var end = this.offset;
while (this.buffer[end])
end = end + 1; // TODO: handle OOB check
this.offset = end + 1;
return this.buffer.utf8Slice(start, end);
};
// TODO reuse?
Packet.prototype.readString = function(len) {
if (typeof len == 'undefined')
len = this.end - this.offset;
this.offset += len;
return this.buffer.utf8Slice(this.offset - len, this.offset);
};
var minus = '-'.charCodeAt(0);
var plus = '+'.charCodeAt(0);
// TODO: faster versions of parseInt for smaller types?
// discard overflow checks if we know from type definition it's safe so
Packet.prototype.parseInt = function(len) {
if (len === null)
return null;
var result = 0;
var end = this.offset + len;
var sign = 1;
if (len === 0)
return 0; // TODO: assert? exception?
if (this.buffer[this.offset] == minus) {
this.offset++;
sign = -1;
}
// max precise int is 9007199254740992
// note that we are here after handling optional +/-
// treat everything above 9000000000000000 as potential overflow
var str;
var numDigits = end - this.offset;
if (numDigits == 16 && (this.buffer[this.offset] - 48) > 8) {
str = this.readString(end - this.offset);
result = parseInt(str, 10);
if (result.toString() == str)
return sign*result;
else
return sign == -1 ? "-" + str : str;
} else if (numDigits > 16) {
str = this.readString(end - this.offset);
return sign == -1 ? "-" + str : str;
}
if (this.buffer[this.offset] == plus) {
this.offset++; // just ignore
}
while(this.offset < end) {
result *= 10;
result += this.buffer[this.offset] - 48;
this.offset++;
}
return result*sign;
};
// copy-paste from https://github.com/felixge/node-mysql/blob/master/lib/protocol/Parser.js
Packet.prototype.parseGeometryValue = function() {
var buffer = this.readLengthCodedBuffer();
var offset = 4;
if (buffer === null || !buffer.length) {
return null;
}
function parseGeometry() {
var x, y, i, j, numPoints, line;
var result = null;
var byteOrder = buffer.readUInt8(offset); offset += 1;
var wkbType = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
switch(wkbType) {
case 1: // WKBPoint
x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
result = {x: x, y: y};
break;
case 2: // WKBLineString
numPoints = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
result = [];
for(i=numPoints;i>0;i--) {
x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
result.push({x: x, y: y});
}
break;
case 3: // WKBPolygon
var numRings = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
result = [];
for(i=numRings;i>0;i--) {
numPoints = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
line = [];
for(j=numPoints;j>0;j--) {
x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
line.push({x: x, y: y});
}
result.push(line);
}
break;
case 4: // WKBMultiPoint
case 5: // WKBMultiLineString
case 6: // WKBMultiPolygon
case 7: // WKBGeometryCollection
var num = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
result = [];
for(i=num;i>0;i--) {
result.push(parseGeometry());
}
break;
}
return result;
}
return parseGeometry();
};
Packet.prototype.parseDate = function() {
var strLen = this.readLengthCodedNumber();
if (strLen === null)
return null;
if (strLen != 10) {
// we expect only YYYY-MM-DD here.
// if for some reason it's not the case return invalid date
return new Date(NaN);
}
var y = this.parseInt(4);
this.offset++; // -
var m = this.parseInt(2);
this.offset++; // -
var d = this.parseInt(2);
return new Date(y, m-1, d);
};
Packet.prototype.parseDateTime = function() {
var str = this.readLengthCodedString();
if (str === null)
return null;
return new Date(str);
};
// TODO: handle E notation
var dot = '.'.charCodeAt(0);
var exponent = 'e'.charCodeAt(0);
var exponentCapital = 'E'.charCodeAt(0);
Packet.prototype.parseFloat = function(len) {
if (len === null)
return null;
var result = 0;
var end = this.offset + len;
var factor = 1;
var pastDot = false;
var charCode = 0;
if (len === 0)
return 0; // TODO: assert? exception?
if (this.buffer[this.offset] == minus) {
this.offset++;
factor = -1;
}
if (this.buffer[this.offset] == plus) {
this.offset++; // just ignore
}
while(this.offset < end) {
charCode = this.buffer[this.offset];
if (charCode == dot)
{
pastDot = true;
this.offset++;
} else if (charCode == exponent || charCode == exponentCapital) {
this.offset++;
var exponentValue = this.parseInt(end - this.offset);
return (result/factor)*Math.pow(10, exponentValue);
} else {
result *= 10;
result += this.buffer[this.offset] - 48;
this.offset++;
if (pastDot)
factor = factor*10;
}
}
return result/factor;
};
Packet.prototype.parseLengthCodedInt = function() {
return this.parseInt(this.readLengthCodedNumber());
};
Packet.prototype.parseLengthCodedFloat = function() {
return this.parseFloat(this.readLengthCodedNumber());
};
Packet.prototype.isError = function() {
return this.buffer[this.offset] == 0xff;
};
Packet.prototype.asError = function() {
this.reset();
var fieldCount = this.readInt8();
var errorCode = this.readInt16();
var sqlState = '';
if (this.buffer[this.offset] == 0x23)
sqlState = this.readBuffer(6).toString();
var message = this.readString();
var err = new Error(message);
err.code = ErrorCodeToName[errorCode];
err.sqlState = sqlState;
return err;
};
Packet.lengthCodedNumberLength = function(n) {
if (n < 0xfb)
return 1;
if (n < 0xffff)
return 3;
if (n < 0xffffff)
return 5;
else
return 9;
};
Packet.prototype.writeInt32 = function(n) {
this.buffer.writeUInt32LE(n, this.offset);
this.offset += 4;
};
Packet.prototype.writeInt24 = function(n) {
this.writeInt8(n & 0xff);
this.writeInt16(n >> 8);
};
Packet.prototype.writeInt16 = function(n) {
this.buffer.writeUInt16LE(n, this.offset);
this.offset += 2;
};
Packet.prototype.writeInt8 = function(n) {
this.buffer.writeUInt8(n, this.offset);
this.offset++;
};
Packet.prototype.writeBuffer = function(b) {
b.copy(this.buffer, this.offset);
this.offset += b.length;
};
Packet.prototype.writeNull = function() {
this.buffer[this.offset] = 0xfb;
this.offset++;
};
// TODO: refactor following three?
Packet.prototype.writeNullTerminatedString = function(s) {
this.buffer.write(s, this.offset);
this.offset += s.length;
this.writeInt8(0);
};
Packet.prototype.writeString = function(s) {
var bytes = Buffer.byteLength(s, 'utf8');
this.buffer.write(s, this.offset, bytes, 'utf8');
this.offset += bytes;
};
Packet.prototype.writeLengthCodedString = function(s) {
var bytes = Buffer.byteLength(s, 'utf8');
this.writeLengthCodedNumber(bytes);
this.buffer.write(s, this.offset, bytes, 'utf8');
this.offset += bytes;
};
Packet.prototype.writeLengthCodedBuffer = function(b) {
this.writeLengthCodedNumber(b.length);
b.copy(this.buffer, this.offset);
this.offset += b.length;
};
Packet.prototype.writeLengthCodedNumber = function(n) {
// TODO: null - http://dev.mysql.com/doc/internals/en/overview.html#length-encoded-integer
if (n < 0xfb)
return this.writeInt8(n);
if (n < 0xffff) {
this.writeInt8(0xfc);
return this.writeInt16(n);
}
if (n < 0xffffff) {
this.writeInt8(0xfd);
return this.writeInt24(n);
}
console.log(n);
console.trace();
throw "No bignumbers yet";
};
Packet.prototype.writeHeader = function(sequenceId)
{
var offset = this.offset;
this.offset = 0;
this.writeInt24(this.buffer.length - 4);
this.writeInt8(sequenceId);
this.offset = offset;
};
Packet.prototype.clone = function() {
var buffer = this.buffer.slice(this.start, this.end);
var other = new Packet(this.sequenceId, this.buffer);
return other;
};
Packet.prototype.type = function() {
if (this.isEOF())
return 'EOF';
if (this.isError())
return 'Error';
if (this.buffer[this.offset] == 0)
return 'maybeOK'; // could be other packet types as well
return '';
};
module.exports = Packet;