From b06486b0e299a6c04bafca349295601f0bb20f4b Mon Sep 17 00:00:00 2001 From: Matt Vollrath Date: Tue, 30 Oct 2018 05:39:31 -0400 Subject: [PATCH 1/3] Add support for CBOR encoding --- package.json | 1 + src/core/Ros.js | 4 +- src/core/SocketAdapter.js | 5 ++ src/core/Topic.js | 4 +- src/util/cborTypedArrayTags.js | 113 ++++++++++++++++++++++++++++++++ test/cbor.test.js | 115 +++++++++++++++++++++++++++++++++ test/karma.conf.js | 2 + test/require-shim.js | 4 +- 8 files changed, 244 insertions(+), 4 deletions(-) create mode 100644 src/util/cborTypedArrayTags.js create mode 100644 test/cbor.test.js diff --git a/package.json b/package.json index bca1fa7f9..d68342ced 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "pngparse": "^2.0.1", "ws": "^1.1.1", "xmldom": "^0.1.19", + "cbor-js": "^0.1.0", "socket.io": "1.4.8" }, "directories": { diff --git a/src/core/Ros.js b/src/core/Ros.js index dc820a25c..1d3dcf85a 100644 --- a/src/core/Ros.js +++ b/src/core/Ros.js @@ -70,7 +70,9 @@ Ros.prototype.connect = function(url) { } else if (this.transportLibrary.constructor.name === 'RTCPeerConnection') { this.socket = assign(this.transportLibrary.createDataChannel(url, this.transportOptions), socketAdapter(this)); }else { - this.socket = assign(new WebSocket(url), socketAdapter(this)); + var sock = new WebSocket(url); + sock.binaryType = 'arraybuffer'; + this.socket = assign(sock, socketAdapter(this)); } }; diff --git a/src/core/SocketAdapter.js b/src/core/SocketAdapter.js index ad97239ff..c93283c5d 100644 --- a/src/core/SocketAdapter.js +++ b/src/core/SocketAdapter.js @@ -9,6 +9,8 @@ 'use strict'; var decompressPng = require('../util/decompressPng'); +var CBOR = require('cbor-js'); +var typedArrayTagger = require('../util/cborTypedArrayTags'); var WebSocket = require('ws'); var BSON = null; if(typeof bson !== 'undefined'){ @@ -106,6 +108,9 @@ function SocketAdapter(client) { decodeBSON(data.data, function (message) { handlePng(message, handleMessage); }); + } else if (data.data instanceof ArrayBuffer) { + var decoded = CBOR.decode(data.data, typedArrayTagger); + handleMessage(decoded); } else { var message = JSON.parse(typeof data === 'string' ? data : data.data); handlePng(message, handleMessage); diff --git a/src/core/Topic.js b/src/core/Topic.js index f5617e98b..b91500357 100644 --- a/src/core/Topic.js +++ b/src/core/Topic.js @@ -18,7 +18,7 @@ var Message = require('./Message'); * * ros - the ROSLIB.Ros connection handle * * name - the topic name, like /cmd_vel * * messageType - the message type, like 'std_msgs/String' - * * compression - the type of compression to use, like 'png' + * * compression - the type of compression to use, like 'png' or 'cbor' * * throttle_rate - the rate (in ms in between messages) at which to throttle the topics * * queue_size - the queue created at bridge side for re-publishing webtopics (defaults to 100) * * latch - latch the topic when publishing @@ -40,7 +40,7 @@ function Topic(options) { // Check for valid compression types if (this.compression && this.compression !== 'png' && - this.compression !== 'none') { + this.compression !== 'cbor' && this.compression !== 'none') { this.emit('warning', this.compression + ' compression is not supported. No compression will be used.'); } diff --git a/src/util/cborTypedArrayTags.js b/src/util/cborTypedArrayTags.js new file mode 100644 index 000000000..07547a27b --- /dev/null +++ b/src/util/cborTypedArrayTags.js @@ -0,0 +1,113 @@ +var UPPER32 = Math.pow(2, 32); + +var warnedPrecision = false; +function warnPrecision() { + if (!warnedPrecision) { + warnedPrecision = true; + console.warn('CBOR 64-bit integer array values may lose precision. No further warnings.'); + } +} + +/** + * Unpacks 64-bit unsigned integer from byte array. + * @param {Uint8Array} bytes +*/ +function decodeUint64LE(bytes) { + warnPrecision(); + + var byteLen = bytes.byteLength; + var arrLen = byteLen / 8; + + var buffer = bytes.buffer.slice(-byteLen); + var uint32View = new Uint32Array(buffer); + + var arr = new Array(arrLen); + for (var i = 0; i < arrLen; i++) { + var si = i * 2; + var lo = uint32View[si]; + var hi = uint32View[si+1]; + arr[i] = lo + UPPER32 * hi; + } + + return arr; +} + +/** + * Unpacks 64-bit signed integer from byte array. + * @param {Uint8Array} bytes +*/ +function decodeInt64LE(bytes) { + warnPrecision(); + + var byteLen = bytes.byteLength; + var arrLen = byteLen / 8; + + var buffer = bytes.buffer.slice(-byteLen); + var uint32View = new Uint32Array(buffer); + var int32View = new Int32Array(buffer); + + var arr = new Array(arrLen); + for (var i = 0; i < arrLen; i++) { + var si = i * 2; + var lo = uint32View[si]; + var hi = int32View[si+1]; + arr[i] = lo + UPPER32 * hi; + } + + return arr; +} + +/** + * Unpacks typed array from byte array. + * @param {Uint8Array} bytes + * @param {type} ArrayType - desired output array type +*/ +function decodeNativeArray(bytes, ArrayType) { + var byteLen = bytes.byteLength; + var buffer = bytes.buffer.slice(-byteLen); + return new ArrayType(buffer); +} + +/** + * Support a subset of draft CBOR typed array tags: + * + * Only support little-endian tags for now. + */ +var nativeArrayTypes = { + 64: Uint8Array, + 69: Uint16Array, + 70: Uint32Array, + 72: Int8Array, + 77: Int16Array, + 78: Int32Array, + 85: Float32Array, + 86: Float64Array +}; + +/** + * We can also decode 64-bit integer arrays, since ROS has these types. + */ +var conversionArrayTypes = { + 71: decodeUint64LE, + 79: decodeInt64LE +}; + +/** + * Handles CBOR typed array tags during decoding. + * @param {Uint8Array} data + * @param {Number} tag + */ +function cborTypedArrayTagger(data, tag) { + if (tag in nativeArrayTypes) { + var arrayType = nativeArrayTypes[tag]; + return decodeNativeArray(data, arrayType); + } + if (tag in conversionArrayTypes) { + return conversionArrayTypes[tag](data); + } + return data; +} + +if (typeof module !== 'undefined' && module.exports) { + module.exports = cborTypedArrayTagger; +} diff --git a/test/cbor.test.js b/test/cbor.test.js new file mode 100644 index 000000000..bbcc7d3a8 --- /dev/null +++ b/test/cbor.test.js @@ -0,0 +1,115 @@ +var expect = require('chai').expect; +var CBOR = require('cbor-js'); +var cborTypedArrayTagger = require('../src/util/cborTypedArrayTags.js'); + +/** Convert hex string to ArrayBuffer. */ +function hexToBuffer(hex) { + var tokens = hex.match(/[0-9a-fA-F]{2}/gi); + var arr = tokens.map(function(t) { + return parseInt(t, 16); + }); + return new Uint8Array(arr).buffer; +} + + +describe('CBOR Typed Array Tagger', function() { + + it('should convert tagged Uint16Array', function() { + var data = hexToBuffer('d84546010002000300'); + var msg = CBOR.decode(data, cborTypedArrayTagger); + + expect(msg).to.be.a('Uint16Array'); + expect(msg).to.have.lengthOf(3); + expect(msg[0]).to.equal(1); + expect(msg[1]).to.equal(2); + expect(msg[2]).to.equal(3); + }); + + it('should convert tagged Uint32Array', function() { + var data = hexToBuffer('d8464c010000000200000003000000'); + var msg = CBOR.decode(data, cborTypedArrayTagger); + + expect(msg).to.be.a('Uint32Array'); + expect(msg).to.have.lengthOf(3); + expect(msg[0]).to.equal(1); + expect(msg[1]).to.equal(2); + expect(msg[2]).to.equal(3); + }); + + it('should convert tagged Uint64Array', function() { + var data = hexToBuffer('d8475818010000000000000002000000000000000300000000000000'); + var msg = CBOR.decode(data, cborTypedArrayTagger); + + expect(msg).to.be.a('Array'); + expect(msg).to.have.lengthOf(3); + expect(msg[0]).to.equal(1); + expect(msg[1]).to.equal(2); + expect(msg[2]).to.equal(3); + }); + + it('should convert tagged Int8Array', function() { + var data = hexToBuffer('d8484301fe03'); + var msg = CBOR.decode(data, cborTypedArrayTagger); + + expect(msg).to.be.a('Int8Array'); + expect(msg).to.have.lengthOf(3); + expect(msg[0]).to.equal(1); + expect(msg[1]).to.equal(-2); + expect(msg[2]).to.equal(3); + }); + + it('should convert tagged Int16Array', function() { + var data = hexToBuffer('d84d460100feff0300'); + var msg = CBOR.decode(data, cborTypedArrayTagger); + + expect(msg).to.be.a('Int16Array'); + expect(msg).to.have.lengthOf(3); + expect(msg[0]).to.equal(1); + expect(msg[1]).to.equal(-2); + expect(msg[2]).to.equal(3); + }); + + it('should convert tagged Int32Array', function() { + var data = hexToBuffer('d84e4c01000000feffffff03000000'); + var msg = CBOR.decode(data, cborTypedArrayTagger); + + expect(msg).to.be.a('Int32Array'); + expect(msg).to.have.lengthOf(3); + expect(msg[0]).to.equal(1); + expect(msg[1]).to.equal(-2); + expect(msg[2]).to.equal(3); + }); + + it('should convert tagged Int64Array', function() { + var data = hexToBuffer('d84f58180100000000000000feffffffffffffff0300000000000000'); + var msg = CBOR.decode(data, cborTypedArrayTagger); + + expect(msg).to.be.a('Array'); + expect(msg).to.have.lengthOf(3); + expect(msg[0]).to.equal(1); + expect(msg[1]).to.equal(-2); + expect(msg[2]).to.equal(3); + }); + + it('should convert tagged Float32Array', function() { + var data = hexToBuffer('d8554ccdcc8c3fcdcc0cc033335340'); + var msg = CBOR.decode(data, cborTypedArrayTagger); + + expect(msg).to.be.a('Float32Array'); + expect(msg).to.have.lengthOf(3); + expect(msg[0]).to.closeTo(1.1, 1e-5); + expect(msg[1]).to.closeTo(-2.2, 1e-5); + expect(msg[2]).to.closeTo(3.3, 1e-5); + }); + + it('should convert tagged Float64Array', function() { + var data = hexToBuffer('d85658189a9999999999f13f9a999999999901c06666666666660a40'); + var msg = CBOR.decode(data, cborTypedArrayTagger); + + expect(msg).to.be.a('Float64Array'); + expect(msg).to.have.lengthOf(3); + expect(msg[0]).to.closeTo(1.1, 1e-5); + expect(msg[1]).to.closeTo(-2.2, 1e-5); + expect(msg[2]).to.closeTo(3.3, 1e-5); + }); +}); diff --git a/test/karma.conf.js b/test/karma.conf.js index c94a6dbe9..6865c7e0e 100644 --- a/test/karma.conf.js +++ b/test/karma.conf.js @@ -12,6 +12,8 @@ module.exports = function(config) { // List of files / patterns to load in the browser files: [ '../node_modules/eventemitter2/lib/eventemitter2.js', + '../node_modules/cbor-js/cbor.js', + '../src/util/cborTypedArrayTags.js', '../build/roslib.js', './require-shim.js', '*.test.js' diff --git a/test/require-shim.js b/test/require-shim.js index 74d790367..ca34053f1 100644 --- a/test/require-shim.js +++ b/test/require-shim.js @@ -1,7 +1,9 @@ window.require = function require(path) { switch (path) { case 'eventemitter2': return EventEmitter2; - case 'xmldom': return {DOMParser: DOMParser} + case 'xmldom': return {DOMParser: DOMParser}; + case 'cbor-js': return CBOR; + case '../src/util/cborTypedArrayTags.js': return cborTypedArrayTagger; } var lastIdx = path.lastIndexOf('/'), path = lastIdx >= 0 ? path.slice(lastIdx + 1) : path; From 8d05fd3250afd9a0593c51eb127554ab7ac7097e Mon Sep 17 00:00:00 2001 From: Matt Vollrath Date: Tue, 6 Nov 2018 14:40:32 -0500 Subject: [PATCH 2/3] Touch up CBOR test assertions --- test/cbor.test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/cbor.test.js b/test/cbor.test.js index bbcc7d3a8..12e2a7dc1 100644 --- a/test/cbor.test.js +++ b/test/cbor.test.js @@ -97,9 +97,9 @@ describe('CBOR Typed Array Tagger', function() { expect(msg).to.be.a('Float32Array'); expect(msg).to.have.lengthOf(3); - expect(msg[0]).to.closeTo(1.1, 1e-5); - expect(msg[1]).to.closeTo(-2.2, 1e-5); - expect(msg[2]).to.closeTo(3.3, 1e-5); + expect(msg[0]).to.be.closeTo(1.1, 1e-5); + expect(msg[1]).to.be.closeTo(-2.2, 1e-5); + expect(msg[2]).to.be.closeTo(3.3, 1e-5); }); it('should convert tagged Float64Array', function() { @@ -108,8 +108,8 @@ describe('CBOR Typed Array Tagger', function() { expect(msg).to.be.a('Float64Array'); expect(msg).to.have.lengthOf(3); - expect(msg[0]).to.closeTo(1.1, 1e-5); - expect(msg[1]).to.closeTo(-2.2, 1e-5); - expect(msg[2]).to.closeTo(3.3, 1e-5); + expect(msg[0]).to.be.closeTo(1.1, 1e-5); + expect(msg[1]).to.be.closeTo(-2.2, 1e-5); + expect(msg[2]).to.be.closeTo(3.3, 1e-5); }); }); From 6678c0e75f960108edc14ddb322fc0e7be8a00f9 Mon Sep 17 00:00:00 2001 From: Matt Vollrath Date: Fri, 9 Nov 2018 09:17:34 -0500 Subject: [PATCH 3/3] Use strict in cborTypedArrayTags.js --- src/util/cborTypedArrayTags.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util/cborTypedArrayTags.js b/src/util/cborTypedArrayTags.js index 07547a27b..d1073dded 100644 --- a/src/util/cborTypedArrayTags.js +++ b/src/util/cborTypedArrayTags.js @@ -1,3 +1,5 @@ +'use strict'; + var UPPER32 = Math.pow(2, 32); var warnedPrecision = false;