UNPKG

nodulator

Version:

Complete NodeJS Framework for Restfull APIs

1,251 lines (1,145 loc) 72.5 kB
/** * 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