├── .gitignore ├── LICENSE ├── package.json ├── readme.md ├── src ├── bson.ts ├── index.ts └── tsconfig.json ├── test ├── common.js ├── mocha.opts ├── spec │ └── bson_test.ts └── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | test/out 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Marco Paland 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bsonfy", 3 | "version": "1.0.2", 4 | "author": "Marco Paland", 5 | "license": "MIT", 6 | "description": "Ultrafast BSON serializer/parser", 7 | "main": "dist/index.js", 8 | "types": "dist/index.d.ts", 9 | "scripts": { 10 | "build": "tsc -p ./src", 11 | "pretest": "tsc -p ./test", 12 | "test": "mocha ./test/out/test/**/*_test.js", 13 | "dev": "lite-server" 14 | }, 15 | "dependencies": {}, 16 | "devDependencies": { 17 | "typescript": "latest", 18 | "lite-server": "latest", 19 | "ts-node": "^4.1.0", 20 | "chai": "^4.1.2", 21 | "mocha": "^5.0.0", 22 | "@types/chai": "^4.1.2", 23 | "@types/mocha": "^2.2.47", 24 | "@types/node": "^9.4.0" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/mpaland/bsonfy.git" 29 | }, 30 | "keywords": [ 31 | "BSON", 32 | "Parser", 33 | "Serializer", 34 | "Deserializer", 35 | "JSON", 36 | "Typescript" 37 | ], 38 | "bugs": { 39 | "url": "https://github.com/mpaland/bsonfy/issues" 40 | }, 41 | "homepage": "https://github.com/mpaland/bsonfy#readme" 42 | } 43 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # bsonfy - ultrafast BSON parser 2 | 3 | [![npm](https://img.shields.io/npm/v/bsonfy.svg)](https://www.npmjs.com/package/bsonfy) 4 | [![npm](https://img.shields.io/npm/dt/bsonfy.svg)](https://www.npmjs.com/package/bsonfy) 5 | [![Github Issues](https://img.shields.io/github/issues/mpaland/bsonfy.svg)](http://github.com/mpaland/bsonfy/issues) 6 | [![Github Releases](https://img.shields.io/github/release/mpaland/bsonfy.svg)](https://github.com/mpaland/bsonfy/releases) 7 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/mpaland/mipher/master/LICENSE) 8 | 9 | **bsonfy** is an ultrafast serializer and deserializer for the [BSON](http://bsonspec.org) format. 10 | It is written in clean typescript and has no other lib dependencies. 11 | BSON is mainly used as compact transport format for (JSON) objects. 12 | 13 | 14 | ### Motivation 15 | I needed a simple, fast and clean (typescript) module to generate and parse BSON for storing JSON objects in files efficiently. 16 | There are some parsers around (2016/06), mainly the primary one of the mongodb project. But I found that it's really not lightweight enough and too slow for mobile usage. 17 | A further requirement was using typed arrays (`Uint8Array`) instead of nodejs buffers, to get this baby portable and running in browsers, too. 18 | 19 | 20 | ### Design goals: 21 | - Written in typescript 22 | - Fast and lightweight parser 23 | - Very easy to use, just one include module, NO dependencies 24 | - tslint warning free, clean code 25 | - Unit tested, around 50 passing test cases 26 | - Rocksolid (I hope so) 27 | - MIT license 28 | 29 | 30 | ## Usage 31 | Using this module is rather simple. Copy or (npm) install *bsonfy* to your project and use it like: 32 | 33 | ```typescript 34 | import { BSON } from 'bsonfy'; 35 | 36 | // create a test document 37 | let doc = { id: 10, time: new BSON.UTC(), arr: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]) }; 38 | 39 | // serialize the document 40 | let bson = BSON.serialize(doc); 41 | 42 | // and deserialize it, using BSON.UTC objects as time representation 43 | let orig = BSON.deserialize(bson, true); 44 | ``` 45 | 46 | 47 | ## API 48 | 49 | Basically the API consists of just two static methods to serialize/deserialize objects to/from BSON format: 50 | 51 | ### BSON serialization and deserialiation 52 | 53 | **`BSON.serialize(object)`** 54 | * @param {Object} object The Javascript object to serialize 55 | * @return {Uint8Array} returns an Uint8Array in BSON format. 56 | Unknown objects are ignored in serialization. 57 | 58 | **`BSON.deserialize(buffer, useUTC)`** 59 | * @param {Uint8Array} buffer An Uint8Array containing the BSON data 60 | * @param {Boolean} useUTC Optional, if set a `BSON.UTC` object is created for 'UTC datetime' instead of a normal JS `Date` object. Defaults to false 61 | * @return {Object} returns the deserialized Javascript object or `undefined` in case of a parsing error (unsupported BSON element etc.) 62 | 63 | 64 | ### UTC 65 | 66 | **`bson.ObjectId.isValid(id)`** - Returns true if `id` is a valid number or hexadecimal string representing an ObjectId. 67 | **`bson.ObjectId.createFromHexString(hexString)`** - Returns the ObjectId the `hexString` represents. 68 | **`bson.ObjectId.createFromTime(time)`** - Returns an ObjectId containing the passed time. 69 | * `time` - A Unix timestamp (number of seconds since the epoch). 70 | 71 | 72 | ### UUID 73 | 74 | **`bson.ObjectId.isValid(id)`** - Returns true if `id` is a valid number or hexadecimal string representing an ObjectId. 75 | **`bson.ObjectId.createFromHexString(hexString)`** - Returns the ObjectId the `hexString` represents. 76 | **`bson.ObjectId.createFromTime(time)`** - Returns an ObjectId containing the passed time. 77 | * `time` - A Unix timestamp (number of seconds since the epoch). 78 | 79 | 80 | ### ObjectId 81 | 82 | **`bson.ObjectId.isValid(id)`** - Returns true if `id` is a valid number or hexadecimal string representing an ObjectId. 83 | **`bson.ObjectId.createFromHexString(hexString)`** - Returns the ObjectId the `hexString` represents. 84 | **`bson.ObjectId.createFromTime(time)`** - Returns an ObjectId containing the passed time. 85 | * `time` - A Unix timestamp (number of seconds since the epoch). 86 | 87 | 88 | ### Unsupported elements 89 | The following BSON elements are currently not supported (and lead to a deserialiation error): 90 | - JavaScript code 91 | - Min key 92 | - Max key 93 | - Regular expression (implemented, but untested yet - so don't rely on it) 94 | 95 | 96 | ## Caveats 97 | - 64-bit integer BSON values are converted to the Javascript Number type. 98 | However, Javascript supports integer precision up to 2^53 as maximum size. 99 | If a parsed 64-bit integer exceeds this size, floating point rounding errors may occur! 100 | 101 | 102 | ## Test suite 103 | bsonfy is using the mocha test suite for testing. 104 | To do all tests just run `npm run test`. 105 | 106 | 107 | ## Contributing 108 | If you find any bugs, have any comments, improvements or suggestions: 109 | 110 | 1. Create an issue and describe your idea 111 | 2. [Fork it](https://github.com/mpaland/bsonfy/fork) 112 | 3. Create your feature branch (`git checkout -b my-new-feature`) 113 | 4. Commit your changes (`git commit -am 'Add some feature'`) 114 | 5. Publish the branch (`git push origin my-new-feature`) 115 | 6. Create a new pull request 116 | 7. Profit! :white_check_mark: 117 | 118 | 119 | ## License 120 | bsonfy is written under the [MIT license](http://www.opensource.org/licenses/MIT). 121 | -------------------------------------------------------------------------------- /src/bson.ts: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // \author (c) Marco Paland (marco@paland.com) 3 | // 2016-2018, PALANDesign Hannover, Germany 4 | // 5 | // \license The MIT License (MIT) 6 | // 7 | // This file is part of the bsonfy library. 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | // \brief Extrem fast BSON implementation in typescript with NO dependencies 27 | // See http://bsonspec.org for details 28 | // Usage: 29 | // import { BSON } from './bsonfy'; 30 | // let obj = { id: 10, time: new BSON.UTC(), arr: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]) }; 31 | // let bson = BSON.serialize(obj); 32 | // let orig = BSON.deserialize(bson); 33 | // 34 | /////////////////////////////////////////////////////////////////////////////// 35 | 36 | 37 | export namespace BSON { 38 | 39 | /** 40 | * BSON module version 41 | */ 42 | const version: string = '1.0.2'; 43 | 44 | 45 | /** 46 | * UUID class 47 | */ 48 | export class UUID { 49 | private _id: Uint8Array; 50 | 51 | constructor(id: Uint8Array | Array) { 52 | this._id = new Uint8Array(id); 53 | } 54 | 55 | buffer(): Uint8Array { 56 | return this._id; 57 | } 58 | } 59 | 60 | 61 | /** 62 | * ObjectId class (for mongoDB usage) 63 | */ 64 | export class ObjectId { 65 | private _id: Uint8Array; 66 | 67 | constructor(id: Uint8Array | Array) { 68 | this._id = new Uint8Array(id); 69 | } 70 | 71 | buffer(): Uint8Array { 72 | return this._id; 73 | } 74 | } 75 | 76 | 77 | /** 78 | * The UTC class contains the milliseconds since the Unix epoch (1.1.1970 00:00:00 UTC) 79 | */ 80 | export class UTC { 81 | private _time: Uint8Array; 82 | 83 | constructor(time?: Uint8Array | Array | string) { 84 | this._time = (typeof time !== 'string') ? new Uint8Array(time || number2long(Date.now())) : number2long(+new Date(time)); 85 | } 86 | 87 | buffer(): Uint8Array { 88 | return this._time; 89 | } 90 | 91 | /** 92 | * Convert an (ISO) date string 93 | * @param {String} date (ISO) Date string 94 | */ 95 | fromString(date: string): void { 96 | this._time = number2long(+new Date(date)); 97 | } 98 | 99 | /** 100 | * Returns the milliseconds since the Unix epoch (UTC) 101 | */ 102 | toNumber(): number { 103 | return long2number(this._time); 104 | } 105 | 106 | toDate(): Date { 107 | return new Date(long2number(this._time)); 108 | } 109 | } 110 | 111 | 112 | /** 113 | * Private, return the size of the given object 114 | * @param {Object} obj The object to get the size from 115 | * @return {Number} The object size in bytes 116 | */ 117 | function getObjectSize(obj: Object): number { 118 | let len = 4 + 1; // handle the obj.length prefix + terminating '0' 119 | for (let key in obj) { 120 | len += getElementSize(key, obj[key]); 121 | } 122 | return len; 123 | } 124 | 125 | 126 | /** 127 | * Private, get the size of the given element 128 | * @param {String} name 129 | * @param {Object} value 130 | * @return {Number} The element size in bytes 131 | */ 132 | function getElementSize(name: string, value: any): number { 133 | let len = 1; // always starting with 1 for the data type byte 134 | if (name) { 135 | len += strlen(name) + 1; // cstring: name + '0' termination 136 | } 137 | 138 | if (value === undefined || value === null) { 139 | return len; // just the type byte plus name cstring 140 | } 141 | 142 | switch (value.constructor) { 143 | case String: 144 | return len + 4 + strlen(value) + 1; 145 | 146 | case Number: 147 | if (Math.floor(value) === value) { 148 | if (value <= 2147483647 && value >= -2147483647) 149 | return len + 4; // 32 bit 150 | else 151 | return len + 8; // 64 bit 152 | } 153 | else 154 | return len + 8; // 64 bit double & float 155 | 156 | case Boolean: 157 | return len + 1; 158 | 159 | case Array: 160 | case Object: 161 | return len + getObjectSize(value); 162 | 163 | case Int8Array: 164 | case Uint8Array: 165 | return len + 5 + value.byteLength; 166 | 167 | case Date: 168 | case UTC: 169 | return len + 8; 170 | 171 | case UUID: 172 | return len + 5 + 16; 173 | 174 | case ObjectId: 175 | return len + 12; 176 | 177 | default: 178 | // unsupported type 179 | return 0; 180 | } 181 | } 182 | 183 | 184 | /** 185 | * Serialize an object to BSON format 186 | * @param {Object} object The object to serialize 187 | * @return {Uint8Array} An byte array with the BSON representation 188 | */ 189 | export function serialize(object: any): Uint8Array { 190 | let buffer = new Uint8Array(getObjectSize(object)); 191 | serializeEx(object, buffer); 192 | return buffer; 193 | } 194 | 195 | 196 | /** 197 | * Private, used by serialize() and is called recursively 198 | * @param object 199 | * @param buffer 200 | * @param i 201 | */ 202 | function serializeEx(object: any, buffer: Uint8Array, i: number = 0): number { 203 | i += int32(buffer.length, buffer, i); 204 | 205 | if (object.constructor === Array) { 206 | for (let j = 0, len = object.length; j < len; j++) { 207 | i = packElement(j.toString(), object[j], buffer, i); 208 | } 209 | } 210 | else { 211 | for (let key in object) { 212 | i = packElement(key, object[key], buffer, i); 213 | } 214 | } 215 | buffer[i++] = 0; // terminating zero 216 | return i; 217 | } 218 | 219 | 220 | /** 221 | * Private, assemble BSON cstring element 222 | * @param name 223 | * @param buffer 224 | * @param offset 225 | * @return Element length in bytes 226 | */ 227 | function cstring(name: string, buffer: Uint8Array, offset: number): number { 228 | let cstring = str2bin(name); 229 | let clen = cstring.length; 230 | buffer.set(cstring, offset); 231 | buffer[offset + clen++] = 0; 232 | return clen; 233 | } 234 | 235 | 236 | /** 237 | * Private, assemble BSON int32 element 238 | * @param size 239 | * @param buffer 240 | * @param offset 241 | * @return Element length in bytes 242 | */ 243 | function int32(size: number, buffer: Uint8Array, offset: number): number { 244 | buffer[offset++] = (size) & 0xff; 245 | buffer[offset++] = (size >>> 8) & 0xff; 246 | buffer[offset++] = (size >>> 16) & 0xff; 247 | buffer[offset++] = (size >>> 24) & 0xff; 248 | return 4; 249 | } 250 | 251 | 252 | /** 253 | * Private, assemble BSON elements 254 | * @param name 255 | * @param value 256 | * @param buffer 257 | * @param i 258 | */ 259 | function packElement(name: string, value: any, buffer: Uint8Array, i: number): number { 260 | if (value === undefined || value === null) { 261 | buffer[i++] = 0x0A; // BSON type: Null 262 | i += cstring(name, buffer, i); 263 | return i; 264 | } 265 | switch (value.constructor) { 266 | case String: 267 | buffer[i++] = 0x02; // BSON type: String 268 | i += cstring(name, buffer, i); 269 | let size = cstring(value, buffer, i + 4); 270 | i += int32(size, buffer, i); 271 | return i + size; 272 | 273 | case Number: 274 | if (Math.floor(value) === value) { 275 | if (value <= 2147483647 && value >= -2147483647) { /// = BSON.BSON_INT32_MAX / MIN asf. 276 | buffer[i++] = 0x10; // BSON type: int32 277 | i += cstring(name, buffer, i); 278 | i += int32(value, buffer, i); 279 | } 280 | else { 281 | buffer[i++] = 0x12; // BSON type: int64 282 | i += cstring(name, buffer, i); 283 | buffer.set(number2long(value), i); 284 | i += 8; 285 | } 286 | } 287 | else { 288 | // it's a float / double 289 | buffer[i++] = 0x01; // BSON type: 64-bit floating point 290 | i += cstring(name, buffer, i); 291 | let f = new Float64Array([value]); 292 | let d = new Uint8Array(f.buffer); 293 | buffer.set(d, i); 294 | i += 8; 295 | } 296 | return i; 297 | 298 | case Boolean: 299 | buffer[i++] = 0x08; // BSON type: Boolean 300 | i += cstring(name, buffer, i); 301 | buffer[i++] = value ? 1 : 0; 302 | return i; 303 | 304 | case Array: 305 | case Object: 306 | buffer[i++] = value.constructor === Array ? 0x04 : 0x03; // BSON type: Array / Document 307 | i += cstring(name, buffer, i); 308 | let end = serializeEx(value, buffer, i); 309 | int32(end - i, buffer, i); // correct size 310 | return end; 311 | 312 | case Int8Array: 313 | case Uint8Array: 314 | buffer[i++] = 0x05; // BSON type: Binary data 315 | i += cstring(name, buffer, i); 316 | i += int32(value.byteLength, buffer, i); 317 | buffer[i++] = 0; // use generic binary subtype 0 318 | buffer.set(value, i); 319 | i += value.byteLength; 320 | return i; 321 | 322 | case Date: 323 | buffer[i++] = 0x09; // BSON type: UTC datetime 324 | i += cstring(name, buffer, i); 325 | buffer.set(number2long(value.getTime()), i); 326 | i += 8; 327 | return i; 328 | 329 | case UTC: 330 | buffer[i++] = 0x09; // BSON type: UTC datetime 331 | i += cstring(name, buffer, i); 332 | buffer.set(value.buffer(), i); 333 | i += 8; 334 | return i; 335 | 336 | case UUID: 337 | buffer[i++] = 0x05; // BSON type: Binary data 338 | i += cstring(name, buffer, i); 339 | i += int32(16, buffer, i); 340 | buffer[i++] = 4; // use UUID subtype 341 | buffer.set(value.buffer(), i); 342 | i += 16; 343 | return i; 344 | 345 | case ObjectId: 346 | buffer[i++] = 0x07; // BSON type: ObjectId 347 | i += cstring(name, buffer, i); 348 | buffer.set(value.buffer(), i); 349 | i += 12; 350 | return i; 351 | 352 | case RegExp: 353 | buffer[i++] = 0x0B; // BSON type: Regular expression 354 | i += cstring(name, buffer, i); 355 | i += cstring(value.source, buffer, i); 356 | if (value.global) buffer[i++] = 0x73; // s = 'g' 357 | if (value.ignoreCase) buffer[i++] = 0x69; // i 358 | if (value.multiline) buffer[i++] = 0x6d; // m 359 | buffer[i++] = 0; 360 | return i; 361 | 362 | default: 363 | return i; // unknown type (ignore element) 364 | } 365 | } 366 | 367 | 368 | /** 369 | * Deserialize (parse) BSON data to an object 370 | * @param {Uint8Array} buffer The buffer with BSON data to convert 371 | * @param {Boolean} useUTC Optional, if set an UTC object is created for 'UTC datetime', else an Date object. Defaults to false 372 | * @return {Object} Returns an object or an array 373 | */ 374 | export function deserialize(buffer: Uint8Array, useUTC: boolean = false, i: number = 0, returnArray: boolean = false): Array | Object { 375 | // check size 376 | if (buffer.length < 5) { 377 | // Document error: Size < 5 bytes 378 | return undefined; 379 | } 380 | let size = buffer[i++] | buffer[i++] << 8 | buffer[i++] << 16 | buffer[i++] << 24; 381 | if (size < 5 || size > buffer.length) { 382 | // Document error: Size mismatch 383 | return undefined; 384 | } 385 | if (buffer[buffer.length - 1] !== 0x00) { 386 | // Document error: Missing termination 387 | return undefined; 388 | } 389 | 390 | let object = returnArray ? [] : {}; // needed for type ARRAY recursion later 391 | 392 | for (;;) { 393 | // get element type 394 | let elementType = buffer[i++]; // read type 395 | if (elementType === 0) break; // zero means last byte, exit 396 | 397 | // get element name 398 | let end = i; 399 | for (; buffer[end] !== 0x00 && end < buffer.length; end++); 400 | if (end >= buffer.length - 1) { 401 | // Document error: Illegal key name 402 | return undefined; 403 | } 404 | let name: any = bin2str(buffer.subarray(i, end)); 405 | if (returnArray) { 406 | name = parseInt(name); // convert to number as array index 407 | } 408 | i = ++end; // skip terminating zero 409 | 410 | switch (elementType) { 411 | case 0x01: // BSON type: 64-bit floating point 412 | object[name] = (new Float64Array(buffer.slice(i, i += 8).buffer))[0]; // use slice() here to get a new array 413 | break; 414 | 415 | case 0x02: // BSON type: String 416 | size = buffer[i++] | buffer[i++] << 8 | buffer[i++] << 16 | buffer[i++] << 24; 417 | object[name] = bin2str(buffer.subarray(i, i += size - 1)); 418 | i++; 419 | break; 420 | 421 | case 0x03: // BSON type: Document (Object) 422 | size = buffer[i] | buffer[i + 1] << 8 | buffer[i + 2] << 16 | buffer[i + 3] << 24; 423 | object[name] = deserialize(buffer, useUTC, i, false); // isArray = false => Object 424 | i += size; 425 | break; 426 | 427 | case 0x04: // BSON type: Array 428 | 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 429 | object[name] = deserialize(buffer, useUTC, i, true); // pass current index & return an array 430 | i += size; 431 | break; 432 | 433 | case 0x05: // BSON type: Binary data 434 | size = buffer[i++] | buffer[i++] << 8 | buffer[i++] << 16 | buffer[i++] << 24; 435 | if (buffer[i++] === 0x04) { // BSON subtype: UUID 436 | if (size !== 16) { 437 | // Element error: Wrong UUID length 438 | return undefined; 439 | } 440 | object[name] = new UUID(buffer.subarray(i, i += size)); 441 | } 442 | else { 443 | // all other subtypes 444 | object[name] = buffer.slice(i, i += size); // use slice() here to get a new array 445 | } 446 | break; 447 | 448 | case 0x06: // BSON type: Undefined (deprecated) 449 | object[name] = null; 450 | break; 451 | 452 | case 0x07: // BSON type: ObjectId 453 | object[name] = new ObjectId(buffer.subarray(i, i += 12)); 454 | break; 455 | 456 | case 0x08: // BSON type: Boolean 457 | object[name] = buffer[i++] === 1; 458 | break; 459 | 460 | case 0x09: // BSON type: UTC datetime 461 | object[name] = useUTC ? new UTC(buffer.subarray(i, i += 8)) : new Date(long2number(buffer.subarray(i, i += 8))); 462 | break; 463 | 464 | case 0x0A: // BSON type: Null 465 | object[name] = null; 466 | break; 467 | 468 | case 0x0B: // BSON type: RegExp 469 | end = i; 470 | // pattern 471 | while (end < buffer.length && buffer[end++] !== 0x00); 472 | if (end >= buffer.length) { 473 | // Document error: Illegal key name 474 | return undefined; 475 | } 476 | let pat = bin2str(buffer.subarray(i, end)); 477 | i = end; 478 | // flags 479 | while (end < buffer.length && buffer[end++] !== 0x00); 480 | if (end >= buffer.length) { 481 | // Document error: Illegal key name 482 | return undefined; 483 | } 484 | let flags = bin2str(buffer.subarray(i, end)); 485 | i = end; 486 | object[name] = new RegExp(pat, flags); 487 | break; 488 | 489 | case 0x10: // BSON type: 32-bit integer 490 | object[name] = buffer[i++] | buffer[i++] << 8 | buffer[i++] << 16 | buffer[i++] << 24; 491 | break; 492 | 493 | case 0x12: // BSON type: 64-bit integer 494 | object[name] = long2number(buffer.subarray(i, i += 8)); 495 | break; 496 | 497 | default: 498 | // Parsing error: Unknown element 499 | return undefined; 500 | } 501 | } 502 | return object; 503 | } 504 | 505 | 506 | ///////////////////////////////////////////////////////////////////////////// 507 | // H E L P E R 508 | 509 | /** 510 | * Convert a number to a 64 bit integer representation 511 | * @param {Number} value Number to convert 512 | * @return {Uint8Array} Converted number 513 | */ 514 | function number2long(value: number): Uint8Array { 515 | let buf = new Uint8Array(8); 516 | if (Math.floor(value) === value) { 517 | const TWO_PWR_32 = 4294967296; 518 | let lo = (value % TWO_PWR_32) | 0, hi = (value / TWO_PWR_32) | 0; 519 | if (value < 0) { 520 | lo = ~(-value % TWO_PWR_32) | 0, hi = ~(-value / TWO_PWR_32) | 0; 521 | lo = (lo + 1) & 0xffffffff; 522 | if (!lo) hi++; 523 | } 524 | let i = 0; 525 | buf[i++] = (lo & 0xff); buf[i++] = (lo >>> 8) & 0xff; buf[i++] = (lo >>> 16) & 0xff; buf[i++] = (lo >>> 24) & 0xff; 526 | buf[i++] = (hi & 0xff); buf[i++] = (hi >>> 8) & 0xff; buf[i++] = (hi >>> 16) & 0xff; buf[i] = (hi >>> 24) & 0xff; 527 | } 528 | else { // it's a float / double 529 | let f = new Float64Array([value]); 530 | let d = new Uint8Array(f.buffer); 531 | buf.set(d); 532 | } 533 | return buf; 534 | } 535 | 536 | 537 | /** 538 | * Convert 64 bit integer to Number 539 | * @param {Uint8Array} buffer Buffer containing a 64 bit integer as typed array at offset position. LSB is [0], MSB is [7] 540 | * @param {Number} offset Offset in buffer, where the integer starts 541 | * @return {Number} Converted number 542 | */ 543 | function long2number(buffer: Uint8Array, offset: number = 0): number { 544 | const TWO_PWR_32 = 4294967296; 545 | let lo = buffer[offset++] | buffer[offset++] << 8 | buffer[offset++] << 16 | buffer[offset++] << 24; 546 | let hi = buffer[offset++] | buffer[offset++] << 8 | buffer[offset++] << 16 | buffer[offset] << 24; 547 | return hi * TWO_PWR_32 + ((lo >= 0) ? lo : TWO_PWR_32 + lo); 548 | } 549 | 550 | 551 | /** 552 | * Convert a string (UTF-8 encoded) to a byte array 553 | * @param {String} str UTF-8 encoded string 554 | * @return {Uint8Array} Byte array 555 | */ 556 | function str2bin(str: string): Uint8Array { 557 | str = str.replace(/\r\n/g, '\n'); 558 | let bin = [], p = 0; 559 | for (let i = 0, len = str.length; i < len; i++) { 560 | let c = str.charCodeAt(i); 561 | if (c < 128) { 562 | bin[p++] = c; 563 | } else if (c < 2048) { 564 | bin[p++] = (c >>> 6) | 192; 565 | bin[p++] = (c & 63) | 128; 566 | } else { 567 | bin[p++] = (c >>> 12) | 224; 568 | bin[p++] = ((c >>> 6) & 63) | 128; 569 | bin[p++] = (c & 63) | 128; 570 | } 571 | } 572 | return new Uint8Array(bin); 573 | } 574 | 575 | 576 | /** 577 | * Convert a byte array to an UTF-8 string 578 | * @param {Uint8Array} bin UTF-8 text given as array of bytes 579 | * @return {String} UTF-8 Text string 580 | */ 581 | function bin2str(bin: Uint8Array): string { 582 | let str = '', len = bin.length, i = 0, c, c2, c3; 583 | 584 | while (i < len) { 585 | c = bin[i]; 586 | if (c < 128) { 587 | str += String.fromCharCode(c); 588 | i++; 589 | } 590 | else if ((c > 191) && (c < 224)) { 591 | c2 = bin[i + 1]; 592 | str += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); 593 | i += 2; 594 | } 595 | else { 596 | c2 = bin[i + 1]; 597 | c3 = bin[i + 2]; 598 | str += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); 599 | i += 3; 600 | } 601 | } 602 | return str; 603 | } 604 | 605 | 606 | /** 607 | * Returns the UTF-8 string length in bytes 608 | * @param {String} Input string 609 | * @return {Number} Stringlength in bytes (not in chars) 610 | */ 611 | function strlen(str: string): number { 612 | return encodeURI(str).split(/%..|./).length - 1; 613 | } 614 | 615 | } // namespace BSON 616 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // \author (c) Marco Paland (marco@paland.com) 3 | // 2015-2018, PALANDesign Hannover, Germany 4 | // 5 | // \license The MIT License (MIT) 6 | // 7 | // This file is part of the bysonfy library. 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | // \brief bsonfy module exports 27 | // 28 | /////////////////////////////////////////////////////////////////////////////// 29 | 30 | export { BSON } from './bson'; 31 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "declaration": true, 9 | "outDir": "../dist", 10 | "rootDir": "." 11 | }, 12 | "exclude": [ 13 | "dist", 14 | "node_modules", 15 | "typings/main", 16 | "typings/main.d.ts" 17 | ], 18 | "filesGlob": [ 19 | "**/*.ts", 20 | "!node_modules/**/*" 21 | ], 22 | "compileOnSave": false, 23 | "atom": { 24 | "rewriteTsconfig": false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/common.js: -------------------------------------------------------------------------------- 1 | global.btoa = function (str) { 2 | return new Buffer(str).toString('base64'); 3 | }; 4 | 5 | global.atob = function (b64) { 6 | return new Buffer(b64, 'base64').toString(); 7 | }; 8 | 9 | // global.chai = require('chai'); 10 | // var expect = chai.expect; 11 | // var assert = chai.assert; 12 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require ./test/common.js 2 | --reporter spec 3 | --ui bdd 4 | --recursive 5 | --colors 6 | --timeout 60000 7 | --slow 300 8 | -------------------------------------------------------------------------------- /test/spec/bson_test.ts: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // \author (c) Marco Paland (marco@paland.com) 3 | // 2018, PALANDesign Hannover, Germany 4 | // 5 | // \license The MIT License (MIT) 6 | // 7 | // This file is part of the bysonfy library. 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | // \brief BSON test cases 27 | // 28 | /////////////////////////////////////////////////////////////////////////////// 29 | 30 | import { BSON } from '../../src/bson'; 31 | 32 | import { expect, assert } from 'chai'; 33 | import 'mocha'; 34 | 35 | 36 | describe('BSON', () => { 37 | 38 | class UnknownObj { 39 | _dummy: number = 123; 40 | test() { 41 | } 42 | } 43 | 44 | let serialize_vector = [ 45 | { 46 | obj: { "BSON": ["awesome", 5.05, 1986] }, 47 | bson: "310000000442534f4e002600000002300008000000617765736f6d65000131003333333333331440103200c20700000000", 48 | }, 49 | { 50 | obj: { int32: 10, int64: 1125899906842624, flo: 3.141592653, str: "Hello äöü", utc: new BSON.UTC("2011-10-10T14:48:00Z"), bool: true, date: new Date("2011-10-10T14:48:00Z") }, 51 | bson: "6400000010696e743332000a00000012696e74363400000000000000040001666c6f0038e92f54fb21094002737472000d00000048656c6c6f20c3a4c3b6c3bc00097574630000f94dee3201000008626f6f6c000109646174650000f94dee3201000000" 52 | }, 53 | { 54 | obj: { arr: ["foo", "bar", 100, 1000], ta: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]), obj: { int32: 10, int64: 1125899906842624, flo: 3.141592653 } }, 55 | bson: "7500000004617272002900000002300004000000666f6f00023100040000006261720010320064000000103300e8030000000574610008000000000102030405060708036f626a002c00000010696e743332000a00000012696e74363400000000000000040001666c6f0038e92f54fb2109400000" 56 | }, 57 | { 58 | obj: { da: [[1, 2, 3], [4, 5, 6], [7, 8, 9]], uuid: new BSON.UUID(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])) }, 59 | bson: "80000000046461005c0000000430001a000000103000010000001031000200000010320003000000000431001a000000103000040000001031000500000010320006000000000432001a000000103000070000001031000800000010320009000000000005757569640010000000040102030405060708090a0b0c0d0e0f1000" 60 | }, 61 | { 62 | obj: { id: 123456, sk: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]), pk: new Uint8Array([255, 254, 253, 252, 251, 250, 249, 248]) }, 63 | bson: "2f0000001069640040e2010005736b000800000000010203040506070805706b000800000000fffefdfcfbfaf9f800" 64 | }, 65 | { 66 | obj: { id: 10, obj: new UnknownObj(), str: "Test", n: null, b: true }, 67 | bson: "22000000106964000a00000002737472000500000054657374000a6e000862000100" 68 | } 69 | ]; 70 | 71 | 72 | let deserialize_vector = [ 73 | { 74 | obj: { "BSON": ["awesome", 5.05, 1986] }, 75 | bson: "310000000442534f4e002600000002300008000000617765736f6d65000131003333333333331440103200c20700000000", 76 | }, 77 | { 78 | obj: { int32: 10, int64: 1125899906842624, flo: 3.141592653, str: "Hello äöü", utc: new BSON.UTC("2011-10-10T14:48:00Z"), bool: true, date: new Uint8Array([1,2,3,4]), nu: null }, 79 | bson: "6900000010696e743332000a00000012696e74363400000000000000040001666c6f0038e92f54fb21094002737472000d00000048656c6c6f20c3a4c3b6c3bc00097574630000f94dee3201000008626f6f6c00010564617465000400000000010203040a6e750000" 80 | }, 81 | { 82 | obj: { arr: ["foo", "bar", 100, 1000], ta: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]), obj: { int32: 10, int64: 1125899906842624, flo: 3.141592653 } }, 83 | bson: "7500000004617272002900000002300004000000666f6f00023100040000006261720010320064000000103300e8030000000574610008000000000102030405060708036f626a002c00000010696e743332000a00000012696e74363400000000000000040001666c6f0038e92f54fb2109400000" 84 | }, 85 | { 86 | obj: { da: [[1, 2, 3], [4, 5, 6], [7, 8, 9]], uuid: new BSON.UUID(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])) }, 87 | bson: "80000000046461005c0000000430001a000000103000010000001031000200000010320003000000000431001a000000103000040000001031000500000010320006000000000432001a000000103000070000001031000800000010320009000000000005757569640010000000040102030405060708090a0b0c0d0e0f1000" 88 | }, 89 | { 90 | obj: { id: 123456, sk: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]), pk: new Uint8Array([255, 254, 253, 252, 251, 250, 249, 248]) }, 91 | bson: "2f0000001069640040e2010005736b000800000000010203040506070805706b000800000000fffefdfcfbfaf9f800" 92 | }, 93 | { 94 | obj: { id: 10, oid: new BSON.ObjectId(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])), str: "Test", n: null, b: true }, 95 | bson: "33000000106964000a000000076f6964000102030405060708090a0b0c02737472000500000054657374000a6e000862000100" 96 | } 97 | ]; 98 | 99 | 100 | describe("serialize", function () { 101 | it("checks empty/unknown object", function () { 102 | let bson = BSON.serialize(""); 103 | expect(bin2hex(bson)).to.deep.equal("0500000000"); 104 | bson = BSON.serialize({ obj: new UnknownObj() }); 105 | expect(bin2hex(bson)).to.deep.equal("0500000000"); 106 | }); 107 | 108 | it("checks int32", function () { 109 | let bson = BSON.serialize({ int: 0x1234 }); 110 | expect(bin2hex(bson)).to.deep.equal("0e00000010696e74003412000000"); 111 | }); 112 | 113 | it("checks negative int32", function () { 114 | let bson = BSON.serialize({ int: -10 }); 115 | expect(bin2hex(bson)).to.deep.equal("0e00000010696e7400f6ffffff00"); 116 | }); 117 | 118 | it("checks int64", function () { 119 | let bson = BSON.serialize({ int: 0x1234567890 }); 120 | expect(bin2hex(bson)).to.deep.equal("1200000012696e7400907856341200000000"); 121 | }); 122 | 123 | it("checks negative int64", function () { 124 | let bson = BSON.serialize({ int: -78187493520 }); 125 | expect(bin2hex(bson)).to.deep.equal("1200000012696e74007087a9cbedffffff00"); 126 | }); 127 | 128 | it("checks double (64-bit binary floating point)", function () { 129 | let bson = BSON.serialize({ flo: 3.1415926535 }); 130 | expect(bin2hex(bson)).to.deep.equal("1200000001666c6f0044174154fb21094000"); 131 | }); 132 | 133 | it("checks string", function () { 134 | let bson = BSON.serialize({ str: "Hello World" }); 135 | expect(bin2hex(bson)).to.deep.equal("1a00000002737472000c00000048656c6c6f20576f726c640000"); 136 | }); 137 | 138 | it("checks UTF-8 string", function () { 139 | let bson = BSON.serialize({ str: "\u00C4\u00D6\u00DC\u00DF" }); 140 | expect(bin2hex(bson)).to.deep.equal("17000000027374720009000000c384c396c39cc39f0000"); 141 | }); 142 | 143 | it("checks boolean", function () { 144 | let bson = BSON.serialize({ bool: false }); 145 | expect(bin2hex(bson)).to.deep.equal("0c00000008626f6f6c000000"); 146 | bson = BSON.serialize({ bool: true }); 147 | expect(bin2hex(bson)).to.deep.equal("0c00000008626f6f6c000100"); 148 | }); 149 | 150 | it("checks null", function () { 151 | let bson = BSON.serialize({ nul: null }); 152 | expect(bin2hex(bson)).to.deep.equal("0a0000000a6e756c0000"); 153 | }); 154 | 155 | it("checks binary", function () { 156 | let bson = BSON.serialize({ bin: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0xFF]) }); 157 | expect(bin2hex(bson)).to.deep.equal("190000000562696e000a00000000010203040506070809ff00"); 158 | }); 159 | 160 | it("checks array", function () { 161 | let bson = BSON.serialize({ arr: [0xFA, 0xFB, 0xFC, 0xFD] }); 162 | expect(bin2hex(bson)).to.deep.equal("2b000000046172720021000000103000fa000000103100fb000000103200fc000000103300fd0000000000"); 163 | }); 164 | 165 | it("checks array in array", function () { 166 | let bson = BSON.serialize({ arr: [[0x10, 0x11, 0x12, 0x13], 0xFA, 0xFB, 0xFC, 0xFD] }); 167 | expect(bin2hex(bson)).to.deep.equal("4f000000046172720045000000043000210000001030001000000010310011000000103200120000001033001300000000103100fa000000103200fb000000103300fc000000103400fd0000000000"); 168 | }); 169 | 170 | it("checks object", function () { 171 | let bson = BSON.serialize({ obj: { int: 10, str: "" } }); 172 | expect(bin2hex(bson)).to.deep.equal("22000000036f626a001800000010696e74000a000000027374720001000000000000"); 173 | }); 174 | 175 | it("checks Date", function () { 176 | let bson = BSON.serialize({ dat: new Date("2016-06-25T14:48:11Z") }); 177 | expect(bin2hex(bson)).to.deep.equal("120000000964617400f84308885501000000"); 178 | }); 179 | 180 | it("checks UTC", function () { 181 | let bson = BSON.serialize({ utc1: new BSON.UTC("2016-06-25T14:48:11Z"), utc2: new BSON.UTC("2016-06-25T14:48:11+0200"), utc3: new BSON.UTC([0x3D, 0x53, 0xAE, 0x91, 0x55, 0x01, 0x00, 0x00]) }); 182 | expect(bin2hex(bson)).to.deep.equal("2f000000097574633100f843088855010000097574633200f8669a87550100000975746333003d53ae915501000000"); 183 | }); 184 | 185 | it("checks UUID", function () { 186 | let bson = BSON.serialize({ uuid: new BSON.UUID(new Uint8Array([0x43, 0xab, 0x2e, 0x98, 0x62, 0x3c, 0x03, 0xe8, 0x5f, 0x54, 0x1a, 0x17, 0x45, 0xe0, 0x1b, 0xda])) }); 187 | expect(bin2hex(bson)).to.deep.equal("20000000057575696400100000000443ab2e98623c03e85f541a1745e01bda00"); 188 | }); 189 | 190 | it("checks ObjectId", function () { 191 | let bson = BSON.serialize({ oid: new BSON.ObjectId([0xa8, 0x05, 0x57, 0xf0, 0x5c, 0x6d, 0x7a, 0xd0, 0x9f, 0xa7, 0x35, 0x70]) }); 192 | expect(bin2hex(bson)).to.deep.equal("16000000076f696400a80557f05c6d7ad09fa7357000"); 193 | }); 194 | 195 | it("checks complex objects", function () { 196 | for (let i = 0; i < serialize_vector.length; i++) { 197 | let bson = BSON.serialize(serialize_vector[i].obj); 198 | expect(bin2hex(bson)).to.deep.equal(serialize_vector[i].bson); 199 | } 200 | }); 201 | }); 202 | 203 | 204 | describe("deserialize", function () { 205 | it("checks empty/unknown object", function () { 206 | let obj = BSON.deserialize(hex2bin("0500000000")); 207 | expect(obj).to.deep.equal({ }); 208 | }); 209 | 210 | it("checks int32", function () { 211 | let obj = BSON.deserialize(hex2bin("0e00000010696e74003412000000")); 212 | expect(obj).to.deep.equal({ int: 0x1234 }); 213 | }); 214 | 215 | it("checks negative int32", function () { 216 | let obj = BSON.deserialize(hex2bin("0e00000010696e7400f6ffffff00")); 217 | expect(obj).to.deep.equal({ int: -10 }); 218 | }); 219 | 220 | it("checks int64", function () { 221 | let obj = BSON.deserialize(hex2bin("1200000012696e7400907856341200000000")); 222 | expect(obj).to.deep.equal({ int: 0x1234567890 }); 223 | }); 224 | 225 | it("checks int64 > 2^53", function () { 226 | let obj = BSON.deserialize(hex2bin("1200000012696e7400FFDEBC9A7856341200")); 227 | expect(obj).to.deep.equal({ int: 0x123456789ABCDEFF }); 228 | }); 229 | 230 | it("checks negative int64", function () { 231 | let obj = BSON.deserialize(hex2bin("1200000012696e74007087a9cbedffffff00")); 232 | expect(obj).to.deep.equal({ int: -78187493520 }); 233 | }); 234 | 235 | it("checks double (64-bit binary floating point)", function () { 236 | let obj = BSON.deserialize(hex2bin("1200000001666c6f0044174154fb21094000")); 237 | expect(obj).to.deep.equal({ flo: 3.1415926535 }); 238 | }); 239 | 240 | it("checks string", function () { 241 | let obj = BSON.deserialize(hex2bin("1a00000002737472000c00000048656c6c6f20576f726c640000")); 242 | expect(obj).to.deep.equal({ str: "Hello World" }); 243 | }); 244 | 245 | it("checks UTF-8 string", function () { 246 | let obj = BSON.deserialize(hex2bin("17000000027374720009000000c384c396c39cc39f0000")); 247 | expect(obj).to.deep.equal({ str: "\u00C4\u00D6\u00DC\u00DF" }); 248 | }); 249 | 250 | it("checks boolean", function () { 251 | let obj = BSON.deserialize(hex2bin("0c00000008626f6f6c000000")); 252 | expect(obj).to.deep.equal({ bool: false }); 253 | obj = BSON.deserialize(hex2bin("0c00000008626f6f6c000100")); 254 | expect(obj).to.deep.equal({ bool: true }); 255 | }); 256 | 257 | it("checks null", function () { 258 | let obj = BSON.deserialize(hex2bin("0a0000000a6e756c0000")); 259 | expect(obj).to.deep.equal({ nul: null }); 260 | }); 261 | 262 | it("checks binary", function () { 263 | let obj = BSON.deserialize(hex2bin("190000000562696e000a00000000010203040506070809ff00")); 264 | expect(obj).to.deep.equal({ bin: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0xFF]) }); 265 | }); 266 | 267 | it("checks array", function () { 268 | let obj = BSON.deserialize(hex2bin("2b000000046172720021000000103000fa000000103100fb000000103200fc000000103300fd0000000000")); 269 | expect(obj).to.deep.equal({ arr: [0xFA, 0xFB, 0xFC, 0xFD] }); 270 | }); 271 | 272 | it("checks array in array", function () { 273 | let obj = BSON.deserialize(hex2bin("4f000000046172720045000000043000210000001030001000000010310011000000103200120000001033001300000000103100fa000000103200fb000000103300fc000000103400fd0000000000")); 274 | expect(obj).to.deep.equal({ arr: [[0x10, 0x11, 0x12, 0x13], 0xFA, 0xFB, 0xFC, 0xFD] }); 275 | }); 276 | 277 | it("checks object", function () { 278 | let obj = BSON.deserialize(hex2bin("22000000036f626a001800000010696e74000a000000027374720001000000000000")); 279 | expect(obj).to.deep.equal({ obj: { int: 10, str: "" } }); 280 | }); 281 | 282 | it("checks Date", function () { 283 | let obj = BSON.deserialize(hex2bin("120000000964617400f84308885501000000")); 284 | expect(obj).to.deep.equal({ dat: new Date("2016-06-25T14:48:11Z") }); 285 | }); 286 | 287 | it("checks UTC", function () { 288 | let obj = BSON.deserialize(hex2bin("2f000000097574633100f843088855010000097574633200f8669a87550100000975746333003d53ae915501000000"), true); 289 | expect(obj).to.deep.equal({ utc1: new BSON.UTC("2016-06-25T14:48:11Z"), utc2: new BSON.UTC("2016-06-25T14:48:11+0200"), utc3: new BSON.UTC([0x3D, 0x53, 0xAE, 0x91, 0x55, 0x01, 0x00, 0x00]) }); 290 | }); 291 | 292 | it("checks UUID", function () { 293 | let obj = BSON.deserialize(hex2bin("20000000057575696400100000000443ab2e98623c03e85f541a1745e01bda00")); 294 | expect(obj).to.deep.equal({ uuid: new BSON.UUID(new Uint8Array([0x43, 0xab, 0x2e, 0x98, 0x62, 0x3c, 0x03, 0xe8, 0x5f, 0x54, 0x1a, 0x17, 0x45, 0xe0, 0x1b, 0xda])) }); 295 | obj = BSON.deserialize(hex2bin("2100000005757569640011000000040143ab2e98623c03e85f541a1745e01bda00")); 296 | expect(obj).to.equal(undefined); 297 | }); 298 | 299 | it("checks ObjectId", function () { 300 | let obj = BSON.deserialize(hex2bin("16000000076f696400a80557f05c6d7ad09fa7357000")); 301 | expect(obj).to.deep.equal({ oid: new BSON.ObjectId([0xa8, 0x05, 0x57, 0xf0, 0x5c, 0x6d, 0x7a, 0xd0, 0x9f, 0xa7, 0x35, 0x70]) }); 302 | }); 303 | 304 | it("checks complex objects", function () { 305 | for (let i = 0; i < deserialize_vector.length; i++) { 306 | let bson = BSON.serialize(deserialize_vector[i].obj); 307 | let obj = BSON.deserialize(hex2bin(deserialize_vector[i].bson), true); 308 | expect(obj).to.deep.equal(deserialize_vector[i].obj); 309 | } 310 | }); 311 | 312 | it("checks document too small", function () { 313 | let obj = BSON.deserialize(hex2bin("04000000")); 314 | expect(obj).to.equal(undefined); 315 | }); 316 | 317 | it("checks document termination", function () { 318 | let obj = BSON.deserialize(hex2bin("0c00000008626f6f6c000001")); 319 | expect(obj).to.equal(undefined); 320 | obj = BSON.deserialize(hex2bin("0c00000008626f6f6c0000")); 321 | expect(obj).to.equal(undefined); 322 | }); 323 | 324 | it("checks document size mismatch", function () { 325 | let obj = BSON.deserialize(hex2bin("0d00000008626f6f6c000000")); 326 | expect(obj).to.equal(undefined); 327 | }); 328 | 329 | it("checks illegal keyname", function () { 330 | let obj = BSON.deserialize(hex2bin("0c00000008626f6f6c010100")); 331 | expect(obj).to.equal(undefined); 332 | }); 333 | 334 | it("checks unknown element", function () { 335 | let obj = BSON.deserialize(hex2bin("0c00000018626f6f6c000000")); 336 | expect(obj).to.equal(undefined); 337 | }); 338 | }); 339 | 340 | ///////////////////////////////////////////////////////////////////////////// 341 | 342 | function bin2hex(bin: Uint8Array, uppercase: boolean = false): string { 343 | let hex = uppercase ? "0123456789ABCDEF" : "0123456789abcdef"; 344 | let str = ""; 345 | for (let i = 0, len = bin.length; i < len; i++) { 346 | str += hex.charAt((bin[i] >>> 4) & 0x0f) + hex.charAt(bin[i] & 0x0f); 347 | } 348 | return str; 349 | } 350 | 351 | function hex2bin(hex: string): Uint8Array { 352 | let bin = new Uint8Array(hex.length >>> 1); 353 | for (let i = 0, len = hex.length >>> 1; i < len; i++) { 354 | bin[i] = parseInt(hex.substr(i << 1, 2), 16); 355 | } 356 | return bin; 357 | } 358 | 359 | }); 360 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "outDir": "./out", 9 | "rootDir": "../" 10 | }, 11 | "exclude": [ 12 | "dist", 13 | "node_modules" 14 | ], 15 | "filesGlob": [ 16 | "**/*.ts", 17 | "!node_modules/**/*" 18 | ], 19 | "compileOnSave": false, 20 | "atom": { 21 | "rewriteTsconfig": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "comment-format": [ 5 | true, 6 | "check-space" 7 | ], 8 | "indent": [ 9 | true, 10 | "spaces" 11 | ], 12 | "no-duplicate-variable": true, 13 | "no-eval": true, 14 | "no-internal-module": true, 15 | "no-trailing-whitespace": true, 16 | "no-var-keyword": false, 17 | "one-line": [ 18 | true, 19 | "check-open-brace", 20 | "check-whitespace" 21 | ], 22 | "quotemark": [ 23 | true, 24 | "single" 25 | ], 26 | "semicolon": [ 27 | true, 28 | "always" 29 | ], 30 | "triple-equals": [ 31 | true, 32 | "allow-null-check" 33 | ], 34 | "typedef-whitespace": [ 35 | true, 36 | { 37 | "call-signature": "nospace", 38 | "index-signature": "nospace", 39 | "parameter": "nospace", 40 | "property-declaration": "nospace", 41 | "variable-declaration": "nospace" 42 | } 43 | ], 44 | "variable-name": [ 45 | true, 46 | "allow-pascal-case", 47 | "ban-keywords" 48 | ], 49 | "whitespace": [ 50 | true, 51 | "check-branch", 52 | "check-decl", 53 | "check-operator", 54 | "check-separator", 55 | "check-type" 56 | ] 57 | } 58 | } --------------------------------------------------------------------------------