| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 | 
							- 'use strict'
 
- var StringDecoder = require('string_decoder').StringDecoder
 
- var decoder = new StringDecoder()
 
- var ReplyError = require('./replyError')
 
- var ParserError = require('./parserError')
 
- var bufferPool = bufferAlloc(32 * 1024)
 
- var bufferOffset = 0
 
- var interval = null
 
- var counter = 0
 
- var notDecreased = 0
 
- var isModern = typeof Buffer.allocUnsafe === 'function'
 
- /**
 
-  * For backwards compatibility
 
-  * @param len
 
-  * @returns {Buffer}
 
-  */
 
- function bufferAlloc (len) {
 
-   return isModern ? Buffer.allocUnsafe(len) : new Buffer(len)
 
- }
 
- /**
 
-  * Used for lengths and numbers only, faster perf on arrays / bulks
 
-  * @param parser
 
-  * @returns {*}
 
-  */
 
- function parseSimpleNumbers (parser) {
 
-   var offset = parser.offset
 
-   var length = parser.buffer.length - 1
 
-   var number = 0
 
-   var sign = 1
 
-   if (parser.buffer[offset] === 45) {
 
-     sign = -1
 
-     offset++
 
-   }
 
-   while (offset < length) {
 
-     var c1 = parser.buffer[offset++]
 
-     if (c1 === 13) { // \r\n
 
-       parser.offset = offset + 1
 
-       return sign * number
 
-     }
 
-     number = (number * 10) + (c1 - 48)
 
-   }
 
- }
 
- /**
 
-  * Used for integer numbers in case of the returnNumbers option
 
-  *
 
-  * The maximimum possible integer to use is: Math.floor(Number.MAX_SAFE_INTEGER / 10)
 
-  * Staying in a SMI Math.floor((Math.pow(2, 32) / 10) - 1) is even more efficient though
 
-  *
 
-  * @param parser
 
-  * @returns {*}
 
-  */
 
- function parseStringNumbers (parser) {
 
-   var offset = parser.offset
 
-   var length = parser.buffer.length - 1
 
-   var number = 0
 
-   var res = ''
 
-   if (parser.buffer[offset] === 45) {
 
-     res += '-'
 
-     offset++
 
-   }
 
-   while (offset < length) {
 
-     var c1 = parser.buffer[offset++]
 
-     if (c1 === 13) { // \r\n
 
-       parser.offset = offset + 1
 
-       if (number !== 0) {
 
-         res += number
 
-       }
 
-       return res
 
-     } else if (number > 429496728) {
 
-       res += (number * 10) + (c1 - 48)
 
-       number = 0
 
-     } else if (c1 === 48 && number === 0) {
 
-       res += 0
 
-     } else {
 
-       number = (number * 10) + (c1 - 48)
 
-     }
 
-   }
 
- }
 
- /**
 
-  * Returns a string or buffer of the provided offset start and
 
-  * end ranges. Checks `optionReturnBuffers`.
 
-  *
 
-  * If returnBuffers is active, all return values are returned as buffers besides numbers and errors
 
-  *
 
-  * @param parser
 
-  * @param start
 
-  * @param end
 
-  * @returns {*}
 
-  */
 
- function convertBufferRange (parser, start, end) {
 
-   parser.offset = end + 2
 
-   if (parser.optionReturnBuffers === true) {
 
-     return parser.buffer.slice(start, end)
 
-   }
 
-   return parser.buffer.toString('utf-8', start, end)
 
- }
 
- /**
 
-  * Parse a '+' redis simple string response but forward the offsets
 
-  * onto convertBufferRange to generate a string.
 
-  * @param parser
 
-  * @returns {*}
 
-  */
 
- function parseSimpleString (parser) {
 
-   var start = parser.offset
 
-   var offset = start
 
-   var buffer = parser.buffer
 
-   var length = buffer.length - 1
 
-   while (offset < length) {
 
-     if (buffer[offset++] === 13) { // \r\n
 
-       return convertBufferRange(parser, start, offset - 1)
 
-     }
 
-   }
 
- }
 
