You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
5.8 KiB
172 lines
5.8 KiB
'use strict'; |
|
|
|
function JavascriptReplyParser(options) { |
|
this.name = 'javascript'; |
|
this.buffer = new Buffer(0); |
|
this.offset = 0; |
|
this.bigStrSize = 0; |
|
this.chunksSize = 0; |
|
this.buffers = []; |
|
this.type = 0; |
|
this.protocolError = false; |
|
this.offsetCache = 0; |
|
// If returnBuffers is active, all return values are returned as buffers besides numbers and errors |
|
if (options.return_buffers) { |
|
this.handleReply = function (start, end) { |
|
return this.buffer.slice(start, end); |
|
}; |
|
} else { |
|
this.handleReply = function (start, end) { |
|
return this.buffer.toString('utf-8', start, end); |
|
}; |
|
} |
|
// 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 |
|
if (options.string_numbers) { |
|
this.handleNumbers = function (start, end) { |
|
return this.buffer.toString('ascii', start, end); |
|
}; |
|
} else { |
|
this.handleNumbers = function (start, end) { |
|
return +this.buffer.toString('ascii', start, end); |
|
}; |
|
} |
|
} |
|
|
|
JavascriptReplyParser.prototype.parseResult = function (type) { |
|
var start = 0, |
|
end = 0, |
|
packetHeader = 0, |
|
reply; |
|
|
|
if (type === 36) { // $ |
|
packetHeader = this.parseHeader(); |
|
// Packets with a size of -1 are considered null |
|
if (packetHeader === -1) { |
|
return null; |
|
} |
|
end = this.offset + packetHeader; |
|
start = this.offset; |
|
if (end + 2 > this.buffer.length) { |
|
this.buffers.push(this.offsetCache === 0 ? this.buffer : this.buffer.slice(this.offsetCache)); |
|
this.chunksSize = this.buffers[0].length; |
|
// Include the packetHeader delimiter |
|
this.bigStrSize = packetHeader + 2; |
|
throw new Error('Wait for more data.'); |
|
} |
|
// Set the offset to after the delimiter |
|
this.offset = end + 2; |
|
return this.handleReply(start, end); |
|
} else if (type === 58) { // : |
|
// Up to the delimiter |
|
end = this.packetEndOffset(); |
|
start = this.offset; |
|
// Include the delimiter |
|
this.offset = end + 2; |
|
// Return the coerced numeric value |
|
return this.handleNumbers(start, end); |
|
} else if (type === 43) { // + |
|
end = this.packetEndOffset(); |
|
start = this.offset; |
|
this.offset = end + 2; |
|
return this.handleReply(start, end); |
|
} else if (type === 42) { // * |
|
packetHeader = this.parseHeader(); |
|
if (packetHeader === -1) { |
|
return null; |
|
} |
|
reply = []; |
|
for (var i = 0; i < packetHeader; i++) { |
|
if (this.offset >= this.buffer.length) { |
|
throw new Error('Wait for more data.'); |
|
} |
|
reply.push(this.parseResult(this.buffer[this.offset++])); |
|
} |
|
return reply; |
|
} else if (type === 45) { // - |
|
end = this.packetEndOffset(); |
|
start = this.offset; |
|
this.offset = end + 2; |
|
return new Error(this.buffer.toString('utf-8', start, end)); |
|
} |
|
}; |
|
|
|
JavascriptReplyParser.prototype.execute = function (buffer) { |
|
if (this.chunksSize !== 0) { |
|
if (this.bigStrSize > this.chunksSize + buffer.length) { |
|
this.buffers.push(buffer); |
|
this.chunksSize += buffer.length; |
|
return; |
|
} |
|
this.buffers.push(buffer); |
|
this.buffer = Buffer.concat(this.buffers, this.chunksSize + buffer.length); |
|
this.buffers = []; |
|
this.bigStrSize = 0; |
|
this.chunksSize = 0; |
|
} else if (this.offset >= this.buffer.length) { |
|
this.buffer = buffer; |
|
} else { |
|
this.buffer = Buffer.concat([this.buffer.slice(this.offset), buffer]); |
|
} |
|
this.offset = 0; |
|
this.run(); |
|
}; |
|
|
|
JavascriptReplyParser.prototype.tryParsing = function () { |
|
try { |
|
return this.parseResult(this.type); |
|
} catch (err) { |
|
// Catch the error (not enough data), rewind if it's an array, |
|
// and wait for the next packet to appear |
|
this.offset = this.offsetCache; |
|
// Indicate that there's no protocol error by resetting the type too |
|
this.type = undefined; |
|
} |
|
}; |
|
|
|
JavascriptReplyParser.prototype.run = function () { |
|
// Set a rewind point. If a failure occurs, wait for the next execute()/append() and try again |
|
this.offsetCache = this.offset; |
|
this.type = this.buffer[this.offset++]; |
|
var reply = this.tryParsing(); |
|
|
|
while (reply !== undefined) { |
|
if (this.type === 45) { // Errors - |
|
this.returnError(reply); |
|
} else { |
|
this.returnReply(reply); // Strings + // Integers : // Bulk strings $ // Arrays * |
|
} |
|
this.offsetCache = this.offset; |
|
this.type = this.buffer[this.offset++]; |
|
reply = this.tryParsing(); |
|
} |
|
if (this.type !== undefined) { |
|
// Reset the buffer so the parser can handle following commands properly |
|
this.buffer = new Buffer(0); |
|
this.returnFatalError(new Error('Protocol error, got ' + JSON.stringify(String.fromCharCode(this.type)) + ' as reply type byte')); |
|
} |
|
}; |
|
|
|
JavascriptReplyParser.prototype.parseHeader = function () { |
|
var end = this.packetEndOffset(), |
|
value = this.buffer.toString('ascii', this.offset, end) | 0; |
|
|
|
this.offset = end + 2; |
|
return value; |
|
}; |
|
|
|
JavascriptReplyParser.prototype.packetEndOffset = function () { |
|
var offset = this.offset, |
|
len = this.buffer.length - 1; |
|
|
|
while (this.buffer[offset] !== 0x0d && this.buffer[offset + 1] !== 0x0a) { |
|
offset++; |
|
|
|
if (offset >= len) { |
|
throw new Error('Did not see LF after NL reading multi bulk count (' + offset + ' => ' + this.buffer.length + ', ' + this.offset + ')'); |
|
} |
|
} |
|
return offset; |
|
}; |
|
|
|
module.exports = JavascriptReplyParser;
|
|
|