f19e44fbdcb729cbc358c48a3232732723ed7e08
[aai/esr-gui.git] /
1 /// reduced to ~ 410 LOCs (parser only 300 vs. 1400+) with (some, needed) BSON classes "inlined".
2 /// Compare ~ 4,300 (22KB vs. 157KB) in browser build at: https://github.com/mongodb/js-bson/blob/master/browser_build/bson.js
3
4 module.exports.calculateObjectSize = calculateObjectSize;
5
6 function calculateObjectSize(object) {
7     var totalLength = (4 + 1);      /// handles the obj.length prefix + terminating '0' ?!
8     for(var key in object) {        /// looks like it handles arrays under the same for...in loop!?
9       totalLength += calculateElement(key, object[key])
10     }
11     return totalLength;
12 }
13
14 function calculateElement(name, value) {
15     var len = 1;                                /// always starting with 1 for the data type byte!
16     if (name) len += Buffer.byteLength(name, 'utf8') + 1;   /// cstring: name + '0' termination
17
18     if (value === undefined || value === null) return len;  /// just the type byte plus name cstring
19     switch( value.constructor ) {      /// removed all checks 'isBuffer' if Node.js Buffer class is present!?
20
21         case ObjectID:          /// we want these sorted from most common case to least common/deprecated;
22             return len + 12;
23         case String:
24             return len + 4 + Buffer.byteLength(value, 'utf8') +1; ///
25         case Number:
26             if (Math.floor(value) === value) {  /// case: integer; pos.# more common, '&&' stops if 1st fails!
27                 if ( value <= 2147483647 && value >= -2147483647 ) // 32 bit
28                     return len + 4;
29                 else return len + 8;    /// covers Long-ish JS integers as Longs!
30             } else return len + 8;      /// 8+1 --- covers Double & std. float
31         case Boolean:
32             return len + 1;
33
34         case Array:
35         case Object:
36             return len + calculateObjectSize(value);
37
38         case Buffer:   ///  replaces the entire Binary class!
39             return len + 4 + value.length + 1;
40
41         case Regex:  /// these are handled as strings by serializeFast() later, hence 'gim' opts = 3 + 1 chars
42             return len + Buffer.byteLength(value.source, 'utf8') + 1
43                 + (value.global ? 1 : 0) + (value.ignoreCase ? 1 : 0) + (value.multiline ? 1 : 0) +1;
44         case Date:
45         case Long:
46         case Timestamp:
47         case Double:
48             return len + 8;
49
50         case MinKey:
51         case MaxKey:
52             return len;     /// these two return the type byte and name cstring only!
53     }
54     return 0;
55 }
56
57 module.exports.serializeFast = serializeFast;
58 module.exports.serialize = function(object, checkKeys, asBuffer, serializeFunctions, index) {
59   var buffer = new Buffer(calculateObjectSize(object));
60   return serializeFast(object, checkKeys, buffer, 0);
61 }
62
63 function serializeFast(object, checkKeys, buffer, i) {   /// set checkKeys = false in query(..., options object to save performance IFF you're certain your keys are safe/system-set!
64     var size = buffer.length;
65     buffer[i++] = size & 0xff; buffer[i++] = (size >> 8) & 0xff;   /// these get overwritten later!
66     buffer[i++] = (size >> 16) & 0xff; buffer[i++] = (size >> 24) & 0xff;
67
68     if (object.constructor === Array) {     /// any need to checkKeys here?!? since we're doing for rather than for...in, should be safe from extra (non-numeric) keys added to the array?!
69         for(var j = 0; j < object.length; j++) {
70             i = packElement(j.toString(), object[j], checkKeys, buffer, i);
71         }
72     } else {   /// checkKeys is needed if any suspicion of end-user key tampering/"injection" (a la SQL)
73         for(var key in object) {    /// mostly there should never be direct access to them!?
74             if (checkKeys && (key.indexOf('\x00') >= 0 || key === '$where') ) {  /// = "no script"?!; could add back key.indexOf('$') or maybe check for 'eval'?!
75 /// took out: || key.indexOf('.') >= 0...  Don't we allow dot notation queries?!
76                 console.log('checkKeys error: ');
77                 return new Error('Illegal object key!');
78             }
79             i = packElement(key, object[key], checkKeys, buffer, i);  /// checkKeys pass needed for recursion!
80         }
81     }
82     buffer[i++] = 0;   /// write terminating zero; !we do NOT -1 the index increase here as original does!
83     return i;
84 }
85
86 function packElement(name, value, checkKeys, buffer, i) {    /// serializeFunctions removed! checkKeys needed for Array & Object cases pass through (calling serializeFast recursively!)
87     if (value === undefined || value === null){
88         buffer[i++] = 10;                                       /// = BSON.BSON_DATA_NULL;
89         i += buffer.write(name, i, 'utf8'); buffer[i++] = 0;    /// buffer.write(...) returns bytesWritten!
90         return i;
91     }
92     switch(value.constructor) {
93
94     case ObjectID:
95         buffer[i++] = 7;   /// = BSON.BSON_DATA_OID;
96         i += buffer.write(name, i, 'utf8');     buffer[i++] = 0;
97 ///     i += buffer.write(value.id, i, 'binary');  ///  OLD: writes a String to a Buffer; 'binary' deprecated!!
98         value.id.copy(buffer, i);  /// NEW ObjectID version has this.id = Buffer at the ready!
99         return i += 12;
100
101     case String:
102         buffer[i++] = 2;    ///  = BSON.BSON_DATA_STRING;
103         i += buffer.write(name, i, 'utf8');     buffer[i++] = 0;
104
105         var size = Buffer.byteLength(value) + 1;  /// includes the terminating '0'!?
106         buffer[i++] = size & 0xff; buffer[i++] = (size >> 8) & 0xff;
107         buffer[i++] = (size >> 16) & 0xff; buffer[i++] = (size >> 24) & 0xff;
108
109         i += buffer.write(value, i, 'utf8');    buffer[i++] = 0;
110         return i;
111
112     case Number:
113         if ( ~~(value) === value) {     /// double-Tilde is equiv. to Math.floor(value)
114             if ( value <= 2147483647 && value >= -2147483647){ /// = BSON.BSON_INT32_MAX / MIN asf.
115                 buffer[i++] = 16;   /// = BSON.BSON_DATA_INT;
116                 i += buffer.write(name, i, 'utf8'); buffer[i++] = 0;
117                 buffer[i++] = value & 0xff; buffer[i++] = (value >> 8) & 0xff;
118                 buffer[i++] = (value >> 16) & 0xff; buffer[i++] = (value >> 24) & 0xff;
119
120 // Else large-ish JS int!? to Long!?
121             } else {  /// if (value <= BSON.JS_INT_MAX && value >= BSON.JS_INT_MIN){ /// 9007199254740992 asf.
122                 buffer[i++] = 18;   /// = BSON.BSON_DATA_LONG;
123                 i += buffer.write(name, i, 'utf8'); buffer[i++] = 0;
124                 var lowBits = ( value % 4294967296 ) | 0, highBits = ( value / 4294967296 ) | 0;
125
126                 buffer[i++] = lowBits & 0xff;           buffer[i++] = (lowBits >> 8) & 0xff;
127                 buffer[i++] = (lowBits >> 16) & 0xff;   buffer[i++] = (lowBits >> 24) & 0xff;
128                 buffer[i++] = highBits & 0xff;          buffer[i++] = (highBits >> 8) & 0xff;
129                 buffer[i++] = (highBits >> 16) & 0xff;  buffer[i++] = (highBits >> 24) & 0xff;
130             }
131         } else {    /// we have a float / Double
132             buffer[i++] = 1;    /// = BSON.BSON_DATA_NUMBER;
133             i += buffer.write(name, i, 'utf8'); buffer[i++] = 0;
134 /// OLD:    writeIEEE754(buffer, value, i, 'little', 52, 8);
135             buffer.writeDoubleLE(value, i);     i += 8;
136         }
137         return i;
138
139     case Boolean:
140         buffer[i++] = 8;    /// = BSON.BSON_DATA_BOOLEAN;
141         i += buffer.write(name, i, 'utf8'); buffer[i++] = 0;
142         buffer[i++] = value ? 1 : 0;
143         return i;
144
145     case Array:
146     case Object:
147         buffer[i++] = value.constructor === Array ? 4 : 3; /// = BSON.BSON_DATA_ARRAY / _OBJECT;
148         i += buffer.write(name, i, 'utf8'); buffer[i++] = 0;
149
150             var endIndex = serializeFast(value, checkKeys, buffer, i); /// + 4); no longer needed b/c serializeFast writes a temp 4 bytes for length
151         var size = endIndex - i;
152         buffer[i++] = size & 0xff;          buffer[i++] = (size >> 8) & 0xff;
153         buffer[i++] = (size >> 16) & 0xff;  buffer[i++] = (size >> 24) & 0xff;
154         return endIndex;
155
156     /// case Binary:        /// is basically identical unless special/deprecated options!
157     case Buffer:            /// solves ALL of our Binary needs without the BSON.Binary class!?
158         buffer[i++] = 5;    /// = BSON.BSON_DATA_BINARY;
159         i += buffer.write(name, i, 'utf8'); buffer[i++] = 0;
160         var size = value.length;
161         buffer[i++] = size & 0xff;          buffer[i++] = (size >> 8) & 0xff;
162         buffer[i++] = (size >> 16) & 0xff;  buffer[i++] = (size >> 24) & 0xff;
163
164         buffer[i++] = 0;        /// write BSON.BSON_BINARY_SUBTYPE_DEFAULT;
165         value.copy(buffer, i);  ///, 0, size); << defaults to sourceStart=0, sourceEnd=sourceBuffer.length);
166         i += size;
167         return i;
168
169     case RegExp:
170         buffer[i++] = 11;   /// = BSON.BSON_DATA_REGEXP;
171         i += buffer.write(name, i, 'utf8');         buffer[i++] = 0;
172         i += buffer.write(value.source, i, 'utf8'); buffer[i++] = 0x00;
173
174         if (value.global) buffer[i++] = 0x73;        // s = 'g' for JS Regex!
175         if (value.ignoreCase) buffer[i++] = 0x69;    // i
176         if (value.multiline) buffer[i++] = 0x6d;     // m
177         buffer[i++] = 0x00;
178         return i;
179
180     case Date:
181         buffer[i++] = 9;    /// = BSON.BSON_DATA_DATE;
182         i += buffer.write(name, i, 'utf8'); buffer[i++] = 0;
183         var millis = value.getTime();
184         var lowBits = ( millis % 4294967296 ) | 0, highBits = ( millis / 4294967296 ) | 0;
185
186         buffer[i++] = lowBits & 0xff;           buffer[i++] = (lowBits >> 8) & 0xff;
187         buffer[i++] = (lowBits >> 16) & 0xff;   buffer[i++] = (lowBits >> 24) & 0xff;
188         buffer[i++] = highBits & 0xff;          buffer[i++] = (highBits >> 8) & 0xff;
189         buffer[i++] = (highBits >> 16) & 0xff;  buffer[i++] = (highBits >> 24) & 0xff;
190         return i;
191
192     case Long:
193     case Timestamp:
194         buffer[i++] = value.constructor === Long ? 18 : 17; /// = BSON.BSON_DATA_LONG / _TIMESTAMP
195         i += buffer.write(name, i, 'utf8'); buffer[i++] = 0;
196         var lowBits = value.getLowBits(), highBits = value.getHighBits();
197
198         buffer[i++] = lowBits & 0xff;           buffer[i++] = (lowBits >> 8) & 0xff;
199         buffer[i++] = (lowBits >> 16) & 0xff;   buffer[i++] = (lowBits >> 24) & 0xff;
200         buffer[i++] = highBits & 0xff;          buffer[i++] = (highBits >> 8) & 0xff;
201         buffer[i++] = (highBits >> 16) & 0xff;  buffer[i++] = (highBits >> 24) & 0xff;
202         return i;
203
204     case Double:
205         buffer[i++] = 1;    /// = BSON.BSON_DATA_NUMBER;
206         i += buffer.write(name, i, 'utf8'); buffer[i++] = 0;
207 /// OLD: writeIEEE754(buffer, value, i, 'little', 52, 8);    i += 8;
208         buffer.writeDoubleLE(value, i);     i += 8;
209         return i
210
211     case MinKey:    /// = BSON.BSON_DATA_MINKEY;
212         buffer[i++] = 127; i += buffer.write(name, i, 'utf8'); buffer[i++] = 0;
213         return i;
214     case MaxKey:    /// = BSON.BSON_DATA_MAXKEY;
215         buffer[i++] = 255; i += buffer.write(name, i, 'utf8'); buffer[i++] = 0;
216         return i;
217
218     } /// end of switch
219     return i;   /// ?! If no value to serialize
220 }
221
222
223 module.exports.deserializeFast = deserializeFast;
224
225 function deserializeFast(buffer, i, isArray){   //// , options, isArray) {       //// no more options!
226     if (buffer.length < 5) return new Error('Corrupt bson message < 5 bytes long'); /// from 'throw'
227     var elementType, tempindex = 0, name;
228     var string, low, high;              /// = lowBits / highBits
229                                         /// using 'i' as the index to keep the lines shorter:
230     i || ( i = 0 );  /// for parseResponse it's 0; set to running index in deserialize(object/array) recursion
231     var object = isArray ? [] : {};         /// needed for type ARRAY recursion later!
232     var size = buffer[i++] | buffer[i++] << 8 | buffer[i++] << 16 | buffer[i++] << 24;
233     if(size < 5 || size > buffer.length) return new Error('Corrupt BSON message');
234 /// 'size' var was not used by anything after this, so we can reuse it
235
236     while(true) {                           // While we have more left data left keep parsing
237         elementType = buffer[i++];          // Read the type
238         if (elementType === 0) break;       // If we get a zero it's the last byte, exit
239
240         tempindex = i;  /// inlined readCStyleString & removed extra i<buffer.length check slowing EACH loop!
241         while( buffer[tempindex] !== 0x00 ) tempindex++;  /// read ahead w/out changing main 'i' index
242         if (tempindex >= buffer.length) return new Error('Corrupt BSON document: illegal CString')
243         name = buffer.toString('utf8', i, tempindex);
244         i = tempindex + 1;               /// Update index position to after the string + '0' termination
245
246         switch(elementType) {
247
248         case 7:     /// = BSON.BSON_DATA_OID:
249             var buf = new Buffer(12);
250             buffer.copy(buf, 0, i, i += 12 );   /// copy 12 bytes from the current 'i' offset into fresh Buffer
251             object[name] = new ObjectID(buf);   ///... & attach to the new ObjectID instance
252             break;
253
254         case 2:     /// = BSON.BSON_DATA_STRING:
255             size = buffer[i++] | buffer[i++] <<8 | buffer[i++] <<16 | buffer[i++] <<24;
256             object[name] = buffer.toString('utf8', i, i += size -1 );
257             i++;        break;          /// need to get the '0' index "tick-forward" back!
258
259         case 16:    /// = BSON.BSON_DATA_INT:        // Decode the 32bit value
260             object[name] = buffer[i++] | buffer[i++] << 8 | buffer[i++] << 16 | buffer[i++] << 24;    break;
261
262         case 1:     /// = BSON.BSON_DATA_NUMBER:     // Decode the double value
263             object[name] = buffer.readDoubleLE(i);   /// slightly faster depending on dec.points; a LOT cleaner
264             /// OLD: object[name] = readIEEE754(buffer, i, 'little', 52, 8);
265             i += 8;     break;
266
267         case 8:     /// = BSON.BSON_DATA_BOOLEAN:
268             object[name] = buffer[i++] == 1;    break;
269
270         case 6:     /// = BSON.BSON_DATA_UNDEFINED:     /// deprecated
271         case 10:    /// = BSON.BSON_DATA_NULL:
272             object[name] = null;     break;
273
274         case 4:     /// = BSON.BSON_DATA_ARRAY
275             size = buffer[i] | buffer[i+1] <<8 | buffer[i+2] <<16 | buffer[i+3] <<24;  /// NO 'i' increment since the size bytes are reread during the recursion!
276             object[name] = deserializeFast(buffer, i, true );  /// pass current index & set isArray = true
277             i += size;      break;
278         case 3:     /// = BSON.BSON_DATA_OBJECT:
279             size = buffer[i] | buffer[i+1] <<8 | buffer[i+2] <<16 | buffer[i+3] <<24;
280             object[name] = deserializeFast(buffer, i, false );          /// isArray = false => Object
281             i += size;      break;
282
283         case 5:     /// = BSON.BSON_DATA_BINARY:             // Decode the size of the binary blob
284             size = buffer[i++] | buffer[i++] << 8 | buffer[i++] << 16 | buffer[i++] << 24;
285             buffer[i++];             /// Skip, as we assume always default subtype, i.e. 0!
286             object[name] = buffer.slice(i, i += size);  /// creates a new Buffer "slice" view of the same memory!
287             break;
288
289         case 9:     /// = BSON.BSON_DATA_DATE:      /// SEE notes below on the Date type vs. other options...
290             low  = buffer[i++] | buffer[i++] << 8 | buffer[i++] << 16 | buffer[i++] << 24;
291             high = buffer[i++] | buffer[i++] << 8 | buffer[i++] << 16 | buffer[i++] << 24;
292             object[name] = new Date( high * 4294967296 + (low < 0 ? low + 4294967296 : low) );  break;
293
294         case 18:    /// = BSON.BSON_DATA_LONG:  /// usage should be somewhat rare beyond parseResponse() -> cursorId, where it is handled inline, NOT as part of deserializeFast(returnedObjects); get lowBits, highBits:
295             low  = buffer[i++] | buffer[i++] << 8 | buffer[i++] << 16 | buffer[i++] << 24;
296             high = buffer[i++] | buffer[i++] << 8 | buffer[i++] << 16 | buffer[i++] << 24;
297
298             size = high * 4294967296 + (low < 0 ? low + 4294967296 : low);      /// from long.toNumber()
299             if (size < JS_INT_MAX && size > JS_INT_MIN) object[name] = size;    /// positive # more likely!
300             else object[name] = new Long(low, high);    break;
301
302         case 127:   /// = BSON.BSON_DATA_MIN_KEY:   /// do we EVER actually get these BACK from MongoDB server?!
303             object[name] = new MinKey();     break;
304         case 255:   /// = BSON.BSON_DATA_MAX_KEY:
305             object[name] = new MaxKey();     break;
306
307         case 17:    /// = BSON.BSON_DATA_TIMESTAMP:   /// somewhat obscure internal BSON type; MongoDB uses it for (pseudo) high-res time timestamp (past millisecs precision is just a counter!) in the Oplog ts: field, etc.
308             low  = buffer[i++] | buffer[i++] << 8 | buffer[i++] << 16 | buffer[i++] << 24;
309             high = buffer[i++] | buffer[i++] << 8 | buffer[i++] << 16 | buffer[i++] << 24;
310             object[name] = new Timestamp(low, high);     break;
311
312 ///        case 11:    /// = RegExp is skipped; we should NEVER be getting any from the MongoDB server!?
313         }   /// end of switch(elementType)
314     }   /// end of while(1)
315     return object;  // Return the finalized object
316 }
317
318
319 function MinKey() { this._bsontype = 'MinKey'; }  /// these are merely placeholders/stubs to signify the type!?
320
321 function MaxKey() { this._bsontype = 'MaxKey'; }
322
323 function Long(low, high) {
324     this._bsontype = 'Long';
325     this.low_ = low | 0;    this.high_ = high | 0;          /// force into 32 signed bits.
326 }
327 Long.prototype.getLowBits = function(){ return this.low_; }
328 Long.prototype.getHighBits = function(){ return this.high_; }
329
330 Long.prototype.toNumber = function(){
331     return this.high_ * 4294967296 + (this.low_ < 0 ? this.low_ + 4294967296 : this.low_);
332 }
333 Long.fromNumber = function(num){
334     return new Long(num % 4294967296, num / 4294967296);    /// |0 is forced in the constructor!
335 }
336 function Double(value) {
337     this._bsontype = 'Double';
338     this.value = value;
339 }
340 function Timestamp(low, high) {
341     this._bsontype = 'Timestamp';
342     this.low_ = low | 0;    this.high_ = high | 0;          /// force into 32 signed bits.
343 }
344 Timestamp.prototype.getLowBits = function(){ return this.low_; }
345 Timestamp.prototype.getHighBits = function(){ return this.high_; }
346
347 ///////////////////////////////  ObjectID /////////////////////////////////
348 /// machine & proc IDs stored as 1 string, b/c Buffer shouldn't be held for long periods (could use SlowBuffer?!)
349
350 var MACHINE = parseInt(Math.random() * 0xFFFFFF, 10);
351 var PROCESS = process.pid % 0xFFFF;
352 var MACHINE_AND_PROC = encodeIntBE(MACHINE, 3) + encodeIntBE(PROCESS, 2); /// keep as ONE string, ready to go.
353
354 function encodeIntBE(data, bytes){  /// encode the bytes to a string
355     var result = '';
356     if (bytes >= 4){ result += String.fromCharCode(Math.floor(data / 0x1000000)); data %= 0x1000000; }
357     if (bytes >= 3){ result += String.fromCharCode(Math.floor(data / 0x10000)); data %= 0x10000; }
358     if (bytes >= 2){ result += String.fromCharCode(Math.floor(data / 0x100)); data %= 0x100; }
359     result += String.fromCharCode(Math.floor(data));
360     return result;
361 }
362 var _counter = ~~(Math.random() * 0xFFFFFF);    /// double-tilde is equivalent to Math.floor()
363 var checkForHex = new RegExp('^[0-9a-fA-F]{24}$');
364
365 function ObjectID(id) {
366     this._bsontype = 'ObjectID';
367     if (!id){  this.id = createFromScratch();     /// base case, DONE.
368     } else {
369         if (id.constructor === Buffer){
370             this.id = id;  /// case of
371         } else if (id.constructor === String) {
372             if ( id.length === 24 && checkForHex.test(id) ) {
373                 this.id = new Buffer(id, 'hex');
374             } else {
375                 this.id = new Error('Illegal/faulty Hexadecimal string supplied!');     /// changed from 'throw'
376             }
377         } else if (id.constructor === Number) {
378             this.id = createFromTime(id);    /// this is what should be the only interface for this!?
379         }
380     }
381 }
382 function createFromScratch() {
383     var buf = new Buffer(12), i = 0;
384     var ts = ~~(Date.now()/1000);    /// 4 bytes timestamp in seconds, BigEndian notation!
385     buf[i++] = (ts >> 24) & 0xFF;    buf[i++] = (ts >> 16) & 0xFF;
386     buf[i++] = (ts >> 8) & 0xFF;     buf[i++] = (ts) & 0xFF;
387
388     buf.write(MACHINE_AND_PROC, i, 5, 'utf8');  i += 5;  /// write 3 bytes + 2 bytes MACHINE_ID and PROCESS_ID
389     _counter = ++_counter % 0xFFFFFF;       /// 3 bytes internal _counter for subsecond resolution; BigEndian
390     buf[i++] = (_counter >> 16) & 0xFF;
391     buf[i++] = (_counter >> 8) & 0xFF;
392     buf[i++] = (_counter) & 0xFF;
393     return buf;
394 }
395 function createFromTime(ts) {
396     ts || ( ts = ~~(Date.now()/1000) );     /// 4 bytes timestamp in seconds only
397     var buf = new Buffer(12), i = 0;
398     buf[i++] = (ts >> 24) & 0xFF;    buf[i++] = (ts >> 16) & 0xFF;
399     buf[i++] = (ts >> 8) & 0xFF;     buf[i++] = (ts) & 0xFF;
400
401     for (;i < 12; ++i) buf[i] = 0x00;       /// indeces 4 through 11 (8 bytes) get filled up with nulls
402     return buf;
403 }
404 ObjectID.prototype.toHexString = function toHexString() {
405     return this.id.toString('hex');
406 }
407 ObjectID.prototype.getTimestamp = function getTimestamp() {
408     return this.id.readUIntBE(0, 4);
409 }
410 ObjectID.prototype.getTimestampDate = function getTimestampDate() {
411     var ts = new Date();
412     ts.setTime(this.id.readUIntBE(0, 4) * 1000);
413     return ts;
414 }
415 ObjectID.createPk = function createPk () {  ///?override if a PrivateKey factory w/ unique factors is warranted?!
416   return new ObjectID();
417 }
418 ObjectID.prototype.toJSON = function toJSON() {
419     return "ObjectID('" +this.id.toString('hex')+ "')";
420 }
421
422 /// module.exports.BSON = BSON;         /// not needed anymore!? exports.Binary = Binary;
423 module.exports.ObjectID = ObjectID;
424 module.exports.MinKey = MinKey;
425 module.exports.MaxKey = MaxKey;
426 module.exports.Long = Long;    /// ?! we really don't want to do the complicated Long math anywhere for now!?
427
428 //module.exports.Double = Double;
429 //module.exports.Timestamp = Timestamp;