- /**
 
-  * Returns the string length via parseSimpleNumbers
 
-  * @param parser
 
-  * @returns {*}
 
-  */
 
- function parseLength (parser) {
 
-   var string = parseSimpleNumbers(parser)
 
-   if (string !== undefined) {
 
-     return string
 
-   }
 
- }
 
- /**
 
-  * Parse a ':' redis integer response
 
-  *
 
-  * If stringNumbers is activated the parser always returns numbers as string
 
-  * This is important for big numbers (number > Math.pow(2, 53)) as js numbers
 
-  * are 64bit floating point numbers with reduced precision
 
-  *
 
-  * @param parser
 
-  * @returns {*}
 
-  */
 
- function parseInteger (parser) {
 
-   if (parser.optionStringNumbers) {
 
-     return parseStringNumbers(parser)
 
-   }
 
-   return parseSimpleNumbers(parser)
 
- }
 
- /**
 
-  * Parse a '$' redis bulk string response
 
-  * @param parser
 
-  * @returns {*}
 
-  */
 
- function parseBulkString (parser) {
 
-   var length = parseLength(parser)
 
-   if (length === undefined) {
 
-     return
 
-   }
 
-   if (length === -1) {
 
-     return null
 
-   }
 
-   var offsetEnd = parser.offset + length
 
-   if (offsetEnd + 2 > parser.buffer.length) {
 
-     parser.bigStrSize = offsetEnd + 2
 
-     parser.bigOffset = parser.offset
 
-     parser.totalChunkSize = parser.buffer.length
 
-     parser.bufferCache.push(parser.buffer)
 
-     return
 
-   }
 
-   return convertBufferRange(parser, parser.offset, offsetEnd)
 
- }
 
- /**
 
-  * Parse a '-' redis error response
 
-  * @param parser
 
-  * @returns {Error}
 
-  */
 
- function parseError (parser) {
 
-   var string = parseSimpleString(parser)
 
-   if (string !== undefined) {
 
-     if (parser.optionReturnBuffers === true) {
 
-       string = string.toString()
 
-     }
 
-     return new ReplyError(string)
 
-   }
 
- }
 
- /**
 
-  * Parsing error handler, resets parser buffer
 
-  * @param parser
 
-  * @param error
 
-  */
 
- function handleError (parser, error) {
 
-   parser.buffer = null
 
-   parser.returnFatalError(error)
 
- }
 
- /**
 
-  * Parse a '*' redis array response
 
-  * @param parser
 
-  * @returns {*}
 
-  */
 
- function parseArray (parser) {
 
-   var length = parseLength(parser)
 
-   if (length === undefined) {
 
-     return
 
-   }
 
-   if (length === -1) {
 
-     return null
 
-   }
 
-   var responses = new Array(length)
 
-   return parseArrayElements(parser, responses, 0)
 
- }
 
- /**
 
-  * Push a partly parsed array to the stack
 
-  *
 
-  * @param parser
 
-  * @param elem
 
-  * @param i
 
-  * @returns {undefined}
 
-  */
 
- function pushArrayCache (parser, elem, pos) {
 
-   parser.arrayCache.push(elem)
 
-   parser.arrayPos.push(pos)
 
- }
 
- /**
 
-  * Parse chunked redis array response
 
-  * @param parser
 
-  * @returns {*}
 
-  */
 
- function parseArrayChunks (parser) {
 
-   var tmp = parser.arrayCache.pop()
 
-   var pos = parser.arrayPos.pop()
 
-   if (parser.arrayCache.length) {
 
-     var res = parseArrayChunks(parser)
 
-     if (!res) {
 
-       pushArrayCache(parser, tmp, pos)
 
-       return
 
-     }
 
-     tmp[pos++] = res
 
-   }
 
-   return parseArrayElements(parser, tmp, pos)
 
- }
 
- /**
 
-  * Parse redis array response elements
 
-  * @param parser
 
-  * @param responses
 
-  * @param i
 
-  * @returns {*}
 
-  */
 
