Browse Source

Update BSON parser

Lutz Roeder 5 years ago
parent
commit
affb3140fc
4 changed files with 166 additions and 179 deletions
  1. 1 1
      setup.py
  2. 0 157
      source/bson.js
  3. 18 19
      source/flux.js
  4. 147 2
      source/json.js

+ 1 - 1
setup.py

@@ -91,7 +91,7 @@ setuptools.setup(
         'netron': [ 
             'favicon.ico', 'icon.png',
             'base.js', 'json.js', 'protobuf.js', 'flatbuffers.js',
-            'numpy.js', 'pickle.js', 'hdf5.js', 'bson.js',
+            'numpy.js', 'pickle.js', 'hdf5.js',
             'zip.js', 'tar.js', 'gzip.js',
             'armnn.js', 'armnn-metadata.json', 'armnn-schema.js',
             'bigdl.js', 'bigdl-metadata.json', 'bigdl-proto.js',

+ 0 - 157
source/bson.js

@@ -1,157 +0,0 @@
-/* jshint esversion: 6 */
-
-// Experimental BSON JavaScript reader
-
-var bson = {};
-
-// http://bsonspec.org/spec.html
-bson.Reader = class {
-
-    constructor(buffer) {
-        this._asciiDecoder = new TextDecoder('ascii');
-        this._utf8Decoder = new TextDecoder('utf-8');
-        this._buffer = buffer;
-        this._position = 0;
-        this._view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
-    }
-
-    read() {
-        return this.document();
-    }
-
-    document(isArray) {
-        const start = this._position;
-        const size = this.int32();
-        if (size < 5 || start + size > this._buffer.length || this._buffer[start + size - 1] != 0x00) {
-            throw new bson.Reader('Invalid BSON size.');
-        }
-        const element = isArray ? [] : {};
-        let index = 0;
-        for (;;) {
-            const type = this.byte();
-            if (type == 0x00) {
-                break;
-            }
-            const key = this.cstring();
-            let value = null;
-            switch (type) {
-                case 0x01:
-                    value = this.double();
-                    break;
-                case 0x02:
-                    value = this.string();
-                    break;
-                case 0x03:
-                    value = this.document(false);
-                    break;
-                case 0x04:
-                    value = this.document(true);
-                    break;
-                case 0x05:
-                    value = this.binary();
-                    break;
-                case 0x08:
-                    value = this.boolean();
-                    break;
-                case 0x0A:
-                    value = null;
-                    break;
-                case 0x10:
-                    value = this.int32();
-                    break;
-                case 0x11:
-                    value = this.uint64();
-                    break;
-                case 0x12:
-                    value = this.int64();
-                    break;
-                default:
-                    throw new bson.Error("Unknown value type '" + type + "'.");
-            }
-            if (isArray)  {
-                if (index !== parseInt(key, 10)) {
-                    throw new bson.Error("Invalid array index '" + key + "'.");
-                }
-                element.push(value);
-                index++;
-            }
-            else {
-                element[key] = value;
-            }
-        }
-        return element;
-    }
-
-    cstring() {
-        const end = this._buffer.indexOf(0x00, this._position);
-        const value = this._asciiDecoder.decode(this._buffer.subarray(this._position, end));
-        this._position = end + 1;
-        return value;
-    }
-
-    string() {
-        const end = this.int32() + this._position - 1;
-        const value = this._utf8Decoder.decode(this._buffer.subarray(this._position, end));
-        this._position = end;
-        if (this.byte() != '0x00') {
-            throw new bson.Error('String missing terminal 0.');
-        }
-        return value;
-    }
-
-    binary() {
-        const size = this.int32();
-        const subtype = this.byte();
-        const data = this._buffer.subarray(this._position, this._position + size);
-        this._position += size;
-        switch (subtype) {
-            case 0x00:
-                return data;
-            default:
-                throw new bson.Error("Unknown binary subtype '" + subtype + "'.");
-        }
-    }
-
-    boolean()  {
-        const value = this.byte();
-        switch (value) {
-            case 0x00: return false;
-            case 0x01: return true;
-            default: throw new bson.Error("Invalid boolean value '" + value + "'.");
-        }
-    }
-
-    byte() {
-        return this._buffer[this._position++];
-    }
-
-    int32() {
-        const value = this._view.getInt32(this._position, true);
-        this._position += 4;
-        return value;
-    }
-
-    int64() {
-        const value = this._view.getInt64(this._position, true).toNumber();
-        this._position += 8;
-        return value;
-    }
-
-    uint64() {
-        const value = this._view.getUint64(this._position, true).toNumber();
-        this._position += 8;
-        return value;
-    }
-};
-
-bson.Error = class extends Error {
-
-    constructor(message) {
-        super(message);
-        this.name = 'BSON Error';
-    }
-};
-
-if (typeof module !== 'undefined' && typeof module.exports === 'object') {
-    module.exports.Reader = bson.Reader;
-}

+ 18 - 19
source/flux.js

@@ -3,6 +3,7 @@
 // Experimental
 
 var flux = flux || {};
+var json = json || require('./json');
 
 flux.ModelFactory = class {
 
@@ -16,31 +17,29 @@ flux.ModelFactory = class {
     }
 
     open(context, host) {
-        return host.require('./bson').then((bson) => {
-            let model = null;
-            const identifier = context.identifier;
+        let model = null;
+        const identifier = context.identifier;
+        try {
+            const reader = json.BinaryReader.create(context.buffer);
+            const root = reader.read();
+            const obj = flux.ModelFactory._backref(root, root);
+            model = obj.model;
+            if (!model) {
+                throw new flux.Error('File does not contain Flux model.');
+            }
+        }
+        catch (error) {
+            const message = error && error.message ? error.message : error.toString();
+            throw new flux.Error(message.replace(/\.$/, '') + " in '" + identifier + "'.");
+        }
+        return flux.Metadata.open(host).then((metadata) => {
             try {
-                const reader = new bson.Reader(context.buffer);
-                const root = reader.read();
-                const obj = flux.ModelFactory._backref(root, root);
-                model = obj.model;
-                if (!model) {
-                    throw new flux.Error('File does not contain Flux model.');
-                }
+                return new flux.Model(metadata, model);
             }
             catch (error) {
                 const message = error && error.message ? error.message : error.toString();
                 throw new flux.Error(message.replace(/\.$/, '') + " in '" + identifier + "'.");
             }
-            return flux.Metadata.open(host).then((metadata) => {
-                try {
-                    return new flux.Model(metadata, model);
-                }
-                catch (error) {
-                    const message = error && error.message ? error.message : error.toString();
-                    throw new flux.Error(message.replace(/\.$/, '') + " in '" + identifier + "'.");
-                }
-            });
         });
     }
 

+ 147 - 2
source/json.js

@@ -516,15 +516,160 @@ json.TextDecoder.Utf16BE = class {
     }
 };
 
+json.BinaryReader = class {
+
+    constructor(buffer) {
+        this._buffer = buffer;
+    }
+
+    static create(buffer) {
+        return new json.BinaryReader(buffer);
+    }
+
+    read() {
+        const buffer = this._buffer;
+        const length = buffer.length;
+        const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
+        const asciiDecoder = new TextDecoder('ascii');
+        const utf8Decoder = new TextDecoder('utf-8');
+        let position = 0;
+        const skip = (offset) => {
+            position += offset;
+            if (position > length) {
+                throw new json.Error('Expected ' + (position + length) + ' more bytes. The file might be corrupted. Unexpected end of file.', true);
+            }
+        };
+        const header = () => {
+            const start = position;
+            skip(4);
+            const size = view.getInt32(start, 4);
+            if (size < 5 || start + size > length || buffer[start + size - 1] != 0x00) {
+                throw new json.Error('Invalid file size.', true);
+            }
+        };
+        header();
+        const stack = [];
+        let obj = {};
+        for (;;) {
+            skip(1);
+            const type = buffer[position - 1];
+            if (type == 0x00) {
+                if (stack.length === 0) {
+                    break;
+                }
+                obj = stack.pop();
+                continue;
+            }
+
+            const start = position;
+            position = buffer.indexOf(0x00, start) + 1;
+            const key = asciiDecoder.decode(buffer.subarray(start, position - 1));
+
+            let value = null;
+            switch (type) {
+                case 0x01: { // float64
+                    const start = position;
+                    skip(8);
+                    value = view.getFloat64(start, true);
+                    break;
+                }
+                case 0x02: { // string
+                    skip(4);
+                    const size = view.getInt32(position - 4, true);
+                    const start = position;
+                    skip(size);
+                    value = utf8Decoder.decode(buffer.subarray(start, position - 1));
+                    if (buffer[position - 1] != '0x00') {
+                        throw new json.Error('String missing terminal 0.', true);
+                    }
+                    break;
+                }
+                case 0x03: { // object
+                    header();
+                    value = {};
+                    break;
+                }
+                case 0x04: { // array
+                    header();
+                    value = [];
+                    break;
+                }
+                case 0x05: { // bytes
+                    const start = position;
+                    skip(5);
+                    const size = view.getInt32(start, true);
+                    const subtype = buffer[start + 4];
+                    if (subtype !== 0x00) {
+                        throw new json.Error("Unknown binary subtype '" + subtype + "'.", true);
+                    }
+                    skip(size);
+                    value = buffer.subarray(start + 5, position);
+                    break;
+                }
+                case 0x08: { // boolean
+                    skip(1);
+                    value = buffer[position - 1];
+                    if (value > 1) {
+                        throw new json.Error("Invalid boolean value '" + value + "'.", true);
+                    }
+                    value = value === 1 ? true : false;
+                    break;
+                }
+                case 0x0A:
+                    value = null;
+                    break;
+                case 0x10: {
+                    const start = position;
+                    skip(4);
+                    value = view.getInt32(start, true);
+                    break;
+                }
+                case 0x11: { // uint64
+                    const start = position;
+                    skip(8);
+                    value = view.getUint64(start, true).toNumber();
+                    break;
+                }
+                case 0x12: { // int64
+                    const start = position;
+                    skip(8);
+                    value = view.getInt64(start, true).toNumber();
+                    break;
+                }
+                default:
+                    throw new json.Error("Unknown value type '" + type + "'.", true);
+            }
+            if (Array.isArray(obj))  {
+                if (obj.length !== parseInt(key, 10)) {
+                    throw new json.Error("Invalid array index '" + key + "'.", true);
+                }
+                obj.push(value);
+            }
+            else {
+                obj[key] = value;
+            }
+            if (type === 0x03 || type === 0x04) {
+                stack.push(obj);
+                obj = value;
+            }
+        }
+        if (position !== length) {
+            throw new json.Error("Unexpected data at '" + position.toString() + "'.", true);
+        }
+        return obj;
+    }
+};
+
 json.Error = class extends Error {
 
-    constructor(message) {
+    constructor(message, binary) {
         super(message);
-        this.name = 'JSON Error';
+        this.name = binary ? 'BSON Error' : 'JSON Error';
     }
 };
 
 if (typeof module !== 'undefined' && typeof module.exports === 'object') {
     module.exports.TextReader = json.TextReader;
     module.exports.TextDecoder = json.TextDecoder;
+    module.exports.BinaryReader = json.BinaryReader;
 }