diff --git a/lib/js/index.html b/lib/js/index.html index 0f15e96e5a20fb915b2465576944b7ef6d30fda3..89acdf300a83041136f4f371d4604334135d784e 100644 --- a/lib/js/index.html +++ b/lib/js/index.html @@ -6,22 +6,35 @@ <script src="lc.js"></script> </head> <body> + <input id="url" type="text" /> + <button id="btn">Get!</button> <!-- page content --> <script> - var oReq = new XMLHttpRequest(); - oReq.open("GET", "http://fair-2014-4.lab1.cs.lth.se:8080/logdata/clamping/experiments/bc4b3d5e-509a-40d1-81ec-1799c2ee167b/data", true); - oReq.responseType = "arraybuffer"; +function decodeFromUrl(url) { + var oReq = new XMLHttpRequest(); + oReq.open("GET", url, true); + oReq.responseType = "arraybuffer"; - oReq.onload = function (oEvent) { - var arrayBuffer = oReq.response; // Note: not oReq.responseText - if (arrayBuffer) { - var buff = new LabComm.Buffer(new DataView(arrayBuffer)); - console.log(buff); - new LabComm.Parser(buff); - } - }; + oReq.onload = function (oEvent) { + var arrayBuffer = oReq.response; + if (arrayBuffer) { + var buff = new LabComm.Buffer(new DataView(arrayBuffer)); + window.decoder = new LabComm.Decoder(function(decl, data) { + console.log(data); + }); + window.decoder.decodePackage(buff); + } + }; + + oReq.send(null); +} + +var urlInp = document.getElementById('url'); +var btn = document.getElementById('btn'); +btn.onclick = function() { + decodeFromUrl(urlInp.value); +}; - oReq.send(null); </script> </body> </html> diff --git a/lib/js/lc.js b/lib/js/lc.js index 8a411a2274d32118c7f511aa1ebbbd18e8e045b6..ceff9ca5e708e947c1e3d6545c35f8e4ea8b24d2 100644 --- a/lib/js/lc.js +++ b/lib/js/lc.js @@ -2,6 +2,12 @@ var LabComm = LabComm || {}; +/** + * The maximum "safe" integer that can be stroed in a double + * @define {number} + */ +var MAX_INT = 0x1fffffffffffff; + /** * Constants used during parsing * @enum {number} @@ -32,8 +38,49 @@ LabComm.Constants = { * @param {number=} offset An initial offset, defaults to 0 */ LabComm.Buffer = function (dataView, offset) { - this.offset = offset || 0; - this.data = dataView; + /** + * The current position in the buffer + * @type {number} + * @private + */ + this.offset_ = offset || 0; + + /** + * The underlying dataview + * @type {DataView} + * @private + */ + this.data_ = dataView; + + /** + * The original offset, used to rewind + * @type {number} + * @private + */ + this.originalOffset_ = this.offset_; +}; + +/** + * Check if there is more data that can be red + * @return {boolean} True if there is more data left + */ +LabComm.Buffer.prototype.hasRemaning = function() { + return this.offset_ < this.data_.byteLength; +}; + +/** + * Returns the remaning number of bytes + * @return {number} The number of bytes left + */ +LabComm.Buffer.prototype.remaning = function() { + return this.data_.byteLength - this.offset_; +}; + +/** + * Sets the position to the beginning of the buffer + */ +LabComm.Buffer.prototype.rewind = function() { + this.offset_ = this.originalOffset_; }; /** @@ -43,11 +90,11 @@ LabComm.Buffer = function (dataView, offset) { LabComm.Buffer.prototype.getVarint = function() { var nextByte, result = 0, bytesRead = 0; do { - nextByte = this.data.getUint8(this.offset+bytesRead); + nextByte = this.data_.getUint8(this.offset_+bytesRead); result += (nextByte & 0x7F) << (7 * bytesRead); bytesRead++; } while (nextByte >= 0x80) - this.offset += bytesRead; + this.offset_ += bytesRead; return result; }; @@ -59,10 +106,10 @@ LabComm.Buffer.prototype.getString = function() { var len = this.getVarint(); var chars = new Uint8Array(len); for(var i = 0; i < len; i++) { - chars[i] = this.data.getUint8(this.offset + i); + chars[i] = this.data_.getUint8(this.offset_ + i); } var str = String.fromCharCode.apply(null, chars); - this.offset += len; + this.offset_ += len; return str; }; @@ -71,7 +118,7 @@ LabComm.Buffer.prototype.getString = function() { * @return {Boolean} A boolean */ LabComm.Buffer.prototype.getBoolean = function() { - return new Boolean(this.data.getUint8(this.offset++)); + return new Boolean(this.data_.getUint8(this.offset_++)); }; /** @@ -79,7 +126,7 @@ LabComm.Buffer.prototype.getBoolean = function() { * @return {number} A byte */ LabComm.Buffer.prototype.getByte = function() { - return this.data.getUint8(this.offset++); + return this.data_.getUint8(this.offset_++); }; /** @@ -87,8 +134,8 @@ LabComm.Buffer.prototype.getByte = function() { * @return {number} A short */ LabComm.Buffer.prototype.getShort = function() { - var s = this.data.getInt16(this.offset); - this.offset += 2; + var s = this.data_.getInt16(this.offset_); + this.offset_ += 2; return s; }; @@ -97,8 +144,8 @@ LabComm.Buffer.prototype.getShort = function() { * @return {number} An integer */ LabComm.Buffer.prototype.getInt = function() { - var s = this.data.getInt32(this.offset); - this.offset += 4; + var s = this.data_.getInt32(this.offset_); + this.offset_ += 4; return s; }; @@ -109,10 +156,15 @@ LabComm.Buffer.prototype.getInt = function() { * @return {number} A long */ LabComm.Buffer.prototype.getLong = function() { - var low = this.data.getInt32(this.offset + 4); - var n = this.data.getInt32(this.offset) * 0x100000000 + low; + var low = this.data_.getInt32(this.offset_ + 4); + var n = this.data_.getInt32(this.offset_) * 0x100000000 + low; if (low < 0) n += 0x100000000; - this.offset += 8; + this.offset_ += 8; + + if(Math.abs(n) > MAX_INT) { + return n < 0 ? -Infinity : Infinity; + } + return n; }; @@ -121,8 +173,8 @@ LabComm.Buffer.prototype.getLong = function() { * @return {number} A float */ LabComm.Buffer.prototype.getFloat = function() { - var s = this.data.getFloat32(this.offset); - this.offset += 4; + var s = this.data_.getFloat32(this.offset_); + this.offset_ += 4; return s; }; @@ -131,8 +183,8 @@ LabComm.Buffer.prototype.getFloat = function() { * @return {number} A double */ LabComm.Buffer.prototype.getDouble = function() { - var s = this.data.getFloat64(this.offset); - this.offset += 8; + var s = this.data_.getFloat64(this.offset_); + this.offset_ += 8; return s; }; @@ -141,106 +193,144 @@ LabComm.Buffer.prototype.getDouble = function() { * @param {number} length The number of bytes to skip */ LabComm.Buffer.prototype.skip = function(length) { - this.offset += len; + this.offset_ += len; }; - - - /** - * A + * A parser that cad decode LabComm binary data * @constructor - * @param {LabComm.Buffer} A buffer to parse from + * @param {function(LabComm.SampleDeclaration, Object)=} callback A callback that will get all the decoded samples */ -LabComm.Parser = function(buffer) { - this.data = buffer; - +LabComm.Decoder = function(callback) { + /** + * The sample declarations that has been registred so far + * @type {Object<number, LabComm.SampleDeclaration>} + * @private + */ + this.declarations_ = {}; + + /** + * The callback that will recieve all samples and declarations + * @type {function(LabComm.SampleDeclaration, Object)} + * @private + */ + this.callback_ = callback || function(a,b) {}; +}; +/** + * A unique id for variable naming inside the decoder functions + * @type {number} + * @private + */ +LabComm.Decoder.prototype.uid_ = 1; - var _uid = 1; - function uid() {return this._uid++}; +/** + * Builds a part of the decoder function + * @param {string} name The name of the current field + * @param {Array<string>} json The final json representation of the fields + * @param {Array<string>} fb The final function body of the decoder + * @param {string} type The string representation of the type + * @param {string} parser The parsing function to be called when decoding current field + * @return {string} The JSON part of the final structure + * @private + */ +LabComm.Decoder.prototype.buildDecoder_ = function(name, json, fb, type, parser) { + var varname = 'v'+this.uid_++; + json.push('"'+name+'":'+varname); + fb.push('var '+varname+' = ' + parser); + return '"'+name+'":'+'"'+type+'"'; +}; - function buildParser(name, json, fb, type, parser) { - var varname = 'v'+_uid++; - json.push('"'+name+'":'+varname); - fb.push('var '+varname+' = ' + parser); - return '"'+name+'":'+'"'+type+'"'; - } - this.sig = function(name, json, fb) { - var type = this.data.getVarint(); - switch(type) { - case LabComm.Constants.ARRAY: - console.log("array"); - //TODO: implement - break; - case LabComm.Constants.STRUCT: - var func = []; - func.push('"'+name+'":{'); - json.push('"'+name+'":{'); - var fields = this.data.getVarint(); - while(fields--) { - var fn = this.data.getString(); - func.push(this.sig(fn, json, fb)); - if(fields!=0) { - json.push(','); - func.push(","); - } +/** + * Decodes a signature recursively + * @param {string} name The name of the current field + * @param {Array<string>} json The final json representation of the fields + * @param {Array<string>} fb The final function body of the decoder + * @return {string} The JSON part of the sub structure + * @throws {string} If decoding could not be performed + * @private + */ +LabComm.Decoder.prototype.decodeSignature_ = function(name, json, fb) { + var type = this.data.getVarint(); + switch(type) { + case LabComm.Constants.ARRAY: + var dim = this.data.getVarint(); + if(dim === 0) { + throw "Array cant have 0 dimensions"; + } + console.log("array"); + //TODO: implement + throw "Array decoding not implemented"; + break; + case LabComm.Constants.STRUCT: + var func = []; + func.push('"'+name+'":{'); + json.push('"'+name+'":{'); + var fields = this.data.getVarint(); + while(fields--) { + var fn = this.data.getString(); + func.push(this.decodeSignature_(fn, json, fb)); + if(fields!=0) { + json.push(','); + func.push(","); } - func.push("}"); - json.push("}"); - return func.join(''); - case LabComm.Constants.BOOLEAN: - return buildParser(name, json, fb, "bool", "data.getBoolean()"); - case LabComm.Constants.BYTE: - return buildParser(name, json, fb, "byte", "data.getByte()"); - case LabComm.Constants.SHORT: - return buildParser(name, json, fb, "short", "data.getShort()"); - case LabComm.Constants.INT: - return buildParser(name, json, fb, "int", "data.getInt()"); - case LabComm.Constants.LONG: - return buildParser(name, json, fb, "long", "data.getLong()"); - case LabComm.Constants.FLOAT: - return buildParser(name, json, fb, "float", "data.getFloat()"); - case LabComm.Constants.DOUBLE: - return buildParser(name, json, fb, "double", "data.getDouble()"); - case LabComm.Constants.STRING: - return buildParser(name, json, fb, "string", "data.getString()"); } + func.push("}"); + json.push("}"); + return func.join(''); + case LabComm.Constants.BOOLEAN: + return this.buildDecoder_(name, json, fb, "bool", "data.getBoolean()"); + case LabComm.Constants.BYTE: + return this.buildDecoder_(name, json, fb, "byte", "data.getByte()"); + case LabComm.Constants.SHORT: + return this.buildDecoder_(name, json, fb, "short", "data.getShort()"); + case LabComm.Constants.INT: + return this.buildDecoder_(name, json, fb, "int", "data.getInt()"); + case LabComm.Constants.LONG: + return this.buildDecoder_(name, json, fb, "long", "data.getLong()"); + case LabComm.Constants.FLOAT: + return this.buildDecoder_(name, json, fb, "float", "data.getFloat()"); + case LabComm.Constants.DOUBLE: + return this.buildDecoder_(name, json, fb, "double", "data.getDouble()"); + case LabComm.Constants.STRING: + return this.buildDecoder_(name, json, fb, "string", "data.getString()"); } + throw "Unknown type found in signature: 0x" + tag.toString(16); +}; - this._defs = {}; - - for(var i = 1; i < 6; i ++) { +/** + * Decodes a buffer to Samples + * @param {LabComm.Buffer} buffer The buffer to decode + * @throws {string} If error during decoding + */ +LabComm.Decoder.prototype.decodePackage = function(buffer) { + this.data = buffer; + while(this.data.hasRemaning()) { var tag = this.data.getVarint(); var len = this.data.getVarint(); - console.log("tag: 0x" + tag.toString(16)); - console.log("len: 0x" + len.toString(16)); switch(tag) { case LabComm.Constants.VERSION: var version = this.data.getString(); - console.log("LabComm version package: " + version); + if(version !== "LabComm2014") { + throw "Unsupported LabComm version: " + version; + } break; case LabComm.Constants.SAMPLE_DEF: var index = this.data.getVarint(); var name = this.data.getString(); var siglen = this.data.getVarint(); - console.log("sample def: 0x" + index.toString(16) + ":" + name + ", size: 0x" + siglen.toString(16)); var json = [], fb = []; - var struct = JSON.parse('{' + this.sig(name, json, fb) + "}"); + var struct = JSON.parse('{' + this.decodeSignature_(name, json, fb) + "}"); var func = fb.join(';\n\t') + ';\n\treturn {' + json.join('') + '}'; - this._defs[index] = { - index: index, - name: name, - struct: struct, - decoder: new Function('data', func) - }; + + this.declarations_[index] = new LabComm.SampleDeclaration(index, name, struct, new Function('data', func)); break; case LabComm.Constants.SAMPLE_REF: case LabComm.Constants.TYPE_DEF: @@ -250,17 +340,56 @@ LabComm.Parser = function(buffer) { console.log("skipping unhandled package of size " + len); break; default: - var s = this._defs[tag]; - - var decoder = s.decoder; - var sample = decoder(this.data); - console.log("sample name: " + s.name); - console.log(decoder); + var decl = this.declarations_[tag]; + var sample = decl.decode(this.data); - console.log(sample); + this.callback_(decl, sample); break; } } -} +}; + +/** + * A Sample declaration + * @constructor + * @param {number} index The samles tag-index + * @param {string} name The name of the sample + * @param {*} struct A JSON representation of the sample + * @param {function(LabComm.Buffer) : Object} func The decoder function + */ +LabComm.SampleDeclaration = function(index, name, struct, func) { + /** + * The tag index + * @type{number} + */ + this.index = index; + + /** + * The name of the sample + * @type {string} + */ + this.name = name; + + /** + * The JSON representation of the sample, can be used to re-create the .lc file + * @type {*} + */ + this.struct = struct; + + /** + * The decoder that turns a buffer into a JSON sample + * @type {function(LabComm.Buffer) : Object} + * @private + */ + this.decoder_ = func; +}; +/** + * Decodes a complete sample and returns a JSON obejct with the data + * @param {LabComm.Buffer} data The buffer that contains the sample + * @return {Object} The JSON object with the data + */ +LabComm.SampleDeclaration.prototype.decode = function(data) { + return this.decoder_(data); +};