- function parseArrayElements (parser, responses, i) {
 
-   var bufferLength = parser.buffer.length
 
-   while (i < responses.length) {
 
-     var offset = parser.offset
 
-     if (parser.offset >= bufferLength) {
 
-       pushArrayCache(parser, responses, i)
 
-       return
 
-     }
 
-     var response = parseType(parser, parser.buffer[parser.offset++])
 
-     if (response === undefined) {
 
-       if (!parser.arrayCache.length) {
 
-         parser.offset = offset
 
-       }
 
-       pushArrayCache(parser, responses, i)
 
-       return
 
-     }
 
-     responses[i] = response
 
-     i++
 
-   }
 
-   return responses
 
- }
 
- /**
 
-  * Called the appropriate parser for the specified type.
 
-  * @param parser
 
-  * @param type
 
-  * @returns {*}
 
-  */
 
- function parseType (parser, type) {
 
-   switch (type) {
 
-     case 36: // $
 
-       return parseBulkString(parser)
 
-     case 58: // :
 
-       return parseInteger(parser)
 
-     case 43: // +
 
-       return parseSimpleString(parser)
 
-     case 42: // *
 
-       return parseArray(parser)
 
-     case 45: // -
 
-       return parseError(parser)
 
-     default:
 
-       return handleError(parser, new ParserError(
 
-         'Protocol error, got ' + JSON.stringify(String.fromCharCode(type)) + ' as reply type byte',
 
-         JSON.stringify(parser.buffer),
 
-         parser.offset
 
-       ))
 
-   }
 
- }
 
- // All allowed options including their typeof value
 
- var optionTypes = {
 
-   returnError: 'function',
 
-   returnFatalError: 'function',
 
-   returnReply: 'function',
 
-   returnBuffers: 'boolean',
 
-   stringNumbers: 'boolean',
 
-   name: 'string'
 
- }
 
- /**
 
-  * Javascript Redis Parser
 
-  * @param options
 
-  * @constructor
 
-  */
 
- function JavascriptRedisParser (options) {
 
-   if (!(this instanceof JavascriptRedisParser)) {
 
-     return new JavascriptRedisParser(options)
 
-   }
 
-   if (!options || !options.returnError || !options.returnReply) {
 
-     throw new TypeError('Please provide all return functions while initiating the parser')
 
-   }
 
-   for (var key in options) {
 
-     // eslint-disable-next-line valid-typeof
 
-     if (optionTypes.hasOwnProperty(key) && typeof options[key] !== optionTypes[key]) {
 
-       throw new TypeError('The options argument contains the property "' + key + '" that is either unknown or of a wrong type')
 
-     }
 
-   }
 
-   if (options.name === 'hiredis') {
 
-     /* istanbul ignore next: hiredis is only supported for legacy usage */
 
-     try {
 
-       var Hiredis = require('./hiredis')
 
-       console.error(new TypeError('Using hiredis is discouraged. Please use the faster JS parser by removing the name option.').stack.replace('Error', 'Warning'))
 
-       return new Hiredis(options)
 
-     } catch (e) {
 
-       console.error(new TypeError('Hiredis is not installed. Please remove the `name` option. The (faster) JS parser is used instead.').stack.replace('Error', 'Warning'))
 
-     }
 
-   }
 
-   this.optionReturnBuffers = !!options.returnBuffers
 
-   this.optionStringNumbers = !!options.stringNumbers
 
-   this.returnError = options.returnError
 
-   this.returnFatalError = options.returnFatalError || options.returnError
 
-   this.returnReply = options.returnReply
 
-   this.name = 'javascript'
 
-   this.reset()
 
- }
 
- /**
 
-  * Reset the parser values to the initial state
 
-  *
 
-  * @returns {undefined}
 
-  */
 
