Skip to content

Commit 41297f4

Browse files
mvollrathjihoonl
authored andcommitted
Add support for CBOR encoding (#303)
* Add support for CBOR encoding * Touch up CBOR test assertions * Use strict in cborTypedArrayTags.js
1 parent 1052c1f commit 41297f4

File tree

8 files changed

+246
-4
lines changed

8 files changed

+246
-4
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"pngparse": "^2.0.1",
3939
"ws": "^1.1.1",
4040
"xmldom": "^0.1.19",
41+
"cbor-js": "^0.1.0",
4142
"socket.io": "1.4.8"
4243
},
4344
"directories": {

src/core/Ros.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ Ros.prototype.connect = function(url) {
7171
this.socket = assign(this.transportLibrary.createDataChannel(url, this.transportOptions), socketAdapter(this));
7272
} else {
7373
if (!this.socket || this.socket.readyState === WebSocket.CLOSED) {
74-
this.socket = assign(new WebSocket(url), socketAdapter(this));
74+
var sock = new WebSocket(url);
75+
sock.binaryType = 'arraybuffer';
76+
this.socket = assign(sock, socketAdapter(this));
7577
}
7678
}
7779

src/core/SocketAdapter.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
'use strict';
1010

1111
var decompressPng = require('../util/decompressPng');
12+
var CBOR = require('cbor-js');
13+
var typedArrayTagger = require('../util/cborTypedArrayTags');
1214
var WebSocket = require('ws');
1315
var BSON = null;
1416
if(typeof bson !== 'undefined'){
@@ -106,6 +108,9 @@ function SocketAdapter(client) {
106108
decodeBSON(data.data, function (message) {
107109
handlePng(message, handleMessage);
108110
});
111+
} else if (data.data instanceof ArrayBuffer) {
112+
var decoded = CBOR.decode(data.data, typedArrayTagger);
113+
handleMessage(decoded);
109114
} else {
110115
var message = JSON.parse(typeof data === 'string' ? data : data.data);
111116
handlePng(message, handleMessage);

src/core/Topic.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ var Message = require('./Message');
1818
* * ros - the ROSLIB.Ros connection handle
1919
* * name - the topic name, like /cmd_vel
2020
* * messageType - the message type, like 'std_msgs/String'
21-
* * compression - the type of compression to use, like 'png'
21+
* * compression - the type of compression to use, like 'png' or 'cbor'
2222
* * throttle_rate - the rate (in ms in between messages) at which to throttle the topics
2323
* * queue_size - the queue created at bridge side for re-publishing webtopics (defaults to 100)
2424
* * latch - latch the topic when publishing
@@ -40,7 +40,7 @@ function Topic(options) {
4040

4141
// Check for valid compression types
4242
if (this.compression && this.compression !== 'png' &&
43-
this.compression !== 'none') {
43+
this.compression !== 'cbor' && this.compression !== 'none') {
4444
this.emit('warning', this.compression +
4545
' compression is not supported. No compression will be used.');
4646
}

src/util/cborTypedArrayTags.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
'use strict';
2+
3+
var UPPER32 = Math.pow(2, 32);
4+
5+
var warnedPrecision = false;
6+
function warnPrecision() {
7+
if (!warnedPrecision) {
8+
warnedPrecision = true;
9+
console.warn('CBOR 64-bit integer array values may lose precision. No further warnings.');
10+
}
11+
}
12+
13+
/**
14+
* Unpacks 64-bit unsigned integer from byte array.
15+
* @param {Uint8Array} bytes
16+
*/
17+
function decodeUint64LE(bytes) {
18+
warnPrecision();
19+
20+
var byteLen = bytes.byteLength;
21+
var arrLen = byteLen / 8;
22+
23+
var buffer = bytes.buffer.slice(-byteLen);
24+
var uint32View = new Uint32Array(buffer);
25+
26+
var arr = new Array(arrLen);
27+
for (var i = 0; i < arrLen; i++) {
28+
var si = i * 2;
29+
var lo = uint32View[si];
30+
var hi = uint32View[si+1];
31+
arr[i] = lo + UPPER32 * hi;
32+
}
33+
34+
return arr;
35+
}
36+
37+
/**
38+
* Unpacks 64-bit signed integer from byte array.
39+
* @param {Uint8Array} bytes
40+
*/
41+
function decodeInt64LE(bytes) {
42+
warnPrecision();
43+
44+
var byteLen = bytes.byteLength;
45+
var arrLen = byteLen / 8;
46+
47+
var buffer = bytes.buffer.slice(-byteLen);
48+
var uint32View = new Uint32Array(buffer);
49+
var int32View = new Int32Array(buffer);
50+
51+
var arr = new Array(arrLen);
52+
for (var i = 0; i < arrLen; i++) {
53+
var si = i * 2;
54+
var lo = uint32View[si];
55+
var hi = int32View[si+1];
56+
arr[i] = lo + UPPER32 * hi;
57+
}
58+
59+
return arr;
60+
}
61+
62+
/**
63+
* Unpacks typed array from byte array.
64+
* @param {Uint8Array} bytes
65+
* @param {type} ArrayType - desired output array type
66+
*/
67+
function decodeNativeArray(bytes, ArrayType) {
68+
var byteLen = bytes.byteLength;
69+
var buffer = bytes.buffer.slice(-byteLen);
70+
return new ArrayType(buffer);
71+
}
72+
73+
/**
74+
* Support a subset of draft CBOR typed array tags:
75+
* <https://tools.ietf.org/html/draft-ietf-cbor-array-tags-00>
76+
* Only support little-endian tags for now.
77+
*/
78+
var nativeArrayTypes = {
79+
64: Uint8Array,
80+
69: Uint16Array,
81+
70: Uint32Array,
82+
72: Int8Array,
83+
77: Int16Array,
84+
78: Int32Array,
85+
85: Float32Array,
86+
86: Float64Array
87+
};
88+
89+
/**
90+
* We can also decode 64-bit integer arrays, since ROS has these types.
91+
*/
92+
var conversionArrayTypes = {
93+
71: decodeUint64LE,
94+
79: decodeInt64LE
95+
};
96+
97+
/**
98+
* Handles CBOR typed array tags during decoding.
99+
* @param {Uint8Array} data
100+
* @param {Number} tag
101+
*/
102+
function cborTypedArrayTagger(data, tag) {
103+
if (tag in nativeArrayTypes) {
104+
var arrayType = nativeArrayTypes[tag];
105+
return decodeNativeArray(data, arrayType);
106+
}
107+
if (tag in conversionArrayTypes) {
108+
return conversionArrayTypes[tag](data);
109+
}
110+
return data;
111+
}
112+
113+
if (typeof module !== 'undefined' && module.exports) {
114+
module.exports = cborTypedArrayTagger;
115+
}

test/cbor.test.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
var expect = require('chai').expect;
2+
var CBOR = require('cbor-js');
3+
var cborTypedArrayTagger = require('../src/util/cborTypedArrayTags.js');
4+
5+
/** Convert hex string to ArrayBuffer. */
6+
function hexToBuffer(hex) {
7+
var tokens = hex.match(/[0-9a-fA-F]{2}/gi);
8+
var arr = tokens.map(function(t) {
9+
return parseInt(t, 16);
10+
});
11+
return new Uint8Array(arr).buffer;
12+
}
13+
14+
15+
describe('CBOR Typed Array Tagger', function() {
16+
17+
it('should convert tagged Uint16Array', function() {
18+
var data = hexToBuffer('d84546010002000300');
19+
var msg = CBOR.decode(data, cborTypedArrayTagger);
20+
21+
expect(msg).to.be.a('Uint16Array');
22+
expect(msg).to.have.lengthOf(3);
23+
expect(msg[0]).to.equal(1);
24+
expect(msg[1]).to.equal(2);
25+
expect(msg[2]).to.equal(3);
26+
});
27+
28+
it('should convert tagged Uint32Array', function() {
29+
var data = hexToBuffer('d8464c010000000200000003000000');
30+
var msg = CBOR.decode(data, cborTypedArrayTagger);
31+
32+
expect(msg).to.be.a('Uint32Array');
33+
expect(msg).to.have.lengthOf(3);
34+
expect(msg[0]).to.equal(1);
35+
expect(msg[1]).to.equal(2);
36+
expect(msg[2]).to.equal(3);
37+
});
38+
39+
it('should convert tagged Uint64Array', function() {
40+
var data = hexToBuffer('d8475818010000000000000002000000000000000300000000000000');
41+
var msg = CBOR.decode(data, cborTypedArrayTagger);
42+
43+
expect(msg).to.be.a('Array');
44+
expect(msg).to.have.lengthOf(3);
45+
expect(msg[0]).to.equal(1);
46+
expect(msg[1]).to.equal(2);
47+
expect(msg[2]).to.equal(3);
48+
});
49+
50+
it('should convert tagged Int8Array', function() {
51+
var data = hexToBuffer('d8484301fe03');
52+
var msg = CBOR.decode(data, cborTypedArrayTagger);
53+
54+
expect(msg).to.be.a('Int8Array');
55+
expect(msg).to.have.lengthOf(3);
56+
expect(msg[0]).to.equal(1);
57+
expect(msg[1]).to.equal(-2);
58+
expect(msg[2]).to.equal(3);
59+
});
60+
61+
it('should convert tagged Int16Array', function() {
62+
var data = hexToBuffer('d84d460100feff0300');
63+
var msg = CBOR.decode(data, cborTypedArrayTagger);
64+
65+
expect(msg).to.be.a('Int16Array');
66+
expect(msg).to.have.lengthOf(3);
67+
expect(msg[0]).to.equal(1);
68+
expect(msg[1]).to.equal(-2);
69+
expect(msg[2]).to.equal(3);
70+
});
71+
72+
it('should convert tagged Int32Array', function() {
73+
var data = hexToBuffer('d84e4c01000000feffffff03000000');
74+
var msg = CBOR.decode(data, cborTypedArrayTagger);
75+
76+
expect(msg).to.be.a('Int32Array');
77+
expect(msg).to.have.lengthOf(3);
78+
expect(msg[0]).to.equal(1);
79+
expect(msg[1]).to.equal(-2);
80+
expect(msg[2]).to.equal(3);
81+
});
82+
83+
it('should convert tagged Int64Array', function() {
84+
var data = hexToBuffer('d84f58180100000000000000feffffffffffffff0300000000000000');
85+
var msg = CBOR.decode(data, cborTypedArrayTagger);
86+
87+
expect(msg).to.be.a('Array');
88+
expect(msg).to.have.lengthOf(3);
89+
expect(msg[0]).to.equal(1);
90+
expect(msg[1]).to.equal(-2);
91+
expect(msg[2]).to.equal(3);
92+
});
93+
94+
it('should convert tagged Float32Array', function() {
95+
var data = hexToBuffer('d8554ccdcc8c3fcdcc0cc033335340');
96+
var msg = CBOR.decode(data, cborTypedArrayTagger);
97+
98+
expect(msg).to.be.a('Float32Array');
99+
expect(msg).to.have.lengthOf(3);
100+
expect(msg[0]).to.be.closeTo(1.1, 1e-5);
101+
expect(msg[1]).to.be.closeTo(-2.2, 1e-5);
102+
expect(msg[2]).to.be.closeTo(3.3, 1e-5);
103+
});
104+
105+
it('should convert tagged Float64Array', function() {
106+
var data = hexToBuffer('d85658189a9999999999f13f9a999999999901c06666666666660a40');
107+
var msg = CBOR.decode(data, cborTypedArrayTagger);
108+
109+
expect(msg).to.be.a('Float64Array');
110+
expect(msg).to.have.lengthOf(3);
111+
expect(msg[0]).to.be.closeTo(1.1, 1e-5);
112+
expect(msg[1]).to.be.closeTo(-2.2, 1e-5);
113+
expect(msg[2]).to.be.closeTo(3.3, 1e-5);
114+
});
115+
});

test/karma.conf.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ module.exports = function(config) {
1212
// List of files / patterns to load in the browser
1313
files: [
1414
'../node_modules/eventemitter2/lib/eventemitter2.js',
15+
'../node_modules/cbor-js/cbor.js',
16+
'../src/util/cborTypedArrayTags.js',
1517
'../build/roslib.js',
1618
'./require-shim.js',
1719
'*.test.js'

test/require-shim.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
window.require = function require(path) {
22
switch (path) {
33
case 'eventemitter2': return EventEmitter2;
4-
case 'xmldom': return {DOMParser: DOMParser}
4+
case 'xmldom': return {DOMParser: DOMParser};
5+
case 'cbor-js': return CBOR;
6+
case '../src/util/cborTypedArrayTags.js': return cborTypedArrayTagger;
57
}
68
var lastIdx = path.lastIndexOf('/'),
79
path = lastIdx >= 0 ? path.slice(lastIdx + 1) : path;

0 commit comments

Comments
 (0)