var MAX _PACKET _LENGTH = Math . pow ( 2 , 24 ) - 1 ;
var MUL _32BIT = Math . pow ( 2 , 32 ) ;
var PacketHeader = require ( './PacketHeader' ) ;
var BigNumber = require ( 'bignumber.js' ) ;
module . exports = Parser ;
function Parser ( options ) {
options = options || { } ;
this . _supportBigNumbers = options . config && options . config . supportBigNumbers ;
this . _buffer = new Buffer ( 0 ) ;
this . _longPacketBuffers = [ ] ;
this . _offset = 0 ;
this . _packetEnd = null ;
this . _packetHeader = null ;
this . _packetOffset = null ;
this . _onError = options . onError || function ( err ) { throw err ; } ;
this . _onPacket = options . onPacket || function ( ) { } ;
this . _nextPacketNumber = 0 ;
this . _encoding = 'utf-8' ;
this . _paused = false ;
}
Parser . prototype . write = function ( buffer ) {
this . append ( buffer ) ;
while ( ! this . _paused ) {
if ( ! this . _packetHeader ) {
if ( this . _bytesRemaining ( ) < 4 ) {
break ;
}
this . _packetHeader = new PacketHeader (
this . parseUnsignedNumber ( 3 ) ,
this . parseUnsignedNumber ( 1 )
) ;
if ( this . _packetHeader . number !== this . _nextPacketNumber ) {
var err = new Error (
'Packets out of order. Got: ' + this . _packetHeader . number + ' ' +
'Expected: ' + this . _nextPacketNumber
) ;
err . code = 'PROTOCOL_PACKETS_OUT_OF_ORDER' ;
err . fatal = true ;
this . _onError ( err ) ;
}
this . incrementPacketNumber ( ) ;
}
if ( this . _bytesRemaining ( ) < this . _packetHeader . length ) {
break ;
}
this . _packetEnd = this . _offset + this . _packetHeader . length ;
this . _packetOffset = this . _offset ;
if ( this . _packetHeader . length === MAX _PACKET _LENGTH ) {
this . _longPacketBuffers . push ( this . _buffer . slice ( this . _packetOffset , this . _packetEnd ) ) ;
this . _advanceToNextPacket ( ) ;
continue ;
}
this . _combineLongPacketBuffers ( ) ;
// Try...finally to ensure exception safety. Unfortunately this is costing
// us up to ~10% performance in some benchmarks.
var hadException = true ;
try {
this . _onPacket ( this . _packetHeader ) ;
hadException = false ;
} catch ( err ) {
if ( ! err || typeof err . code !== 'string' || err . code . substr ( 0 , 7 ) !== 'PARSER_' ) {
throw err ; // Rethrow non-MySQL errors
}
// Pass down parser errors
this . _onError ( err ) ;
hadException = false ;
} finally {
this . _advanceToNextPacket ( ) ;
// If we had an exception, the parser while loop will be broken out
// of after the finally block. So we need to make sure to re-enter it
// to continue parsing any bytes that may already have been received.
if ( hadException ) {
process . nextTick ( this . write . bind ( this ) ) ;
}
}
}
} ;
Parser . prototype . append = function append ( chunk ) {
if ( ! chunk || chunk . length === 0 ) {
return ;
}
var buffer = chunk ;
var sliceEnd = this . _buffer . length ;
var sliceStart = this . _packetOffset === null
? this . _offset
: this . _packetOffset ;
var sliceLength = sliceEnd - sliceStart ;
if ( sliceLength !== 0 ) {
// Create a new Buffer
buffer = new Buffer ( sliceLength + chunk . length ) ;
// Copy data
this . _buffer . copy ( buffer , 0 , sliceStart , sliceEnd ) ;
chunk . copy ( buffer , sliceLength ) ;
}
// Adjust data-tracking pointers
this . _buffer = buffer ;
this . _offset = this . _offset - sliceStart ;
this . _packetEnd = this . _packetEnd !== null
? this . _packetEnd - sliceStart
: null ;
this . _packetOffset = this . _packetOffset !== null
? this . _packetOffset - sliceStart
: null ;
} ;
Parser . prototype . pause = function ( ) {
this . _paused = true ;
} ;
Parser . prototype . resume = function ( ) {
this . _paused = false ;
// nextTick() to avoid entering write() multiple times within the same stack
// which would cause problems as write manipulates the state of the object.
process . nextTick ( this . write . bind ( this ) ) ;
} ;
Parser . prototype . peak = function ( ) {
return this . _buffer [ this . _offset ] ;
} ;
Parser . prototype . parseUnsignedNumber = function ( bytes ) {
if ( bytes === 1 ) {
return this . _buffer [ this . _offset ++ ] ;
}
var buffer = this . _buffer ;
var offset = this . _offset + bytes - 1 ;
var value = 0 ;
if ( bytes > 4 ) {
var err = new Error ( 'parseUnsignedNumber: Supports only up to 4 bytes' ) ;
err . offset = ( this . _offset - this . _packetOffset - 1 ) ;
err . code = 'PARSER_UNSIGNED_TOO_LONG' ;
throw err ;
}
while ( offset >= this . _offset ) {
value = ( ( value << 8 ) | buffer [ offset ] ) >>> 0 ;
offset -- ;
}
this . _offset += bytes ;
return value ;
} ;
Parser . prototype . parseLengthCodedString = function ( ) {
var length = this . parseLengthCodedNumber ( ) ;
if ( length === null ) {
return null ;
}
return this . parseString ( length ) ;
} ;
Parser . prototype . parseLengthCodedBuffer = function ( ) {
var length = this . parseLengthCodedNumber ( ) ;
if ( length === null ) {
return null ;
}
return this . parseBuffer ( length ) ;
} ;
Parser . prototype . parseLengthCodedNumber = function parseLengthCodedNumber ( ) {
if ( this . _offset >= this . _buffer . length ) {
var err = new Error ( 'Parser: read past end' ) ;
err . offset = ( this . _offset - this . _packetOffset ) ;
err . code = 'PARSER_READ_PAST_END' ;
throw err ;
}
var bits = this . _buffer [ this . _offset ++ ] ;
if ( bits <= 250 ) {
return bits ;
}
switch ( bits ) {
case 251 :
return null ;
case 252 :
return this . parseUnsignedNumber ( 2 ) ;
case 253 :
return this . parseUnsignedNumber ( 3 ) ;
case 254 :
break ;
default :
var err = new Error ( 'Unexpected first byte' + ( bits ? ': 0x' + bits . toString ( 16 ) : '' ) ) ;
err . offset = ( this . _offset - this . _packetOffset - 1 ) ;
err . code = 'PARSER_BAD_LENGTH_BYTE' ;
throw err ;
}
var low = this . parseUnsignedNumber ( 4 ) ;
var high = this . parseUnsignedNumber ( 4 ) ;
var value ;
if ( high >>> 21 ) {
value = ( new BigNumber ( low ) ) . plus ( ( new BigNumber ( MUL _32BIT ) ) . times ( high ) ) . toString ( ) ;
if ( this . _supportBigNumbers ) {
return value ;
}
var err = new Error (
'parseLengthCodedNumber: JS precision range exceeded, ' +
'number is >= 53 bit: "' + value + '"'
) ;
err . offset = ( this . _offset - this . _packetOffset - 8 ) ;
err . code = 'PARSER_JS_PRECISION_RANGE_EXCEEDED' ;
throw err ;
}
value = low + ( MUL _32BIT * high ) ;
return value ;
} ;
Parser . prototype . parseFiller = function ( length ) {
return this . parseBuffer ( length ) ;
} ;
Parser . prototype . parseNullTerminatedBuffer = function ( ) {
var end = this . _nullByteOffset ( ) ;
var value = this . _buffer . slice ( this . _offset , end ) ;
this . _offset = end + 1 ;
return value ;
} ;
Parser . prototype . parseNullTerminatedString = function ( ) {
var end = this . _nullByteOffset ( ) ;
var value = this . _buffer . toString ( this . _encoding , this . _offset , end ) ;
this . _offset = end + 1 ;
return value ;
} ;
Parser . prototype . _nullByteOffset = function ( ) {
var offset = this . _offset ;
while ( this . _buffer [ offset ] !== 0x00 ) {
offset ++ ;
if ( offset >= this . _buffer . length ) {
var err = new Error ( 'Offset of null terminated string not found.' ) ;
err . offset = ( this . _offset - this . _packetOffset ) ;
err . code = 'PARSER_MISSING_NULL_BYTE' ;
throw err ;
}
}
return offset ;
} ;
Parser . prototype . parsePacketTerminatedString = function ( ) {
var length = this . _packetEnd - this . _offset ;
return this . parseString ( length ) ;
} ;
Parser . prototype . parseBuffer = function ( length ) {
var response = new Buffer ( length ) ;
this . _buffer . copy ( response , 0 , this . _offset , this . _offset + length ) ;
this . _offset += length ;
return response ;
} ;
Parser . prototype . parseString = function ( length ) {
var offset = this . _offset ;
var end = offset + length ;
var value = this . _buffer . toString ( this . _encoding , offset , end ) ;
this . _offset = end ;
return value ;
} ;
Parser . prototype . parseGeometryValue = function ( ) {
var buffer = this . parseLengthCodedBuffer ( ) ;
var offset = 4 ;
if ( buffer === null || ! buffer . length ) {
return null ;
}
function parseGeometry ( ) {
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
var x = byteOrder ? buffer . readDoubleLE ( offset ) : buffer . readDoubleBE ( offset ) ; offset += 8 ;
var y = byteOrder ? buffer . readDoubleLE ( offset ) : buffer . readDoubleBE ( offset ) ; offset += 8 ;
result = { x : x , y : y } ;
break ;
case 2 : // WKBLineString
var numPoints = byteOrder ? buffer . readUInt32LE ( offset ) : buffer . readUInt32BE ( offset ) ; offset += 4 ;
result = [ ] ;
for ( var i = numPoints ; i > 0 ; i -- ) {
var x = byteOrder ? buffer . readDoubleLE ( offset ) : buffer . readDoubleBE ( offset ) ; offset += 8 ;
var 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 ( var i = numRings ; i > 0 ; i -- ) {
var numPoints = byteOrder ? buffer . readUInt32LE ( offset ) : buffer . readUInt32BE ( offset ) ; offset += 4 ;
var line = [ ] ;
for ( var j = numPoints ; j > 0 ; j -- ) {
var x = byteOrder ? buffer . readDoubleLE ( offset ) : buffer . readDoubleBE ( offset ) ; offset += 8 ;
var 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 ;
var result = [ ] ;
for ( var i = num ; i > 0 ; i -- ) {
result . push ( parseGeometry ( ) ) ;
}
break ;
}
return result ;
}
return parseGeometry ( ) ;
} ;
Parser . prototype . reachedPacketEnd = function ( ) {
return this . _offset === this . _packetEnd ;
} ;
Parser . prototype . _bytesRemaining = function ( ) {
return this . _buffer . length - this . _offset ;
} ;
Parser . prototype . incrementPacketNumber = function ( ) {
var currentPacketNumber = this . _nextPacketNumber ;
this . _nextPacketNumber = ( this . _nextPacketNumber + 1 ) % 256 ;
return currentPacketNumber ;
} ;
Parser . prototype . resetPacketNumber = function ( ) {
this . _nextPacketNumber = 0 ;
} ;
Parser . prototype . packetLength = function ( ) {
return this . _longPacketBuffers . reduce ( function ( length , buffer ) {
return length + buffer . length ;
} , this . _packetHeader . length ) ;
} ;
Parser . prototype . _combineLongPacketBuffers = function ( ) {
if ( ! this . _longPacketBuffers . length ) {
return ;
}
var trailingPacketBytes = this . _buffer . length - this . _packetEnd ;
var length = this . _longPacketBuffers . reduce ( function ( length , buffer ) {
return length + buffer . length ;
} , this . _bytesRemaining ( ) ) ;
var combinedBuffer = new Buffer ( length ) ;
var offset = this . _longPacketBuffers . reduce ( function ( offset , buffer ) {
buffer . copy ( combinedBuffer , offset ) ;
return offset + buffer . length ;
} , 0 ) ;
this . _buffer . copy ( combinedBuffer , offset , this . _offset ) ;
this . _buffer = combinedBuffer ;
this . _longPacketBuffers = [ ] ;
this . _offset = 0 ;
this . _packetEnd = this . _buffer . length - trailingPacketBytes ;
this . _packetOffset = 0 ;
} ;
Parser . prototype . _advanceToNextPacket = function ( ) {
this . _offset = this . _packetEnd ;
this . _packetHeader = null ;
this . _packetEnd = null ;
this . _packetOffset = null ;
} ;