nodulator
Version:
Complete NodeJS Framework for Restfull APIs
1,251 lines (1,145 loc) • 72.5 kB
JavaScript
/**
* Module dependencies.
*/
var BinaryParser = require('./binary_parser').BinaryParser
, Long = require('../goog/math/long').Long
, Timestamp = require('./timestamp').Timestamp
, ObjectID = require('./objectid').ObjectID
, Binary = require('./binary').Binary
//, debug = require('util').debug
//, inspect = require('util').inspect
//, inherits = require('util').inherits
, ieee754 = require('./float_parser')
, binaryutils = require('./binary_utils');
/**
* BSON constructor.
*/
function BSON () {};
// BSON MAX VALUES
BSON.BSON_INT32_MAX = 2147483648;
BSON.BSON_INT32_MIN = -2147483648;
// BSON DATA TYPES
BSON.BSON_DATA_NUMBER = 1;
BSON.BSON_DATA_STRING = 2;
BSON.BSON_DATA_OBJECT = 3;
BSON.BSON_DATA_ARRAY = 4;
BSON.BSON_DATA_BINARY = 5;
BSON.BSON_DATA_OID = 7;
BSON.BSON_DATA_BOOLEAN = 8;
BSON.BSON_DATA_DATE = 9;
BSON.BSON_DATA_NULL = 10;
BSON.BSON_DATA_REGEXP = 11;
BSON.BSON_DATA_CODE_W_SCOPE = 15;
BSON.BSON_DATA_INT = 16;
BSON.BSON_DATA_TIMESTAMP = 17;
BSON.BSON_DATA_LONG = 18;
// BSON BINARY DATA SUBTYPES
BSON.BSON_BINARY_SUBTYPE_DEFAULT = 0;
BSON.BSON_BINARY_SUBTYPE_FUNCTION = 1;
BSON.BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2;
BSON.BSON_BINARY_SUBTYPE_UUID = 3;
BSON.BSON_BINARY_SUBTYPE_MD5 = 4;
BSON.BSON_BINARY_SUBTYPE_USER_DEFINED = 128;
/**
* Serialize `data` as BSON.
*
* @param {TODO} data
* @param {Bool|null} checkKeys - TODO
* @return {TODO}
*/
// Does not do recursion, uses a stack to handle depth
// Experiment for performance
BSON.calculateObjectSize = function(object) {
var totalLength = (4 + 1);
var done = false;
var stack = [];
var currentObject = object;
var keys = null;
// Controls the flow
var finished = false;
while(!done) {
// Only get keys if we have a new object
keys = keys == null ? Object.keys(currentObject) : keys;
// Let's process all the elements
while(keys.length > 0) {
var name = keys.shift();
var value = currentObject[name];
if(value == null) {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (1);
} else if(Array.isArray(value)) {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (4 + 1) + 1;
stack.push({keys:keys, object:currentObject});
currentObject = value;
keys = Object.keys(value)
break;
} else if(typeof value == 'number' && value === parseInt(value, 10)) {
if(value >= BSON.BSON_INT32_MAX || value < BSON.BSON_INT32_MIN) {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (8 + 1);
} else {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (4 + 1);
}
} else if(typeof value == 'number' || toString.call(value) === '[object Number]') {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (8 + 1);
} else if(typeof value == 'boolean' || toString.call(value) === '[object Boolean]') {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (1 + 1);
} else if(value instanceof Date) {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (8 + 1);
} else if(typeof value == 'string') {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (Buffer.byteLength(value, 'utf8') + 4 + 1 + 1);
} else if(value instanceof ObjectID || (value.id && value.toHexString)) {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (12 + 1);
} else if(value instanceof Binary) {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (value.position + 1 + 4 + 1);
} else if(value instanceof Long) {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (8 + 1);
} else if(value instanceof Timestamp) {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (8 + 1);
} else if(value instanceof RegExp || toString.call(value) === '[object RegExp]') {
// Keep list of valid options
var options_array = [];
var str = value.toString();
var clean_regexp = str.match(/\/.*\//, '');
clean_regexp = clean_regexp[0].substring(1, clean_regexp[0].length - 1);
var options = str.substr(clean_regexp.length + 2);
// Extract all options that are legal and sort them alphabetically
for(var index = 0, len = options.length; index < len; ++index) {
var chr = options.charAt(index);
if('i' == chr || 'm' == chr || 'x' == chr) {
options_array.push(chr);
}
}
// Calculate the total length
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (Buffer.byteLength(clean_regexp) + 1 + options_array.length + 1 + 1);
} else if(value instanceof DBRef) {
var ordered_values = {
'$ref': value.namespace
, '$id' : value.oid
};
if(null != value.db) {
ordered_values['$db'] = value.db;
}
// Calculate the object
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (4 + 1 + 1);
stack.push({keys:keys, object:currentObject});
currentObject = ordered_values;
keys = Object.keys(ordered_values)
break;
} else if(value instanceof Code) {
// Calculate the length of the code string
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + 4 + (value.code.toString().length + 1) + 4;
totalLength += (4 + 1 + 1);
// Push the current object
stack.push({keys:keys, object:currentObject});
currentObject = value.scope;
keys = Object.keys(value.scope)
break;
} else if(typeof value == 'object') {
// Calculate the object
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (4 + 1 + 1);
// Otherwise handle keys
stack.push({keys:keys, object:currentObject});
currentObject = value;
keys = Object.keys(value)
break;
}
// Finished up the object
if(keys.length == 0) {
finished = true;
}
}
// If the stack is empty let's finish up, otherwise pop the previous object and
// continue
if(stack.length == 0) {
done = true;
} else if(finished || keys.length == 0){
currentObjectStored = stack.pop();
currentObject = currentObjectStored.object;
keys = currentObjectStored.keys;
finished = keys.length == 0 ? true: false;
}
}
return totalLength;
}
BSON.encodeObjectNoRec = function(buffer, object, checkKeys, startIndex) {
var index = startIndex == null ? 0 : startIndex;
var done = false;
var stack = [];
var currentObject = object;
var keys = null;
var size = 0;
var objectIndex = 0;
var totalNumberOfObjects = 0;
// Special index for Code objects
var codeStartIndex = 0;
// Signals if we are finished up
var finished = false;
// Current parsing object state
var currentObjectStored = {object: object, index: index, endIndex: 0, keys: Object.keys(object)};
// Adjust the index
index = index + 4;
// While meeting
while(!done) {
// While current object has keys
while(currentObjectStored.keys.length > 0) {
var name = currentObjectStored.keys.shift();
var value = currentObjectStored.object[name];
// If we got a key check for valid type
if(name != null && checkKeys == true && (name != '$db' && name != '$ref' && name != '$id')) {
BSON.checkKey(name);
}
if(value == null) {
// Write the type
buffer[index++] = BSON.BSON_DATA_NULL;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
} else if(typeof value == 'string') {
// Write the type
buffer[index++] = BSON.BSON_DATA_STRING;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Calculate size
size = Buffer.byteLength(value) + 1;
// Write the size of the string to buffer
buffer[index + 3] = (size >> 24) & 0xff;
buffer[index + 2] = (size >> 16) & 0xff;
buffer[index + 1] = (size >> 8) & 0xff;
buffer[index] = size & 0xff;
// Ajust the index
index = index + 4;
// Write the string
buffer.write(value, index, 'utf8');
// Update index
index = index + size - 1;
// Write zero
buffer[index++] = 0;
} else if(typeof value == 'number' && value === parseInt(value, 10)) {
// Write the type
buffer[index++] = value >= BSON.BSON_INT32_MAX || value < BSON.BSON_INT32_MIN ? BSON.BSON_DATA_LONG : BSON.BSON_DATA_INT;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
if(value >= BSON.BSON_INT32_MAX || value < BSON.BSON_INT32_MIN) {
// Write the number
var long = Long.fromNumber(value);
binaryutils.encodeIntInPlace(long.getLowBits(), buffer, index);
binaryutils.encodeIntInPlace(long.getHighBits(), buffer, index + 4);
index += 8;
} else {
// Write the int value to the buffer
buffer[index + 3] = (value >> 24) & 0xff;
buffer[index + 2] = (value >> 16) & 0xff;
buffer[index + 1] = (value >> 8) & 0xff;
buffer[index] = value & 0xff;
// Ajust the index
index = index + 4;
}
} else if(typeof value == 'number' || toString.call(value) === '[object Number]') {
// Write the type
buffer[index++] = BSON.BSON_DATA_NUMBER;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Write float
ieee754.writeIEEE754(buffer, value, index, 'little', 52, 8);
// Ajust index
index = index + 8;
} else if(typeof value == 'boolean' || toString.call(value) === '[object Boolean]') {
// Write the type
buffer[index++] = BSON.BSON_DATA_BOOLEAN;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
buffer[index++] = value ? 1 : 0;
} else if(value instanceof Date || toString.call(value) === '[object Date]') {
// Write the type
buffer[index++] = BSON.BSON_DATA_DATE;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Write the date
var dateInMilis = Long.fromNumber(value.getTime());
binaryutils.encodeIntInPlace(dateInMilis.getLowBits(), buffer, index);
binaryutils.encodeIntInPlace(dateInMilis.getHighBits(), buffer, index + 4);
// Ajust index
index = index + 8;
} else if(value instanceof RegExp || toString.call(value) === '[object RegExp]') {
// Write the type
buffer[index++] = BSON.BSON_DATA_REGEXP;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Keep list of valid options
var options_array = [];
var str = value.toString();
var clean_regexp = str.match(/\/.*\//, '');
clean_regexp = clean_regexp[0].substring(1, clean_regexp[0].length - 1);
// Get options from the regular expression
var options = str.substr(clean_regexp.length + 2);
// Write the regexp to the buffer
buffer.write(clean_regexp, index, 'utf8');
// Update the index
index = index + Buffer.byteLength(clean_regexp) + 1;
// Write ending cstring zero
buffer[index - 1] = 0;
// Extract all options that are legal and sort them alphabetically
for(var i = 0, len = options.length; i < len; ++i) {
var chr = options[i];
if('i' == chr || 'm' == chr || 'x' == chr) {
buffer[index++] = chr.charCodeAt(0)
}
}
// Write ending cstring zero
buffer[index++] = 0;
} else if(value instanceof Long) {
// Write the type
buffer[index++] = BSON.BSON_DATA_LONG;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Write the date
binaryutils.encodeIntInPlace(value.getLowBits(), buffer, index);
binaryutils.encodeIntInPlace(value.getHighBits(), buffer, index + 4);
// Ajust index
index = index + 8;
} else if(value instanceof Timestamp) {
// Write the type
buffer[index++] = BSON.BSON_DATA_TIMESTAMP;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Write the date
binaryutils.encodeIntInPlace(value.getLowBits(), buffer, index);
binaryutils.encodeIntInPlace(value.getHighBits(), buffer, index + 4);
// Ajust index
index = index + 8;
} else if(value instanceof Binary) {
// Write the type
buffer[index++] = BSON.BSON_DATA_BINARY;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Extract the buffer
var data = value.value(true);
// Calculate size
size = data.length;
// Write the size of the string to buffer
buffer[index + 3] = (size >> 24) & 0xff;
buffer[index + 2] = (size >> 16) & 0xff;
buffer[index + 1] = (size >> 8) & 0xff;
buffer[index] = size & 0xff;
// Update the index
index = index + 4;
// Write the subtype to the buffer
buffer[index++] = value.sub_type;
// Write the data to the object
data.copy(buffer, index, 0, data.length);
// Ajust index
index = index + data.length;
} else if(value instanceof ObjectID) {
// Write the type
buffer[index++] = BSON.BSON_DATA_OID;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Write objectid
buffer.write(value.id, index, 'binary');
// Ajust index
index = index + 12;
} else if(value instanceof DBRef) {
// Write the type
buffer[index++] = BSON.BSON_DATA_OBJECT;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
var ordered_values = {
'$ref': value.namespace
, '$id' : value.oid
};
if(null != value.db) {
ordered_values['$db'] = value.db;
}
// Push object on stack
stack.push(currentObjectStored);
// Set the new object
currentObjectStored = {object: ordered_values, index: index, endIndex: 0, keys: Object.keys(ordered_values)};
// Adjust index
index = index + 4;
} else if(value instanceof Code) {
// Calculate the scope size
var scopeSize = BSON.calculateObjectSize(value.scope);
// Write the type
buffer[index++] = BSON.BSON_DATA_CODE_W_SCOPE;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Convert value to string
var codeString = value.code.toString();
var codeStringLength = Buffer.byteLength(codeString);
// Calculate size
size = 4 + codeStringLength + 1 + 4 + scopeSize;
// Write the size of the string to buffer
buffer[index + 3] = (size >> 24) & 0xff;
buffer[index + 2] = (size >> 16) & 0xff;
buffer[index + 1] = (size >> 8) & 0xff;
buffer[index] = size & 0xff;
// Update index
index = index + 4;
// Calculate codestring length
size = codeStringLength + 1;
// Write the size of the string to buffer
buffer[index + 3] = (size >> 24) & 0xff;
buffer[index + 2] = (size >> 16) & 0xff;
buffer[index + 1] = (size >> 8) & 0xff;
buffer[index] = size & 0xff;
// Update index
index = index + 4;
// Write the string
buffer.write(codeString, index, 'utf8');
// Update index
index = index + codeStringLength;
// Add final 0 for cstring
buffer[index++] = 0;
// Push the scope object
stack.push(currentObjectStored);
// Set the new object
currentObjectStored = {object: value.scope, index: index, endIndex: 0, keys: Object.keys(value.scope)};
// Adjust index
index = index + 4;
} else if(typeof value == 'object') {
// Write the type of either Array or object
buffer[index++] = Array.isArray(value) ? BSON.BSON_DATA_ARRAY : BSON.BSON_DATA_OBJECT;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Push object on stack
stack.push(currentObjectStored);
// Set the new object
currentObjectStored = {object: value, index: index, endIndex: 0, keys: Object.keys(value)};
// Adjust index
index = index + 4;
}
if(currentObjectStored.keys.length == 0) {
// Save end index
currentObjectStored.endIndex = index;
// If we have a stack pop and finish up processing
if(stack.length > 0) {
// Write the current object size out
// Pack the size of the total buffer length
size = currentObjectStored.endIndex - currentObjectStored.index + 1;
// Write the size of the string to buffer
buffer[currentObjectStored.index + 3] = (size >> 24) & 0xff;
buffer[currentObjectStored.index + 2] = (size >> 16) & 0xff;
buffer[currentObjectStored.index + 1] = (size >> 8) & 0xff;
buffer[currentObjectStored.index] = size & 0xff;
// Adjust and set null last parameter
buffer[index++] = 0;
// Pop off the stored object
currentObjectStored = stack.pop();
}
}
}
if(stack.length > 0) {
// Write the current object size out
// Pack the size of the total buffer length
size = stack.length >= 1 ? (index - currentObjectStored.index + 1) :
currentObjectStored.endIndex - currentObjectStored.index + 16;
// Write the size of the string to buffer
buffer[currentObjectStored.index + 3] = (size >> 24) & 0xff;
buffer[currentObjectStored.index + 2] = (size >> 16) & 0xff;
buffer[currentObjectStored.index + 1] = (size >> 8) & 0xff;
buffer[currentObjectStored.index] = size & 0xff;
// Adjust and set null last parameter
buffer[index++] = 0;
// Pop off the stored object
currentObjectStored = stack.pop();
} else {
// Pack the size of the total buffer length
size = buffer.length;
// Write the size of the string to buffer
buffer[3] = (size >> 24) & 0xff;
buffer[2] = (size >> 16) & 0xff;
buffer[1] = (size >> 8) & 0xff;
buffer[0] = size & 0xff;
// Set last buffer field to 0
buffer[buffer.length - 1] = 0;
// return buffer;
done = true;
break;
}
}
// If we passed in an index
if(startIndex != null) return index;
// Otherwise buffer
return buffer;
}
// In place serialization with index to starting point of serialization
BSON.serializeWithBufferAndIndex = function serializeWithBufferAndIndex(object, checkKeys, buffer, startIndex) {
if(typeof object == 'object' || object instanceof Object) {
// Encode the object using single allocated buffer and no recursion
var index = startIndex == null ? 0 : startIndex;
var done = false;
var stack = [];
var currentObject = object;
var keys = null;
var size = 0;
var objectIndex = 0;
var totalNumberOfObjects = 0;
// Special index for Code objects
var codeStartIndex = 0;
// Signals if we are finished up
var finished = false;
// Current parsing object state
var currentObjectStored = {object: object, index: index, endIndex: 0, keys: Object.keys(object)};
// Adjust the index
index = index + 4;
// While meeting
while(!done) {
// While current object has keys
while(currentObjectStored.keys.length > 0) {
var name = currentObjectStored.keys.shift();
var value = currentObjectStored.object[name];
// If we got a key check for valid type
if(name != null && checkKeys == true && (name != '$db' && name != '$ref' && name != '$id')) {
BSON.checkKey(name);
}
if(value == null) {
// Write the type
buffer[index++] = BSON.BSON_DATA_NULL;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
} else if(typeof value == 'string') {
// Write the type
buffer[index++] = BSON.BSON_DATA_STRING;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Calculate size
size = Buffer.byteLength(value) + 1;
// Write the size of the string to buffer
buffer[index + 3] = (size >> 24) & 0xff;
buffer[index + 2] = (size >> 16) & 0xff;
buffer[index + 1] = (size >> 8) & 0xff;
buffer[index] = size & 0xff;
// Ajust the index
index = index + 4;
// Write the string
buffer.write(value, index, 'utf8');
// Update index
index = index + size - 1;
// Write zero
buffer[index++] = 0;
} else if(typeof value == 'number' && value === parseInt(value, 10)) {
// Write the type
buffer[index++] = value >= BSON.BSON_INT32_MAX || value < BSON.BSON_INT32_MIN ? BSON.BSON_DATA_LONG : BSON.BSON_DATA_INT;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
if(value >= BSON.BSON_INT32_MAX || value < BSON.BSON_INT32_MIN) {
// Write the number
var long = Long.fromNumber(value);
binaryutils.encodeIntInPlace(long.getLowBits(), buffer, index);
binaryutils.encodeIntInPlace(long.getHighBits(), buffer, index + 4);
index += 8;
} else {
// Write the int value to the buffer
buffer[index + 3] = (value >> 24) & 0xff;
buffer[index + 2] = (value >> 16) & 0xff;
buffer[index + 1] = (value >> 8) & 0xff;
buffer[index] = value & 0xff;
// Ajust the index
index = index + 4;
}
} else if(typeof value == 'number' || toString.call(value) === '[object Number]') {
// Write the type
buffer[index++] = BSON.BSON_DATA_NUMBER;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Write float
ieee754.writeIEEE754(buffer, value, index, 'little', 52, 8);
// Ajust index
index = index + 8;
} else if(typeof value == 'boolean' || toString.call(value) === '[object Boolean]') {
// Write the type
buffer[index++] = BSON.BSON_DATA_BOOLEAN;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
buffer[index++] = value ? 1 : 0;
} else if(value instanceof Date || toString.call(value) === '[object Date]') {
// Write the type
buffer[index++] = BSON.BSON_DATA_DATE;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Write the date
var dateInMilis = Long.fromNumber(value.getTime());
binaryutils.encodeIntInPlace(dateInMilis.getLowBits(), buffer, index);
binaryutils.encodeIntInPlace(dateInMilis.getHighBits(), buffer, index + 4);
// Ajust index
index = index + 8;
} else if(value instanceof RegExp || toString.call(value) === '[object RegExp]') {
// Write the type
buffer[index++] = BSON.BSON_DATA_REGEXP;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Keep list of valid options
var options_array = [];
var str = value.toString();
var clean_regexp = str.match(/\/.*\//, '');
clean_regexp = clean_regexp[0].substring(1, clean_regexp[0].length - 1);
// Get options from the regular expression
var options = str.substr(clean_regexp.length + 2);
// Write the regexp to the buffer
buffer.write(clean_regexp, index, 'utf8');
// Update the index
index = index + Buffer.byteLength(clean_regexp) + 1;
// Write ending cstring zero
buffer[index - 1] = 0;
// Extract all options that are legal and sort them alphabetically
for(var i = 0, len = options.length; i < len; ++i) {
var chr = options[i];
if('i' == chr || 'm' == chr || 'x' == chr) {
buffer[index++] = chr.charCodeAt(0)
}
}
// Write ending cstring zero
buffer[index++] = 0;
} else if(value instanceof Long) {
// Write the type
buffer[index++] = BSON.BSON_DATA_LONG;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Write the date
binaryutils.encodeIntInPlace(value.getLowBits(), buffer, index);
binaryutils.encodeIntInPlace(value.getHighBits(), buffer, index + 4);
// Ajust index
index = index + 8;
} else if(value instanceof Timestamp) {
// Write the type
buffer[index++] = BSON.BSON_DATA_TIMESTAMP;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Write the date
binaryutils.encodeIntInPlace(value.getLowBits(), buffer, index);
binaryutils.encodeIntInPlace(value.getHighBits(), buffer, index + 4);
// Ajust index
index = index + 8;
} else if(value instanceof Binary) {
// Write the type
buffer[index++] = BSON.BSON_DATA_BINARY;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Extract the buffer
var data = value.value(true);
// Calculate size
size = data.length;
// Write the size of the string to buffer
buffer[index + 3] = (size >> 24) & 0xff;
buffer[index + 2] = (size >> 16) & 0xff;
buffer[index + 1] = (size >> 8) & 0xff;
buffer[index] = size & 0xff;
// Update the index
index = index + 4;
// Write the subtype to the buffer
buffer[index++] = value.sub_type;
// Write the data to the object
data.copy(buffer, index, 0, data.length);
// Ajust index
index = index + data.length;
} else if(value instanceof ObjectID) {
// Write the type
buffer[index++] = BSON.BSON_DATA_OID;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Write objectid
buffer.write(value.id, index, 'binary');
// Ajust index
index = index + 12;
} else if(value instanceof DBRef) {
// Write the type
buffer[index++] = BSON.BSON_DATA_OBJECT;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
var ordered_values = {
'$ref': value.namespace
, '$id' : value.oid
};
if(null != value.db) {
ordered_values['$db'] = value.db;
}
// Push object on stack
stack.push(currentObjectStored);
// Set the new object
currentObjectStored = {object: ordered_values, index: index, endIndex: 0, keys: Object.keys(ordered_values)};
// Adjust index
index = index + 4;
} else if(value instanceof Code) {
// Calculate the scope size
var scopeSize = BSON.calculateObjectSize(value.scope);
// Write the type
buffer[index++] = BSON.BSON_DATA_CODE_W_SCOPE;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Convert value to string
var codeString = value.code.toString();
var codeStringLength = Buffer.byteLength(codeString);
// Calculate size
size = 4 + codeStringLength + 1 + 4 + scopeSize;
// Write the size of the string to buffer
buffer[index + 3] = (size >> 24) & 0xff;
buffer[index + 2] = (size >> 16) & 0xff;
buffer[index + 1] = (size >> 8) & 0xff;
buffer[index] = size & 0xff;
// Update index
index = index + 4;
// Calculate codestring length
size = codeStringLength + 1;
// Write the size of the string to buffer
buffer[index + 3] = (size >> 24) & 0xff;
buffer[index + 2] = (size >> 16) & 0xff;
buffer[index + 1] = (size >> 8) & 0xff;
buffer[index] = size & 0xff;
// Update index
index = index + 4;
// Write the string
buffer.write(codeString, index, 'utf8');
// Update index
index = index + codeStringLength;
// Add final 0 for cstring
buffer[index++] = 0;
// Push the scope object
stack.push(currentObjectStored);
// Set the new object
currentObjectStored = {object: value.scope, index: index, endIndex: 0, keys: Object.keys(value.scope)};
// Adjust index
index = index + 4;
} else if(typeof value == 'object') {
// Write the type of either Array or object
buffer[index++] = Array.isArray(value) ? BSON.BSON_DATA_ARRAY : BSON.BSON_DATA_OBJECT;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Push object on stack
stack.push(currentObjectStored);
// Set the new object
currentObjectStored = {object: value, index: index, endIndex: 0, keys: Object.keys(value)};
// Adjust index
index = index + 4;
}
if(currentObjectStored.keys.length == 0) {
// Save end index
currentObjectStored.endIndex = index;
// If we have a stack pop and finish up processing
if(stack.length > 0) {
// Write the current object size out
// Pack the size of the total buffer length
size = currentObjectStored.endIndex - currentObjectStored.index + 1;
// Write the size of the string to buffer
buffer[currentObjectStored.index + 3] = (size >> 24) & 0xff;
buffer[currentObjectStored.index + 2] = (size >> 16) & 0xff;
buffer[currentObjectStored.index + 1] = (size >> 8) & 0xff;
buffer[currentObjectStored.index] = size & 0xff;
// Adjust and set null last parameter
buffer[index++] = 0;
// Pop off the stored object
currentObjectStored = stack.pop();
}
}
}
if(stack.length > 0) {
// Write the current object size out
// Pack the size of the total buffer length
size = stack.length >= 1 ? (index - currentObjectStored.index + 1) :
currentObjectStored.endIndex - currentObjectStored.index + 16;
// Write the size of the string to buffer
buffer[currentObjectStored.index + 3] = (size >> 24) & 0xff;
buffer[currentObjectStored.index + 2] = (size >> 16) & 0xff;
buffer[currentObjectStored.index + 1] = (size >> 8) & 0xff;
buffer[currentObjectStored.index] = size & 0xff;
// Adjust and set null last parameter
buffer[index++] = 0;
// Pop off the stored object
currentObjectStored = stack.pop();
} else {
// Pack the size of the total buffer length
size = buffer.length;
// Write the size of the string to buffer
buffer[3] = (size >> 24) & 0xff;
buffer[2] = (size >> 16) & 0xff;
buffer[1] = (size >> 8) & 0xff;
buffer[0] = size & 0xff;
// Set last buffer field to 0
buffer[buffer.length - 1] = 0;
// return buffer;
done = true;
break;
}
}
// If we passed in an index
return index;
} else {
throw new Error("Not a valid object");
}
}
BSON.serialize = function serialize(object, checkKeys, asBuffer) {
if(object instanceof Object) {
//
// Calculate size of the object
//
var totalLength = (4 + 1);
var done = false;
var stack = [];
var currentObject = object;
var keys = null;
// Controls the flow
var finished = false;
while(!done) {
// Only get keys if we have a new object
keys = keys == null ? Object.keys(currentObject) : keys;
// Let's process all the elements
while(keys.length > 0) {
var name = keys.shift();
var value = currentObject[name];
if(value == null) {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (1);
} else if(Array.isArray(value)) {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (4 + 1) + 1;
stack.push({keys:keys, object:currentObject});
currentObject = value;
keys = Object.keys(value)
break;
} else if(typeof value == 'number' && value === parseInt(value, 10)) {
if(value >= BSON.BSON_INT32_MAX || value < BSON.BSON_INT32_MIN) {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (8 + 1);
} else {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (4 + 1);
}
} else if(typeof value == 'number' || toString.call(value) === '[object Number]') {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (8 + 1);
} else if(typeof value == 'boolean' || toString.call(value) === '[object Boolean]') {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (1 + 1);
} else if(value instanceof Date) {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (8 + 1);
} else if(typeof value == 'string') {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (Buffer.byteLength(value, 'utf8') + 4 + 1 + 1);
} else if(value instanceof ObjectID || (value.id && value.toHexString)) {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (12 + 1);
} else if(value instanceof Binary) {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (value.position + 1 + 4 + 1);
} else if(value instanceof Long) {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (8 + 1);
} else if(value instanceof Timestamp) {
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (8 + 1);
} else if(value instanceof RegExp || toString.call(value) === '[object RegExp]') {
// Keep list of valid options
var options_array = [];
var str = value.toString();
var clean_regexp = str.match(/\/.*\//, '');
clean_regexp = clean_regexp[0].substring(1, clean_regexp[0].length - 1);
var options = str.substr(clean_regexp.length + 2);
// Extract all options that are legal and sort them alphabetically
for(var index = 0, len = options.length; index < len; ++index) {
var chr = options.charAt(index);
if('i' == chr || 'm' == chr || 'x' == chr) {
options_array.push(chr);
}
}
// Calculate the total length
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (Buffer.byteLength(clean_regexp) + 1 + options_array.length + 1 + 1);
} else if(value instanceof DBRef) {
var ordered_values = {
'$ref': value.namespace
, '$id' : value.oid
};
if(null != value.db) {
ordered_values['$db'] = value.db;
}
// Calculate the object
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (4 + 1 + 1);
stack.push({keys:keys, object:currentObject});
currentObject = ordered_values;
keys = Object.keys(ordered_values)
break;
} else if(value instanceof Code) {
// Calculate the length of the code string
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + 4 + (value.code.toString().length + 1) + 4;
totalLength += (4 + 1 + 1);
// Push the current object
stack.push({keys:keys, object:currentObject});
currentObject = value.scope;
keys = Object.keys(value.scope)
break;
} else if(typeof value == 'object') {
// Calculate the object
totalLength += (name != null ? (Buffer.byteLength(name) + 1) : 0) + (4 + 1 + 1);
// Otherwise handle keys
stack.push({keys:keys, object:currentObject});
currentObject = value;
keys = Object.keys(value)
break;
}
// Finished up the object
if(keys.length == 0) {
finished = true;
}
}
// If the stack is empty let's finish up, otherwise pop the previous object and
// continue
if(stack.length == 0) {
done = true;
} else if(finished || keys.length == 0){
currentObjectStored = stack.pop();
currentObject = currentObjectStored.object;
keys = currentObjectStored.keys;
finished = keys.length == 0 ? true: false;
}
}
// Create a single buffer object
var buffer = new Buffer(totalLength);
//
// Serialize object
//
var index = 0;
var done = false;
var stack = [];
var currentObject = object;
var keys = null;
var size = 0;
var objectIndex = 0;
var totalNumberOfObjects = 0;
// Special index for Code objects
var codeStartIndex = 0;
// Signals if we are finished up
var finished = false;
// Current parsing object state
var currentObjectStored = {object: object, index: index, endIndex: 0, keys: Object.keys(object)};
// Adjust the index
index = index + 4;
// While meeting
while(!done) {
// While current object has keys
while(currentObjectStored.keys.length > 0) {
var name = currentObjectStored.keys.shift();
var value = currentObjectStored.object[name];
// If we got a key check for valid type
if(name != null && checkKeys == true && (name != '$db' && name != '$ref' && name != '$id')) {
BSON.checkKey(name);
}
if(value == null) {
// Write the type
buffer[index++] = BSON.BSON_DATA_NULL;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
} else if(typeof value == 'string') {
// Write the type
buffer[index++] = BSON.BSON_DATA_STRING;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Calculate size
size = Buffer.byteLength(value) + 1;
// Write the size of the string to buffer
buffer[index + 3] = (size >> 24) & 0xff;
buffer[index + 2] = (size >> 16) & 0xff;
buffer[index + 1] = (size >> 8) & 0xff;
buffer[index] = size & 0xff;
// Ajust the index
index = index + 4;
// Write the string
buffer.write(value, index, 'utf8');
// Update index
index = index + size - 1;
// Write zero
buffer[index++] = 0;
} else if(typeof value == 'number' && value === parseInt(value, 10)) {
// Write the type
buffer[index++] = value >= BSON.BSON_INT32_MAX || value < BSON.BSON_INT32_MIN ? BSON.BSON_DATA_LONG : BSON.BSON_DATA_INT;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
if(value >= BSON.BSON_INT32_MAX || value < BSON.BSON_INT32_MIN) {
// Write the number
var long = Long.fromNumber(value);
binaryutils.encodeIntInPlace(long.getLowBits(), buffer, index);
binaryutils.encodeIntInPlace(long.getHighBits(), buffer, index + 4);
index += 8;
} else {
// Write the int value to the buffer
buffer[index + 3] = (value >> 24) & 0xff;
buffer[index + 2] = (value >> 16) & 0xff;
buffer[index + 1] = (value >> 8) & 0xff;
buffer[index] = value & 0xff;
// Ajust the index
index = index + 4;
}
} else if(typeof value == 'number' || toString.call(value) === '[object Number]') {
// Write the type
buffer[index++] = BSON.BSON_DATA_NUMBER;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Write float
ieee754.writeIEEE754(buffer, value, index, 'little', 52, 8);
// Ajust index
index = index + 8;
} else if(typeof value == 'boolean' || toString.call(value) === '[object Boolean]') {
// Write the type
buffer[index++] = BSON.BSON_DATA_BOOLEAN;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
buffer[index++] = value ? 1 : 0;
} else if(value instanceof Date || toString.call(value) === '[object Date]') {
// Write the type
buffer[index++] = BSON.BSON_DATA_DATE;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Write the date
var dateInMilis = Long.fromNumber(value.getTime());
binaryutils.encodeIntInPlace(dateInMilis.getLowBits(), buffer, index);
binaryutils.encodeIntInPlace(dateInMilis.getHighBits(), buffer, index + 4);
// Ajust index
index = index + 8;
} else if(value instanceof RegExp || toString.call(value) === '[object RegExp]') {
// Write the type
buffer[index++] = BSON.BSON_DATA_REGEXP;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Keep list of valid options
var options_array = [];
var str = value.toString();
var clean_regexp = str.match(/\/.*\//, '');
clean_regexp = clean_regexp[0].substring(1, clean_regexp[0].length - 1);
// Get options from the regular expression
var options = str.substr(clean_regexp.length + 2);
// Write the regexp to the buffer
buffer.write(clean_regexp, index, 'utf8');
// Update the index
index = index + Buffer.byteLength(clean_regexp) + 1;
// Write ending cstring zero
buffer[index - 1] = 0;
// Extract all options that are legal and sort them alphabetically
for(var i = 0, len = options.length; i < len; ++i) {
var chr = options[i];
if('i' == chr || 'm' == chr || 'x' == chr) {
buffer[index++] = chr.charCodeAt(0)
}
}
// Write ending cstring zero
buffer[index++] = 0;
} else if(value instanceof Long) {
// Write the type
buffer[index++] = BSON.BSON_DATA_LONG;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Write the date
binaryutils.encodeIntInPlace(value.getLowBits(), buffer, index);
binaryutils.encodeIntInPlace(value.getHighBits(), buffer, index + 4);
// Ajust index
index = index + 8;
} else if(value instanceof Timestamp) {
// Write the type
buffer[index++] = BSON.BSON_DATA_TIMESTAMP;
// Write the name
if(name != null) {
index = index + buffer.write(name, index, 'utf8') + 1;
buffer[index - 1] = 0;
}
// Write the date
binaryutils.encodeIntInPlace(value.getLowBits(), buffer, index);
binaryutils.encodeIntInPlace(value.getHighBits(), buffer, index + 4);
// Ajust index
index = index + 8;
} else if(value instanceof Binary) {
// Write the type
buffer[index++] = BSON.BSON_DATA_BINARY;
// Write the name
if(name != null) {
index