- JavascriptRedisParser.prototype.reset = function () {
 
-   this.offset = 0
 
-   this.buffer = null
 
-   this.bigStrSize = 0
 
-   this.bigOffset = 0
 
-   this.totalChunkSize = 0
 
-   this.bufferCache = []
 
-   this.arrayCache = []
 
-   this.arrayPos = []
 
- }
 
- /**
 
-  * Set the returnBuffers option
 
-  *
 
-  * @param returnBuffers
 
-  * @returns {undefined}
 
-  */
 
- JavascriptRedisParser.prototype.setReturnBuffers = function (returnBuffers) {
 
-   if (typeof returnBuffers !== 'boolean') {
 
-     throw new TypeError('The returnBuffers argument has to be a boolean')
 
-   }
 
-   this.optionReturnBuffers = returnBuffers
 
- }
 
- /**
 
-  * Set the stringNumbers option
 
-  *
 
-  * @param stringNumbers
 
-  * @returns {undefined}
 
-  */
 
- JavascriptRedisParser.prototype.setStringNumbers = function (stringNumbers) {
 
-   if (typeof stringNumbers !== 'boolean') {
 
-     throw new TypeError('The stringNumbers argument has to be a boolean')
 
-   }
 
-   this.optionStringNumbers = stringNumbers
 
- }
 
- /**
 
-  * Decrease the bufferPool size over time
 
-  * @returns {undefined}
 
-  */
 
- function decreaseBufferPool () {
 
-   if (bufferPool.length > 50 * 1024) {
 
-     // Balance between increasing and decreasing the bufferPool
 
-     if (counter === 1 || notDecreased > counter * 2) {
 
-       // Decrease the bufferPool by 10% by removing the first 10% of the current pool
 
-       var sliceLength = Math.floor(bufferPool.length / 10)
 
-       if (bufferOffset <= sliceLength) {
 
-         bufferOffset = 0
 
-       } else {
 
-         bufferOffset -= sliceLength
 
-       }
 
-       bufferPool = bufferPool.slice(sliceLength, bufferPool.length)
 
-     } else {
 
-       notDecreased++
 
-       counter--
 
-     }
 
-   } else {
 
-     clearInterval(interval)
 
-     counter = 0
 
-     notDecreased = 0
 
-     interval = null
 
-   }
 
- }
 
- /**
 
-  * Check if the requested size fits in the current bufferPool.
 
-  * If it does not, reset and increase the bufferPool accordingly.
 
-  *
 
-  * @param length
 
-  * @returns {undefined}
 
-  */
 
- function resizeBuffer (length) {
 
-   if (bufferPool.length < length + bufferOffset) {
 
-     var multiplier = length > 1024 * 1024 * 75 ? 2 : 3
 
-     if (bufferOffset > 1024 * 1024 * 111) {
 
-       bufferOffset = 1024 * 1024 * 50
 
-     }
 
-     bufferPool = bufferAlloc(length * multiplier + bufferOffset)
 
-     bufferOffset = 0
 
-     counter++
 
-     if (interval === null) {
 
-       interval = setInterval(decreaseBufferPool, 50)
 
-     }
 
-   }
 
- }
 
- /**
 
-  * Concat a bulk string containing multiple chunks
 
-  *
 
-  * Notes:
 
-  * 1) The first chunk might contain the whole bulk string including the \r
 
-  * 2) We are only safe to fully add up elements that are neither the first nor any of the last two elements
 
-  *
 
-  * @param parser
 
-  * @returns {String}
 
-  */
 
- function concatBulkString (parser) {
 
-   var list = parser.bufferCache
 
-   var chunks = list.length
 
-   var offset = parser.bigStrSize - parser.totalChunkSize
 
-   parser.offset = offset
 
-   if (offset <= 2) {
 
-     if (chunks === 2) {
 
-       return list[0].toString('utf8', parser.bigOffset, list[0].length + offset - 2)
 
-     }
 
-     chunks--
 
-     offset = list[list.length - 2].length + offset
 
-   }
 
-   var res = decoder.write(list[0].slice(parser.bigOffset))
 
-   for (var i = 1; i < chunks - 1; i++) {
 
-     res += decoder.write(list[i])
 
-   }
 
-   res += decoder.end(list[i].slice(0, offset - 2))
 
-   return res
 
- }
 
