This repository has been archived by the owner on Jan 31, 2018. It is now read-only.
forked from dfellis/multitransport-jsonrpc
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Arrayification of TCP transports. Removed the corruption checks becau…
…se the simplified format has no safeguards against it -- relying on TCP for integrity.
- Loading branch information
David Ellis
committed
Mar 18, 2013
1 parent
bb54184
commit 5731cdc
Showing
10 changed files
with
194 additions
and
165 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,11 @@ | ||
language: node_js | ||
node_js: | ||
- 0.8 | ||
- 0.9 | ||
- 0.10 | ||
|
||
notifications: | ||
email: | ||
recipients: | ||
- dispatch@uber.com | ||
on_success: change | ||
on_failure: change | ||
on_failure: change |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,120 @@ | ||
// Take a JSON object and transform it into a [Pascal string](http://en.wikipedia.org/wiki/String_%28computer_science%29#Length-prefixed) stored in a buffer. | ||
// The length prefix is big-endian because DEATH TO THE LITTLE ENDIAN LILLIPUTIANS! | ||
function formatMessage(obj) { | ||
var str = JSON.stringify(obj); | ||
return Buffer.byteLength(str) + '\0' + str + '\0'; | ||
var strlen = Buffer.byteLength(str); | ||
var buf = new Buffer(4 + strlen); | ||
buf.writeUInt32BE(strlen, 0); | ||
buf.write(str, 4, strlen, 'utf8'); | ||
return buf; | ||
} | ||
|
||
function containsCompleteMessage(str) { | ||
return str.split('\0').length > 2; | ||
// Since all messages start with a length prefix and the "current" message is the first in the buffers array, | ||
// we can determine the message length just by the first buffer in the array. This technically assumes that | ||
// a buffer is at least 4 bytes large, but that should be a safe assumption. | ||
function getMessageLen(buffers) { | ||
if(buffers[0] && buffers[0].length >= 4) { | ||
return buffers[0].readUInt32BE(0); | ||
} else { | ||
return 0; | ||
} | ||
} | ||
|
||
// Simple helper function that returns the minimum value from all values passed into it | ||
function min() { | ||
return Array.prototype.reduce.call(arguments, function(curr, val) { | ||
return (val < curr) ? val : curr; | ||
}, Infinity); | ||
} | ||
|
||
function parseBuffer(buffer, eventEmitter) { | ||
var nullSpot = buffer.indexOf('\0'); | ||
if (nullSpot !== -1) { | ||
var messageSizeStr = buffer.toString('utf8', 0, nullSpot); | ||
var messageSizePrefixBytes = Buffer.byteLength(messageSizeStr); | ||
var messageSize = Number(messageSizeStr); | ||
if (isNaN(messageSize) || messageSize < 0) { | ||
eventEmitter.emit('error', new Error('Invalid message format: Not a valid message length: "' + messageSizeStr + '"')); | ||
return parseBuffer(buffer.slice(nullSpot + 1), eventEmitter); | ||
// Given an array of buffers, the message length, and the eventEmitter object (in case of error) | ||
// try to parse the message and return the object it contains | ||
function parseBuffer(buffers, messageLen, eventEmitter) { | ||
|
||
// Allocate a new buffer the size of the message to copy the buffers into | ||
// and keep track of how many bytes have been copied and what buffer we're currently on | ||
var buf = new Buffer(messageLen); | ||
var bytesCopied = 0; | ||
var currBuffer = 0; | ||
|
||
// Continue copying until we've hit the message size | ||
while (bytesCopied < messageLen) { | ||
|
||
// bytesToCopy contains how much of the buffer we'll copy, either the | ||
// "whole thing" or "the rest of the message". | ||
var bytesToCopy = 0; | ||
|
||
// Since the first buffer contains the message length itself, it's special-cased | ||
// to skip those 4 bytes | ||
if (currBuffer === 0) { | ||
bytesToCopy = min(messageLen, buffers[0].length-4); | ||
buffers[0].copy(buf, bytesCopied, 4, bytesToCopy+4); | ||
} else { | ||
bytesToCopy = min(messageLen-bytesCopied, buffers[currBuffer].length); | ||
buffers[currBuffer].copy(buf, bytesCopied, 0, bytesToCopy); | ||
} | ||
|
||
// Increment the number of bytes copied by how many were copied | ||
bytesCopied += bytesToCopy; | ||
|
||
// If we're done, we have some cleanup to do; either appending the final chunk of the buffer | ||
// to the next buffer, or making sure that the array slice after the while loop is done | ||
// appropriately | ||
if (bytesCopied === messageLen) { | ||
if(currBuffer === 0) bytesToCopy += 4; | ||
if(buffers[currBuffer].length != bytesToCopy) { | ||
buffers[currBuffer] = buffers[currBuffer].slice(bytesToCopy); | ||
if (buffers[currBuffer].length < 4 && buffers[currBuffer+1]) { | ||
buffers[currBuffer+1] = Buffer.concat([buffers[currBuffer], buffers[currBuffer+1]]); | ||
} else { | ||
currBuffer--; // Counter the increment below | ||
} | ||
} | ||
} | ||
var totalMessageLength = 2 + messageSizePrefixBytes + messageSize; | ||
if (buffer[totalMessageLength - 1] === undefined) { | ||
// Return when we do not have the full contents of the message in the buffer | ||
return; | ||
} else if (buffer[totalMessageLength - 1] !== 0) { | ||
// There is no message delimiter where we expect one - we assume the buffer is | ||
// corrupt and try to recover by advancing to the next delimiter | ||
eventEmitter.emit('error', new Error('Invalid message format: No message delimiter as position ' + (totalMessageLength - 1) + ' for message "' + messageSizeStr + '"')); | ||
return parseBuffer(buffer.slice(nullSpot + 1), eventEmitter); | ||
|
||
// Move on to the next buffer in the array | ||
currBuffer++; | ||
} | ||
|
||
// Trim the buffers array to the next message | ||
buffers = buffers.slice(currBuffer); | ||
|
||
// Parse the buffer we created into a string and then a JSON object, or emit the parsing error | ||
var obj; | ||
try { | ||
obj = JSON.parse(buf.toString()); | ||
} catch (e) { | ||
eventEmitter.emit('error', e); | ||
} | ||
return [buffers, obj]; | ||
} | ||
|
||
|
||
function createDataHandler(self, callback) { | ||
var buffers = [], bufferLen = 0, messageLen = 0; | ||
return function dataHandler(data) { | ||
if(buffers[buffers.length-1] && buffers[buffers.length-1].length < 4) { | ||
buffers[buffers.length-1] = Buffer.concat([buffers[buffers.length-1], data], buffers[buffers.length-1].length + data.length); | ||
} else { | ||
buffers.push(data); | ||
} | ||
var message = buffer.toString('utf8', messageSizePrefixBytes + 1, messageSizePrefixBytes + 1 + messageSize); | ||
buffer = buffer.slice(totalMessageLength); | ||
var obj; | ||
try { | ||
obj = JSON.parse(message); | ||
} catch(e) { | ||
eventEmitter.emit('error', e); | ||
bufferLen += data.length; | ||
if(!messageLen) messageLen = getMessageLen(buffers); | ||
if(bufferLen - 4 >= messageLen) { | ||
var result, obj; | ||
while (messageLen && bufferLen - 4 >= messageLen && (result = parseBuffer(buffers, messageLen, self))) { | ||
buffers = result[0]; obj = result[1]; | ||
this.emit('message', obj); | ||
callback(obj); | ||
bufferLen = bufferLen - (messageLen + 4); | ||
messageLen = getMessageLen(buffers); | ||
} | ||
} | ||
return [buffer, obj]; | ||
} | ||
} | ||
|
||
// Export the public methods | ||
module.exports.formatMessage = formatMessage; | ||
module.exports.getMessageLen = getMessageLen; | ||
module.exports.parseBuffer = parseBuffer; | ||
module.exports.containsCompleteMessage = containsCompleteMessage; | ||
module.exports.createDataHandler = createDataHandler; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.