- /**
 
-  * Concat the collected chunks from parser.bufferCache.
 
-  *
 
-  * Increases the bufferPool size beforehand if necessary.
 
-  *
 
-  * @param parser
 
-  * @returns {Buffer}
 
-  */
 
- function concatBulkBuffer (parser) {
 
-   var list = parser.bufferCache
 
-   var chunks = list.length
 
-   var length = parser.bigStrSize - parser.bigOffset - 2
 
-   var offset = parser.bigStrSize - parser.totalChunkSize
 
-   parser.offset = offset
 
-   if (offset <= 2) {
 
-     if (chunks === 2) {
 
-       return list[0].slice(parser.bigOffset, list[0].length + offset - 2)
 
-     }
 
-     chunks--
 
-     offset = list[list.length - 2].length + offset
 
-   }
 
-   resizeBuffer(length)
 
-   var start = bufferOffset
 
-   list[0].copy(bufferPool, start, parser.bigOffset, list[0].length)
 
-   bufferOffset += list[0].length - parser.bigOffset
 
-   for (var i = 1; i < chunks - 1; i++) {
 
-     list[i].copy(bufferPool, bufferOffset)
 
-     bufferOffset += list[i].length
 
-   }
 
-   list[i].copy(bufferPool, bufferOffset, 0, offset - 2)
 
-   bufferOffset += offset - 2
 
-   return bufferPool.slice(start, bufferOffset)
 
- }
 
- /**
 
-  * Parse the redis buffer
 
-  * @param buffer
 
-  * @returns {undefined}
 
-  */
 
- JavascriptRedisParser.prototype.execute = function execute (buffer) {
 
-   if (this.buffer === null) {
 
-     this.buffer = buffer
 
-     this.offset = 0
 
-   } else if (this.bigStrSize === 0) {
 
-     var oldLength = this.buffer.length
 
-     var remainingLength = oldLength - this.offset
 
-     var newBuffer = bufferAlloc(remainingLength + buffer.length)
 
-     this.buffer.copy(newBuffer, 0, this.offset, oldLength)
 
-     buffer.copy(newBuffer, remainingLength, 0, buffer.length)
 
-     this.buffer = newBuffer
 
-     this.offset = 0
 
-     if (this.arrayCache.length) {
 
-       var arr = parseArrayChunks(this)
 
-       if (!arr) {
 
-         return
 
-       }
 
-       this.returnReply(arr)
 
-     }
 
-   } else if (this.totalChunkSize + buffer.length >= this.bigStrSize) {
 
-     this.bufferCache.push(buffer)
 
-     var tmp = this.optionReturnBuffers ? concatBulkBuffer(this) : concatBulkString(this)
 
-     this.bigStrSize = 0
 
-     this.bufferCache = []
 
-     this.buffer = buffer
 
-     if (this.arrayCache.length) {
 
-       this.arrayCache[0][this.arrayPos[0]++] = tmp
 
-       tmp = parseArrayChunks(this)
 
-       if (!tmp) {
 
-         return
 
-       }
 
-     }
 
-     this.returnReply(tmp)
 
-   } else {
 
-     this.bufferCache.push(buffer)
 
-     this.totalChunkSize += buffer.length
 
-     return
 
-   }
 
-   while (this.offset < this.buffer.length) {
 
-     var offset = this.offset
 
-     var type = this.buffer[this.offset++]
 
-     var response = parseType(this, type)
 
-     if (response === undefined) {
 
-       if (!this.arrayCache.length) {
 
-         this.offset = offset
 
-       }
 
-       return
 
-     }
 
-     if (type === 45) {
 
-       this.returnError(response)
 
-     } else {
 
-       this.returnReply(response)
 
-     }
 
-   }
 
-   this.buffer = null
 
- }
 
- module.exports = JavascriptRedisParser
 
 
  |