├── .gitignore ├── COPYING ├── README ├── examples ├── echo-client.js ├── echo-server.js ├── math-client.js └── math-server.js ├── package.json ├── src ├── bert.js └── bertrpc.js └── test ├── test_bert_atom.js ├── test_bert_biginteger.js ├── test_bert_binary.js ├── test_bert_boolean.js ├── test_bert_dictionary.js ├── test_bert_float.js ├── test_bert_integer.js ├── test_bert_list.js ├── test_bert_tuple.js └── test_bertrpc.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (C) 2009 Ryan Tomayko 2 | Copyright (C) 2013 Jason Lunz 3 | 4 | Permission is hereby granted, free of charge, to any person ob- 5 | taining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restric- 7 | tion, including without limitation the rights to use, copy, modi- 8 | fy, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is fur- 10 | nished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONIN- 18 | FRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | bertrpc.js 2 | 3 | This is a BERT-RPC server and client library for 4 | node.js , the V8 based evented 5 | IO framework. 6 | 7 | BERT-RPC is a schemaless binary remote procedure call 8 | protocol by Tom Preston-Werner, based on the BERT 9 | (Binary ERlang Term) serialization format. 10 | 11 | See for more information about 12 | BERT and BERT-RPC, including additional language 13 | implementations. 14 | 15 | The bert.js library was taken from Rusty Klophaus's 16 | BERT-JS project: 17 | but has been modified quite a bit at this point. 18 | 19 | STATUS 20 | 21 | This package is lightly maintained in hopes somebody finds it useful. 22 | 23 | INSTALL 24 | 25 | node.js must be installed and available on your PATH 26 | before continuing. It's trivial: 27 | 28 | cd /tmp 29 | curl -L http://bit.ly/nodejs | tar xvzf - 30 | cd node-* 31 | ./configure PREFIX=/usr/local 32 | make && sudo make install 33 | 34 | EXAMPLES 35 | 36 | Once you have node installed, grab a clone of the 37 | node-bertrpc git repo 38 | 39 | $ git clone git://github.com/znull/node-bertrpc 40 | $ cd node-bertrpc 41 | $ node examples/helloworld.js 42 | <-- exposing: say [funs: hello, echo] 43 | 44 | Leave that there and then run the example client in a 45 | separate shell: 46 | 47 | $ node examples/helloclient.js 48 | 49 | You should see something like the following output from 50 | the server: 51 | 52 | --> connect 53 | --> 30: {call, say, hello, []} 54 | <-- 21: {reply, <<"hello">>} 55 | --> 45: {call, say, echo, [<<"Hello World">>]} 56 | <-- 33: {reply, [<<"Hello World">>]} 57 | --> 85: {call, say, echo, [[{foo, <<"bar">>}, {bar, <<"baz">>}], 21]} 58 | <-- 73: {reply, [[{foo, <<"bar">>}, {bar, <<"baz">>}], 21]} 59 | --> eof 60 | <-- close 61 | 62 | Connect with the Ruby BERT-RPC client (or any other 63 | BERT-RPC client) to play at a REPL: 64 | 65 | $ sudo gem install bertrpc -s http://gemcutter.org/ 66 | $ irb -rubygems -rbertrpc 67 | >> service = BERTRPC::Service.new('localhost', 7000) 68 | >> service.call.say.echo('hello', 'world', { 'foo' => %w[bar baz bizle] }) 69 | => ["hello", "world", {:foo=>["bar", "baz", "bizzle"]}] 70 | 71 | BERT-RPC SERVERS 72 | 73 | Require the "bertrpc" lib and expose objects: 74 | 75 | var rpc = require('bertrpc'); 76 | rpc.expose('hello', { 77 | add: function (a, b) { 78 | return a + b; 79 | }, 80 | subtract: function (a, b) { 81 | return a - b; 82 | } 83 | }); 84 | 85 | rpc.listen(7001, 'localhost'); 86 | 87 | Start the node server by running your script directly: 88 | 89 | node yourstuff.js 90 | 91 | That exposes a BERT-RPC module named "hello" with two 92 | remotely callable functions: "add" and "subtract" but you 93 | can pass any object to expose to make all of its functions 94 | available. 95 | 96 | BERT-RPC CLIENTS 97 | 98 | The BERT-RPC client library is still very experimental and 99 | likely to change significantly: 100 | 101 | var sys = require('sys'), 102 | rpc = require('bertrpc'); 103 | rpc.connect(7001, 'localhost', function (service) { 104 | sys.puts("client calling the add fun in the hello module"); 105 | service.call('hello', 'add', [1, 2], function (result) { 106 | sys.puts("client received response: " + sys.inspect(result)); 107 | // do something useful 108 | }); 109 | }); 110 | 111 | BERT ENCODING / DECODING 112 | 113 | The bert.js library includes functions for encoding, 114 | decoding, and getting a formatting string representation of 115 | BERT objects. 116 | 117 | Get in a REPL: 118 | 119 | $ NODE_PATH=./src node-repl 120 | Welcome to the Node.js REPL. 121 | Enter ECMAScript at the prompt. 122 | node> bert = require('bert'); 123 | 124 | bert.encode serializes an object graph as a BERT: 125 | 126 | node> holabert = bert.encode('hello') 127 | "\u0083m\u0000\u0000\u0000\u0005hello" 128 | node> sys.puts(bert.bin_repr(holabert)) 129 | <<131,109,0,0,0,5,104,101,108,108,111>> 130 | 131 | bert.decode deserializes a BERT, creating an object graph: 132 | 133 | node> bert.decode("\u0083m\u0000\u0000\u0000\u0005hello") 134 | "hello" 135 | 136 | DEVELOPMENT 137 | 138 | The tests have been converted to use `nodeunit`. Make sure it is installed 139 | and available in your path, then run `nodeunit test/test_*` to execute the 140 | tests. 141 | 142 | SEE ALSO 143 | 144 | CODE: git clone git://github.com/znull/node-bertrpc.git 145 | HOME: http://github.com/znull/node-bertrpc/ 146 | BUGS: http://github.com/znull/node-bertrpc/issues 147 | COPY: See the file COPYING (it's MIT) 148 | -------------------------------------------------------------------------------- /examples/echo-client.js: -------------------------------------------------------------------------------- 1 | var util = require('util'), 2 | rpc = require('../src/bertrpc'); 3 | 4 | // connect to the server 5 | rpc.connect(7000, 'localhost', function (service) { 6 | 7 | // call the echo module's hello function with no arguments 8 | var res = service.call('echo', 'hello', []); 9 | util.debug("client sending {call, say, hello, []}"); 10 | 11 | // the call return comes back asynchronously 12 | res.finish(function (result) { 13 | util.debug("client received: " + util.inspect(result)); 14 | }); 15 | 16 | // call the echo module's echo function with a simple string 17 | // argument and provide the finish callback to the call method 18 | // instead of registering it on the resulting promise: 19 | util.debug("client sending {call, say, echo, ['Hello World']}"); 20 | service.call('echo', 'echo', ['Hello World'], function (result) { 21 | util.debug("client received " + util.inspect(result)); 22 | }); 23 | 24 | // grab the echo module object so that we can call on it without 25 | // specifying the module name. 26 | var echo_module = service.mod('echo'); 27 | 28 | // pass dict and integer args this time to make things a bit more 29 | // interesting. 30 | util.debug("client sending {call, say, echo, [[{foo, <<'bar'>>}, {bar, <<'baz'>>}], 21]}"); 31 | echo_module.call('echo', [{foo: 'bar', bar: 'baz'}, 21], function (result) { 32 | util.debug("client received: " + util.inspect(result)); 33 | }); 34 | 35 | util.debug("client closing connection"); 36 | service.end(); 37 | }); 38 | -------------------------------------------------------------------------------- /examples/echo-server.js: -------------------------------------------------------------------------------- 1 | var util = require('util'), 2 | rpc = require('../src/bertrpc'); 3 | 4 | rpc.expose('echo', { 5 | // return 'hello' no matter what 6 | hello: function(what) { 7 | return "hello"; 8 | }, 9 | 10 | // return arguments exactly as provided 11 | echo: function() { 12 | var args = []; 13 | for ( var i = 0; i < arguments.length; i++ ) 14 | args[i] = arguments[i]; 15 | return args; 16 | } 17 | }); 18 | 19 | rpc.listen(7000, 'localhost'); 20 | -------------------------------------------------------------------------------- /examples/math-client.js: -------------------------------------------------------------------------------- 1 | var util = require('util'), 2 | rpc = require('../src/bertrpc'); 3 | 4 | // connect to the server 5 | rpc.connect(7001, 'localhost', function (service) { 6 | // grab the math module object so that we can call on it without 7 | // specifying the module name. 8 | var mathrpc = service.mod('math'); 9 | 10 | // call the math module's sum function with a few integers 11 | // and print the result 12 | util.debug("math client summing [1, 2, 3]"); 13 | mathrpc.call('sum', [[1, 2, 3]], function (result) { 14 | util.debug("math client got: " + util.inspect(result)); 15 | }); 16 | 17 | // calculate a fibonacci number and pass the result to 18 | // the callback provided. 19 | var fib = function (num, callback) { 20 | if ( num == 1 ) { 21 | callback(0); 22 | } else if ( num == 2 ) { 23 | callback(1) 24 | } else { 25 | fib(num - 1, function(n1) { 26 | fib(num - 2, function(n2) { 27 | mathrpc.call('sum', [[n1, n2]], callback); 28 | }); 29 | }); 30 | } 31 | } 32 | 33 | // calculate the 25th fibonacci number 34 | fib(25, function (number) { 35 | util.debug("the 25th fibonacci number is " + number); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /examples/math-server.js: -------------------------------------------------------------------------------- 1 | var util = require('util'), 2 | rpc = require('../src/bertrpc'); 3 | 4 | rpc.expose('math', { 5 | // return the sum of an array of numeric values 6 | sum: function(values) { 7 | var res = 0; 8 | for(var i = 0; i < values.length; i++) 9 | res += values[i]; 10 | return res; 11 | }, 12 | 13 | // return the average of an array of numeric values 14 | avg: function(values) { 15 | return this.sum(values) / values.length; 16 | } 17 | }); 18 | 19 | rpc.listen(7001, 'localhost'); 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-bertrpc", 3 | "version": "1.0.1", 4 | "description": "BERT-RPC is a schemaless binary remote procedure call protocol", 5 | "main": "src/bertrpc.js", 6 | "directories": { 7 | "example": "examples", 8 | "test": "test" 9 | }, 10 | "dependencies": { 11 | }, 12 | "devDependencies": { 13 | "nodeunit": "~0.9.0" 14 | }, 15 | "scripts": { 16 | "test": "nodeunit test/test_*.js" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/znull/node-bertrpc.git" 21 | }, 22 | "keywords": [ 23 | "bert", 24 | "rpc", 25 | "binary", 26 | "erlang", 27 | "term" 28 | ], 29 | "author": "Ryan Tomayko", 30 | "contributors": [ 31 | "Jason Lunz", 32 | "Rusty Klophaus", 33 | "Ben Browning" 34 | ], 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/znull/node-bertrpc/issues" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/bert.js: -------------------------------------------------------------------------------- 1 | // bert.js 2 | // 3 | // Copyright (c) 2014 Jason Lunz 4 | // See COPYING for licensing information. 5 | // 6 | // 7 | // Based heavily on the BERT-JS library by Rusty Klophaus & Ryan Tomayko: 8 | // 9 | // 10 | // 11 | // Copyright (c) 2009 Ryan Tomayko 12 | // Copyright (c) 2009 Rusty Klophaus (@rklophaus) 13 | // Contributions by Ben Browning (@bbrowning) 14 | // 15 | // TODO time 16 | // TODO regex 17 | // TODO push / streaming 18 | 19 | 20 | // BERT types are mapped to JavaScript types as follows: 21 | // 22 | // +--------------+----------------+ 23 | // | BERT | JavaScript | 24 | // +--------------+----------------+ 25 | // | atom | bert.Atom | 26 | // | binary | String | 27 | // | boolean | true, false | 28 | // | bytelist | bert.Bytelist | 29 | // | dictionary | Object | 30 | // | float | Number | 31 | // | integer | Number | 32 | // | list | Array | 33 | // | nil | null | 34 | // | regex | NOT SUPPORTED | 35 | // | tuple | bert.Tuple | 36 | // | time | NOT SUPPORTED | 37 | // +--------------+----------------+ 38 | // 39 | 40 | 41 | // frequently used atom objects; set after BERT is defined. 42 | var _bert, _dict, _nil, _true, _false, _reply; 43 | 44 | var BERT = { 45 | /* WIRE PROTOCOL CODES */ 46 | 47 | BERT_START: String.fromCharCode(131), 48 | SMALL_ATOM: String.fromCharCode(115), 49 | ATOM: String.fromCharCode(100), 50 | BINARY: String.fromCharCode(109), 51 | SMALL_INTEGER: String.fromCharCode(97), 52 | INTEGER: String.fromCharCode(98), 53 | SMALL_BIG: String.fromCharCode(110), 54 | LARGE_BIG: String.fromCharCode(111), 55 | FLOAT: String.fromCharCode(99), 56 | NEW_FLOAT: String.fromCharCode(70), 57 | STRING: String.fromCharCode(107), 58 | LIST: String.fromCharCode(108), 59 | SMALL_TUPLE: String.fromCharCode(104), 60 | LARGE_TUPLE: String.fromCharCode(105), 61 | NIL: String.fromCharCode(106), 62 | ZERO: String.fromCharCode(0), 63 | 64 | /* BERT TYPE WRAPPER CLASSES */ 65 | 66 | Atom: function (string) { 67 | this.type = "atom"; 68 | this.value = string; 69 | this.repr = function () { return string } 70 | this.toString = function () { return string }; 71 | }, 72 | 73 | Bytelist: function (string) { 74 | this.type = "bytelist"; 75 | this.value = string; 76 | this.toString = function () { return this.value } 77 | this.repr = function () { 78 | var bytes = BERT.string_to_bytelist(this.value), 79 | string= ""; 80 | for (var i=0; i < bytes.length; i++) { 81 | if (i > 0) string += ","; 82 | string += bytes[i]; 83 | } 84 | return "<<" + string + ">>"; 85 | } 86 | }, 87 | 88 | Tuple: function (array) { 89 | this.type = "tuple"; 90 | this.length = array.length; 91 | this.value = array; 92 | for (var i=0; i < array.length; i++) 93 | this[i] = array[i]; 94 | this.repr = function () { 95 | var s = ""; 96 | for (var i=0; i < this.length; i++) { 97 | if (i > 0) s += ", "; 98 | s += BERT.repr(this.value[i]); 99 | } 100 | return "{" + s + "}"; 101 | } 102 | this.toString = this.repr; 103 | }, 104 | 105 | /* CASTING TO BERT TYPES */ 106 | 107 | atom: function (string) { return new BERT.Atom(string); }, 108 | tuple: function () { return new BERT.Tuple(arguments); }, 109 | tup: function (array) { return new BERT.Tuple(array); }, 110 | bytelist: function (string) { return new BERT.Bytelist(string); }, 111 | binary: function (string) { return string; }, 112 | list: function (array) { return list; }, 113 | dictionary: function (object) { return object; }, 114 | 115 | /* BASIC INTERFACE */ 116 | 117 | encode: function (obj) { 118 | return BERT.BERT_START + BERT.encode_inner(obj); 119 | }, 120 | 121 | encode_to_buffer: function (obj) { 122 | return new Buffer(BERT.string_to_bytelist(BERT.encode(obj))); 123 | }, 124 | 125 | decode: function (data) { 126 | if (data[0] != BERT.BERT_START) throw new Error("Not a valid BERT."); 127 | var obj = BERT.decode_inner(data.substring(1)); 128 | if (obj.rest != "") throw new Error("Invalid BERT."); 129 | return obj.value; 130 | }, 131 | 132 | decode_buffer: function (buf) { 133 | return BERT.decode(buf.toString('binary')); 134 | }, 135 | 136 | /* ENCODING */ 137 | 138 | encode_inner: function (obj) { 139 | var type = typeof(obj); 140 | return this["encode_" + type].call(this, obj); 141 | }, 142 | 143 | encode_string: function (obj) { 144 | return this.BINARY + 145 | this.int_to_bytes(obj.length, 4) + 146 | obj; 147 | }, 148 | 149 | encode_bytelist: function (obj) { 150 | return this.STRING + 151 | this.int_to_bytes(obj.value.length, 2) + 152 | obj.value; 153 | }, 154 | 155 | encode_boolean: function (obj) { 156 | if (obj) { 157 | return this.encode_tup(_bert, _true); 158 | } else { 159 | return this.encode_tup(_bert, _false); 160 | } 161 | }, 162 | 163 | encode_number: function (obj) { 164 | var remainder = (obj % 1 != 0); 165 | 166 | if (remainder) 167 | return this.encode_float(obj); 168 | 169 | // small int... 170 | if (obj >= 0 && obj < 256) 171 | return this.SMALL_INTEGER + this.int_to_bytes(obj, 1); 172 | 173 | // 4 byte int... 174 | if (obj >= -134217728 && obj <= 134217727) 175 | return this.INTEGER + this.int_to_bytes(obj, 4); 176 | 177 | // bignum... 178 | var s = this.bignum_to_bytes(obj); 179 | if (s.length < 256) { 180 | return this.SMALL_BIG + this.int_to_bytes(s.length - 1, 1) + s; 181 | } else { 182 | return this.LARGE_BIG + this.int_to_bytes(s.length - 1, 4) + s; 183 | } 184 | }, 185 | 186 | encode_float: function (obj) { 187 | var s = obj.toExponential(); 188 | while (s.length < 31) 189 | s += this.ZERO; 190 | return this.FLOAT + s; 191 | }, 192 | 193 | encode_object: function (obj) { 194 | if (obj == null) 195 | return this.encode_null(obj); 196 | if (obj.type == 'atom') 197 | return this.encode_atom(obj); 198 | if (obj.type == 'tuple') 199 | return this.encode_tuple(obj); 200 | if (obj.type == 'bytelist') 201 | return this.encode_bytelist(obj); 202 | if (obj.constructor.toString().indexOf("Array") >= 0) 203 | return this.encode_list(obj); 204 | return this.encode_dictionary(obj); 205 | }, 206 | 207 | encode_atom: function (obj) { 208 | return this.ATOM + 209 | this.int_to_bytes(obj.value.length, 2) + 210 | obj.value; 211 | }, 212 | 213 | encode_binary: function (obj) { 214 | return this.BINARY + 215 | this.int_to_bytes(obj.value.length, 4) + 216 | obj.value; 217 | }, 218 | 219 | encode_tuple: function (obj) { 220 | var s = ""; 221 | 222 | if (obj.length < 256) { 223 | s += this.SMALL_TUPLE + this.int_to_bytes(obj.length, 1); 224 | } else { 225 | s += this.LARGE_TUPLE + this.int_to_bytes(obj.length, 4); 226 | } 227 | for (var i=0; i < obj.length; i++) { 228 | s += this.encode_inner(obj[i]); 229 | } 230 | return s; 231 | }, 232 | 233 | encode_tup: function () { 234 | return this.encode_tuple(this.tup(arguments)); 235 | }, 236 | 237 | encode_list: function (obj) { 238 | var s = this.LIST + this.int_to_bytes(obj.length, 4); 239 | for (var i=0; i < obj.length; i++) { 240 | s += this.encode_inner(obj[i]); 241 | } 242 | s += this.NIL; 243 | return s; 244 | }, 245 | 246 | encode_dictionary: function (obj) { 247 | var array = new Array(); 248 | for (var key in obj) 249 | array.push(this.tuple(key, obj[key])); 250 | return this.encode_tup(_bert, _dict, array); 251 | }, 252 | 253 | encode_null: function (obj) { 254 | return this.encode_tup(_bert, _nil); 255 | }, 256 | 257 | /* DECODING */ 258 | 259 | decode_inner: function (data) { 260 | var type = data[0]; 261 | data = data.substring(1); 262 | if (type == this.SMALL_ATOM) return this.decode_atom(data, 1); 263 | if (type == this.ATOM) return this.decode_atom(data, 2); 264 | if (type == this.BINARY) return this.decode_binary(data); 265 | if (type == this.SMALL_INTEGER) return this.decode_integer(data, 1); 266 | if (type == this.INTEGER) return this.decode_integer(data, 4); 267 | if (type == this.SMALL_BIG) return this.decode_big(data, 1); 268 | if (type == this.LARGE_BIG) return this.decode_big(data, 4); 269 | if (type == this.FLOAT) return this.decode_float(data); 270 | if (type == this.NEW_FLOAT) return this.decode_new_float(data); 271 | if (type == this.STRING) return this.decode_bytelist(data); 272 | if (type == this.LIST) return this.decode_list(data); 273 | if (type == this.SMALL_TUPLE) return this.decode_tuple(data, 1); 274 | if (type == this.LARGE_TUPLE) return this.decode_large_tuple(data, 4); 275 | if (type == this.NIL) return this.decode_nil(data); 276 | throw new Error("Unexpected BERT type: " + String.charCodeAt(type)); 277 | }, 278 | 279 | decode_atom: function (data, count) { 280 | var size = this.bytes_to_int(data, count); 281 | data = data.substring(count); 282 | var value = data.substring(0, size); 283 | if (value == "true") { 284 | value = _true; 285 | } else if (value == "false") { 286 | value = _false; 287 | } else if (value == "bert") { 288 | value = _bert; 289 | } else if (value == "nil") { 290 | value = _nil; 291 | } else if (value == "dict") { 292 | value = _dict; 293 | } else { 294 | value = this.atom(value); 295 | } 296 | return { 297 | value: value, 298 | rest: data.substring(size) 299 | }; 300 | }, 301 | 302 | decode_binary: function (data) { 303 | var size = this.bytes_to_int(data, 4); 304 | data = data.substring(4); 305 | return { 306 | value: data.substring(0, size), 307 | rest: data.substring(size) 308 | }; 309 | }, 310 | 311 | decode_integer: function (data, count) { 312 | var value = this.bytes_to_int(data, count); 313 | data = data.substring(count); 314 | return { 315 | value: value, 316 | rest: data 317 | }; 318 | }, 319 | 320 | decode_big: function (data, count) { 321 | var size = this.bytes_to_int(data, count); 322 | data = data.substring(count); 323 | var value = this.bytes_to_bignum(data, size); 324 | return { 325 | value: value, 326 | rest: data.substring(size + 1) 327 | }; 328 | }, 329 | 330 | decode_float: function (data) { 331 | var size = 31; 332 | return { 333 | value: parseFloat(data.substring(0, size)), 334 | rest: data.substring(size) 335 | }; 336 | }, 337 | 338 | decode_new_float: function (data) { 339 | var size = 8; 340 | return { 341 | value: new Buffer(data.substring(0, size), 'binary').readDoubleBE(0, true), 342 | rest: data.substring(size) 343 | }; 344 | }, 345 | 346 | decode_bytelist: function (data) { 347 | var size = this.bytes_to_int(data, 2); 348 | data = data.substring(2); 349 | return { 350 | value: this.bytelist(data.substring(0, size)), 351 | rest: data.substring(size) 352 | }; 353 | }, 354 | 355 | decode_list: function (data) { 356 | var size = this.bytes_to_int(data, 4); 357 | data = data.substring(4); 358 | var array = new Array(); 359 | for (var i=0; i < size; i++) { 360 | var element = this.decode_inner(data); 361 | array.push(element.value); 362 | data = element.rest; 363 | } 364 | var last = data[0]; 365 | if (last != this.NIL) throw new Error("List does not end with NIL!"); 366 | data = data.substring(1); 367 | return { 368 | value: array, 369 | rest: data 370 | }; 371 | }, 372 | 373 | decode_tuple: function (data, count) { 374 | var size = this.bytes_to_int(data, count), 375 | array = new Array(), 376 | value = null; 377 | data = data.substring(count); 378 | for (var i=0; i < size; i++) { 379 | var element = this.decode_inner(data); 380 | array.push(element.value); 381 | data = element.rest; 382 | } 383 | if (array[0] == _bert) { 384 | if ( array[1] == _dict ) { 385 | var list = array[2], 386 | dict = {}, 387 | item = null; 388 | for(var i=0; i < list.length; i++) { 389 | item = list[i]; 390 | if ( item[0] === null ) { 391 | dict[null] = item[1]; 392 | } else if ( item[0].type == 'atom' ) { 393 | dict[item[0].toString()] = item[1]; 394 | } else { 395 | dict[item[0]] = item[1]; 396 | } 397 | } 398 | value = dict; 399 | } 400 | else if ( array[1] == _nil ) { value = null; } 401 | else if ( array[1] == _true ) { value = true; } 402 | else if ( array[1] == _false ) { value = false; } 403 | else 404 | throw new Error('unsupported complex tuple: {bert, ' + array[1] + '}'); 405 | }else{ 406 | value = this.tup(array); 407 | } 408 | return {value:value, rest:data}; 409 | }, 410 | 411 | decode_nil: function (data) { 412 | return { 413 | value: [], 414 | rest: data 415 | }; 416 | }, 417 | 418 | /* UTILITY FUNCTIONS */ 419 | 420 | // Encode an integer to a big-endian byte-string 421 | // of the length specified. Throw an exception if 422 | // the integer is too large to fit into the specified 423 | // number of bytes. 424 | int_to_bytes: function (int, length) { 425 | var negative = (int < 0), 426 | data = "", 427 | orig = int; 428 | if (negative) { int = ~int; } 429 | for (var i=0; i < length; i++) { 430 | var remainder = int % 256; 431 | if (negative) { remainder = 255 - remainder }; 432 | data = String.fromCharCode(remainder) + data; 433 | int = Math.floor(int / 256); 434 | } 435 | if (int > 0) throw new Error("Argument out of range: " + orig); 436 | return data; 437 | }, 438 | 439 | // Read a big-endian encoded integer from the first length 440 | // bytes of the supplied string. When length is a single byte, 441 | // just return the unsigned byte value. 442 | bytes_to_int: function (data, length) { 443 | if ( length == 1 ) 444 | return data.charCodeAt(i); 445 | 446 | var num = 0, 447 | negative = (length > 1 && data.charCodeAt(0) > 128), 448 | n = null; 449 | 450 | for (var i=0; i < length; i++) { 451 | n = data.charCodeAt(i); 452 | if (negative) n = 255 - n; 453 | if (num == 0) num = n; 454 | else num = num * 256 + n; 455 | } 456 | if (negative) num = ~num; 457 | return num; 458 | }, 459 | 460 | // Encode an integer into an Erlang bignum, which is a 461 | // byte of 1 or 0 representing whether the number is 462 | // negative or positive, followed by little-endian bytes. 463 | bignum_to_bytes: function (int) { 464 | var negative = int < 0, 465 | data = ""; 466 | if (negative) { 467 | int *= -1; 468 | data += String.fromCharCode(1); 469 | } else { 470 | data += String.fromCharCode(0); 471 | } 472 | while (int != 0) { 473 | var remainder = int % 256; 474 | data += String.fromCharCode(remainder); 475 | int = Math.floor(int / 256); 476 | } 477 | return data; 478 | }, 479 | 480 | // Encode a list of bytes into an Erlang bignum. 481 | bytes_to_bignum: function (data, count) { 482 | var negative = (data.charCodeAt(0) == 1), 483 | num = 0, 484 | n = null; 485 | data = data.substring(1); 486 | for (var i = count - 1; i >= 0; i--) { 487 | n = data.charCodeAt(i); 488 | if (num == 0) num = n; 489 | else num = num * 256 + n; 490 | } 491 | if (negative) num *= -1; 492 | return num; 493 | }, 494 | 495 | // Convert an array of bytes into a string. 496 | bytelist_to_string: function (bytes) { 497 | var string = ""; 498 | for (var i=0; i < bytes.length; i++) 499 | string += String.fromCharCode(bytes[i]); 500 | return string; 501 | }, 502 | 503 | string_to_bytelist: function (string) { 504 | var bytelist = new Array(); 505 | for (var i=0; i < string.length; i++) 506 | bytelist[i] = string.charCodeAt(i); 507 | return bytelist; 508 | }, 509 | 510 | /* FORMATTING */ 511 | 512 | bin_repr: function (obj) { 513 | return BERT.repr(BERT.bytelist(obj)); 514 | }, 515 | 516 | // pretty print a JS object in erlang term form 517 | repr: function (obj) { 518 | if (obj === null) 519 | return ""; 520 | 521 | if (obj === true) 522 | return ""; 523 | 524 | if (obj === false) 525 | return ""; 526 | 527 | if (typeof(obj) == 'string') 528 | return "<<\"" + obj + "\">>"; 529 | 530 | // numbers, booleans, stuff like that 531 | if (typeof(obj) != 'object') 532 | return obj.toString(); 533 | 534 | // BERT special types: atom, tuple, bytelist 535 | if (obj.repr) 536 | return obj.repr(); 537 | 538 | // arrays 539 | if (obj.constructor.toString().indexOf("Array") >= 0) { 540 | var s = ""; 541 | for (var i = 0; i < obj.length; i++) { 542 | if (i > 0) s += ", "; 543 | s += BERT.repr(obj[i]) 544 | } 545 | return "[" + s + "]"; 546 | } 547 | 548 | // Assume it's a dictionary 549 | var s = "", prev = null; 550 | for (var key in obj) { 551 | var val = obj[key]; 552 | if ( typeof(key) == 'string' ) 553 | key = BERT.atom(key); 554 | if ( prev ) s += ", "; 555 | s += BERT.repr(BERT.tuple(key, val)); 556 | prev = val; 557 | } 558 | return "[" + s + "]"; 559 | } 560 | }; 561 | 562 | _bert = BERT.atom('bert'); 563 | _nil = BERT.atom('nil'); 564 | _dict = BERT.atom('dict'); 565 | _true = BERT.atom('true'); 566 | _false = BERT.atom('false'); 567 | _reply = BERT.atom('reply'); 568 | 569 | // common JS 570 | exports = module.exports = BERT; 571 | 572 | // vim: ft=javascript ts=3 sw=3 expandtab 573 | -------------------------------------------------------------------------------- /src/bertrpc.js: -------------------------------------------------------------------------------- 1 | // bertrpc.js 2 | // 3 | // BERT-RPC is a schemaless binary remote procedure call 4 | // protocol by Tom Preston-Warner. It's based on the BERT 5 | // (Binary ERlang Term) serialization format. 6 | // 7 | // 8 | // Copyright (c) 2014 Jason Lunz 9 | // See COPYING for licensing information. 10 | // 11 | // 12 | // Based heavily on the BERT-JS library by Rusty Klophaus & Ryan Tomayko: 13 | // 14 | // 15 | // 16 | // Copyright (c) 2009 Ryan Tomayko 17 | // Copyright (c) 2009 Rusty Klophaus (@rklophaus) 18 | // Contributions by Ben Browning (@bbrowning) 19 | // 20 | // TODO errors 21 | // TODO client interface should be more node-like 22 | // TODO better client calling interface 23 | // TODO: cast(must) 24 | // TODO: trace should be switchable 25 | // TODO: logger and error handling 26 | 27 | var util = require('util'), 28 | net = require('net'), 29 | bert = require('./bert'); 30 | 31 | var bytes_to_int = bert.bytes_to_int, 32 | int_to_bytes = bert.int_to_bytes, 33 | t = bert.tuple, 34 | a = bert.atom, 35 | _reply = a('reply'); 36 | 37 | // exposed modules go here. 38 | var modules = {}; 39 | 40 | var BERTRPC = { 41 | 42 | /* BERT-RPC SERVER IMPLEMENTATION */ 43 | 44 | // Direct access to the modules dictionary. 45 | modules: modules, 46 | logger: console, 47 | 48 | // Expose all functions in object under the given BERTPRPC module 49 | // name. This should be called before bertrpc.listen. 50 | expose: function (mod, object) { 51 | var funs = []; 52 | for (var fun in object) { 53 | if (typeof(object[fun]) == 'function') 54 | funs.push(fun); 55 | } 56 | BERTRPC.trace("SERVER", "<--", "exposing: "+mod+" [funs: "+funs.join(", ")+"]"); 57 | 58 | modules[mod] = object; 59 | return object; 60 | }, 61 | 62 | // Begin listing on the port and host specified. 63 | listen: function (port, host) { 64 | BERTRPC.server.listen(port, host); 65 | }, 66 | 67 | // Dispatch a call or cast on an exposed module function. This is 68 | // called by the server when new requests are received. 69 | // type: 'call' or 'cast' 70 | // mod: the name of a module registered with BERTRPC.expose; 71 | // fun: the name of a function defined on the module. 72 | // args: arguments to fun, as an array. 73 | dispatch: function (type, mod, fun, args) { 74 | if (mod = modules[mod]) { 75 | if (fun = mod[fun]) { 76 | if (fun.apply) 77 | return fun.apply(mod, args); 78 | else 79 | throw 'no such fun'; 80 | } 81 | else { throw 'no such fun' } 82 | } 83 | else { throw 'no such module' } 84 | }, 85 | 86 | // Write a message to the console/log. 87 | trace: function (side, direction, message) { 88 | util.puts(" " + direction + " [" + side + "] " + message); 89 | }, 90 | 91 | // The node net.Server object -- ready to go. Use BERTRPC.listen 92 | // if you just want to start a server. 93 | server: net.createServer(function (socket) { 94 | var trace = BERTRPC.trace; 95 | socket.setEncoding("binary"); 96 | 97 | socket.on("connect", function () { 98 | trace("SERVER", "-->", "connect") }); 99 | 100 | socket.on("end", function () { 101 | trace("SERVER", "-->", "end"); 102 | socket.end(); 103 | trace("SERVER", "<--", "end"); 104 | }); 105 | 106 | // read BERPs off the wire and dispatch. 107 | BERTRPC.read(socket, function (size, term) { 108 | trace("SERVER", "-->", "" + size + ": " + bert.repr(term)); 109 | 110 | // dispatch call to module handler 111 | var type = term[0].toString(), 112 | mod = term[1].toString(), 113 | fun = term[2].toString(), 114 | args = term[3]; 115 | 116 | var res = BERTRPC.dispatch(type, mod, fun, args); 117 | 118 | // encode and throw back over the wire 119 | var reply = t(_reply, res); 120 | var len = BERTRPC.write(socket, reply); 121 | trace("SERVER", "<--", "" + len + ": " + bert.repr(reply)); 122 | }); 123 | }), 124 | 125 | // Connect to a remote BERT-RPC service. This is the main client 126 | // interface. 127 | connect: function (port, host, callback) { 128 | var trace = BERTRPC.trace; 129 | var socket = net.createConnection(port, host), 130 | blocks = [], 131 | client = { 132 | call: function (mod, fun, args, block) { 133 | var packet = t(a('call'), a(mod), a(fun), args); 134 | trace("CLIENT", "<--", bert.repr(packet)); 135 | BERTRPC.write(socket, packet); 136 | if (block) { blocks.push(block) } 137 | return this; 138 | }, 139 | 140 | mod: function (mod) { 141 | return { 142 | call: function (fun, args, block) { 143 | return client.call(mod, fun, args, block); 144 | }, 145 | fun: function (fun) { 146 | return function (args, block) { 147 | return client.call(mod, fun, args, block); 148 | } 149 | } 150 | } 151 | }, 152 | 153 | finish: function(block) { 154 | blocks.push(block); 155 | }, 156 | 157 | fun: function (mod, fun) { 158 | return function (args, block) { 159 | return client.call(mod, fun, args, block); 160 | } 161 | }, 162 | 163 | end: function () { 164 | socket.end(); 165 | } 166 | }; 167 | 168 | socket.setEncoding("binary"); 169 | socket.on("connect", function () { 170 | trace("CLIENT", "<--", "connected"); 171 | callback(client); 172 | }); 173 | 174 | BERTRPC.read(socket, function (size, term) { 175 | var reply = term[0], 176 | value = term[1], 177 | block = blocks.shift(); 178 | trace("CLIENT", "-->", bert.repr(term)); 179 | block(value); 180 | }); 181 | 182 | socket.on("end", function () { 183 | blocks = []; 184 | }); 185 | 186 | return client; 187 | }, 188 | 189 | // Read BERPs off the wire and call the callback provided. The 190 | // callback should accept size and term arguments, where size 191 | // is the length of the BERT packet in bytes and term is the 192 | // decoded object payload. 193 | read: function (socket, callback) { 194 | var size = null, buf = ""; 195 | socket.on("data", function(data) { 196 | buf += data.toString("binary"); 197 | while (size || buf.length >= 4) { 198 | if (size == null) { 199 | // read BERP length header and adjust buffer 200 | size = bytes_to_int(buf, 4); 201 | buf = buf.substring(4); 202 | } else if (buf.length >= size) { 203 | // TODO: improve error handling 204 | // should take care of: 205 | // * incorrect BERT-packet 206 | // * call exception 207 | try { 208 | callback(size, bert.decode(buf.substring(0, size))); 209 | } catch (e) { 210 | BERTRPC.logger.error(e); 211 | } 212 | buf = buf.substring(size); 213 | size = null; 214 | } else { 215 | // nothing more we can do 216 | break; 217 | } 218 | } 219 | }); 220 | }, 221 | 222 | // Write the object specified by the second argument to the 223 | // socket or file descriptor in the first argument. This 224 | // BERT encodes the term and writes the result on the socket with 225 | // a four byte BERP length header. 226 | write: function (socket, term) { 227 | var data = bert.encode(term), 228 | // don't use raw strings to send binary data 229 | packet = new Buffer(int_to_bytes(data.length, 4) + data, "binary"); 230 | socket.write(packet); 231 | return data.length; 232 | } 233 | }; 234 | 235 | exports = module.exports = BERTRPC; 236 | 237 | // vim: ts=2 sw=2 expandtab 238 | -------------------------------------------------------------------------------- /test/test_bert_atom.js: -------------------------------------------------------------------------------- 1 | var nodeunit = require('nodeunit'); 2 | var bert = require('../src/bert'); 3 | 4 | exports.testAtomRepr = function(test) { 5 | var obj = bert.atom("hello"); 6 | test.strictEqual(bert.repr(obj), "hello"); 7 | test.done(); 8 | }; 9 | 10 | exports.testAtomEncode = function(test) { 11 | var data = bert.encode(bert.atom("hello")); 12 | test.strictEqual(bert.bin_repr(data), "<<131,100,0,5,104,101,108,108,111>>"); 13 | test.done(); 14 | }; 15 | 16 | exports.testAtomDecode = function(test) { 17 | var data = bert.encode(bert.atom("hello")); 18 | var obj = bert.decode(data); 19 | test.strictEqual(obj.type, 'atom'); 20 | test.strictEqual(obj.toString(), 'hello'); 21 | test.done(); 22 | }; -------------------------------------------------------------------------------- /test/test_bert_biginteger.js: -------------------------------------------------------------------------------- 1 | var nodeunit = require('nodeunit'); 2 | var util = require('util'); 3 | var bert = require('../src/bert'); 4 | 5 | exports.testBigIntegerRepr = function(test) { 6 | test.strictEqual(bert.repr(28421841298),'28421841298'); 7 | test.strictEqual(bert.repr(-28421841298),'-28421841298'); 8 | test.done(); 9 | }; 10 | 11 | var bigIntegersEncoded = {}; 12 | 13 | bigIntegersEncoded[987654321] = "<<131,110,4,0,177,104,222,58>>"; 14 | bigIntegersEncoded[-987654321] = "<<131,110,4,1,177,104,222,58>>"; 15 | 16 | exports.testBigIntegerEncode = function(test) { 17 | for (var number in bigIntegersEncoded) { 18 | if (bigIntegersEncoded.hasOwnProperty(number)) { 19 | // use the + notation to cast the strings in the hash key 20 | // back to integers 21 | var actual = bert.encode(+number); 22 | test.strictEqual(bert.bin_repr(actual), bigIntegersEncoded[number]); 23 | } 24 | } 25 | test.done(); 26 | }; 27 | 28 | exports.testBigIntegerDecode = function(test) { 29 | for (var number in bigIntegersEncoded) { 30 | // use the + notation to cast the strings in the hash key 31 | // back to integers 32 | var actual = bert.decode(bert.encode(+number)); 33 | test.strictEqual(actual, +number); 34 | } 35 | test.done(); 36 | }; 37 | -------------------------------------------------------------------------------- /test/test_bert_binary.js: -------------------------------------------------------------------------------- 1 | var nodeunit = require('nodeunit'); 2 | var bert = require('../src/bert'); 3 | 4 | exports.testBinaryRepr = function(test) { 5 | var obj = "hello"; 6 | test.strictEqual(bert.repr(obj), '<<"hello">>'); 7 | test.done(); 8 | }; 9 | 10 | exports.testBinaryEncode = function(test) { 11 | var data = bert.encode("hello"); 12 | test.strictEqual(bert.bin_repr(data), "<<131,109,0,0,0,5,104,101,108,108,111>>"); 13 | test.done(); 14 | }; 15 | 16 | exports.testBinaryDecode = function(test) { 17 | var data = bert.encode("hello"); 18 | test.strictEqual(bert.decode(data), "hello"); 19 | test.done(); 20 | }; 21 | 22 | -------------------------------------------------------------------------------- /test/test_bert_boolean.js: -------------------------------------------------------------------------------- 1 | var nodeunit = require('nodeunit'); 2 | var bert = require('../src/bert'); 3 | 4 | exports.testBooleanRepr = function(test) { 5 | test.strictEqual(bert.repr(true), ''); 6 | test.done(); 7 | }; 8 | 9 | exports.testBooleanEncode = function(test) { 10 | var obj = bert.encode(true); 11 | test.strictEqual(bert.bin_repr(obj), "<<131,104,2,100,0,4,98,101,114,116,100,0,4,116,114,117,101>>"); 12 | test.done(); 13 | }; 14 | 15 | exports.testBooleanDecode = function(test) { 16 | var obj = bert.encode(true); 17 | test.strictEqual(bert.decode(obj), true); 18 | test.done(); 19 | }; 20 | 21 | -------------------------------------------------------------------------------- /test/test_bert_dictionary.js: -------------------------------------------------------------------------------- 1 | var nodeunit = require('nodeunit'); 2 | var bert = require('../src/bert'); 3 | 4 | exports.testDictRepr = function(test) { 5 | test.strictEqual(bert.repr({a:1, b:2, c:3}), "[{a, 1}, {b, 2}, {c, 3}]"); 6 | test.done(); 7 | }; 8 | 9 | exports.testDictEncode = function(test) { 10 | var data = bert.encode({a:1, b:2, c:3}); 11 | var expected = "<<131,104,3,100,0,4,98,101,114,116,100,0,4,100,105,99,116,108,0,0,0,3,104,2,109,0,0,0,1,97,97,1,104,2,109,0,0,0,1,98,97,2,104,2,109,0,0,0,1,99,97,3,106>>"; 12 | test.strictEqual(bert.bin_repr(data), expected); 13 | test.done(); 14 | }; 15 | 16 | exports.testDictDecode = function(test) { 17 | var data = bert.encode({a:1, b:2, c:3}); 18 | var obj = bert.decode(data); 19 | test.strictEqual(obj['a'], 1); 20 | test.strictEqual(obj['b'], 2); 21 | test.strictEqual(obj['c'], 3); 22 | test.done(); 23 | }; 24 | 25 | exports.testComplexDictRepr = function(test) { 26 | var obj = { a: bert.tuple(1, 2, 3), b: [4, 5, 6] }; 27 | test.strictEqual(bert.repr(obj), "[{a, {1, 2, 3}}, {b, [4, 5, 6]}]"); 28 | test.done(); 29 | }; 30 | 31 | exports.testComplexDictEncode = function(test) { 32 | var obj = { a: bert.tuple(1, 2, 3), b: [4, 5, 6] }; 33 | var data = bert.encode(obj); 34 | var expected = "<<131,104,3,100,0,4,98,101,114,116,100,0,4,100,105,99,116,108,0,0,0,2,104,2,109,0,0,0,1,97,104,3,97,1,97,2,97,3,104,2,109,0,0,0,1,98,108,0,0,0,3,97,4,97,5,97,6,106,106>>"; 35 | test.strictEqual(bert.bin_repr(data), expected); 36 | test.done(); 37 | }; 38 | 39 | exports.testComplexDictDecode = function(test) { 40 | var data = bert.encode({ a: bert.tuple(1, 2, 3), b: [4, 5, 6] }); 41 | var obj = bert.decode(data); 42 | test.strictEqual(typeof(obj), 'object'); 43 | test.strictEqual(obj.a.type, 'tuple'); 44 | test.strictEqual(obj.a[0], 1); 45 | test.strictEqual(obj.a[1], 2); 46 | test.strictEqual(obj.a[2], 3); 47 | test.done(); 48 | }; 49 | -------------------------------------------------------------------------------- /test/test_bert_float.js: -------------------------------------------------------------------------------- 1 | var nodeunit = require('nodeunit'); 2 | var util = require('util'); 3 | var bert = require('../src/bert'); 4 | 5 | exports.testFloatRepr = function(test) { 6 | test.strictEqual(bert.repr(3.14159),'3.14159'); 7 | test.strictEqual(bert.repr(-3.14159),'-3.14159'); 8 | test.done(); 9 | }; 10 | 11 | var floatsEncoded = {}; 12 | 13 | floatsEncoded[3.14159] = "<<131,99,51,46,49,52,49,53,57,101,43,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>"; 14 | floatsEncoded[-3.14159] = "<<131,99,45,51,46,49,52,49,53,57,101,43,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>"; 15 | 16 | exports.testFloatEncode = function(test) { 17 | for (var number in floatsEncoded) { 18 | if (floatsEncoded.hasOwnProperty(number)) { 19 | // use the + notation to cast the strings in the hash key 20 | // back to integers 21 | var actual = bert.encode(+number); 22 | test.strictEqual(bert.bin_repr(actual), floatsEncoded[number]); 23 | } 24 | } 25 | test.done(); 26 | }; 27 | 28 | exports.testFloatDecode = function(test) { 29 | for (var number in floatsEncoded) { 30 | // use the + notation to cast the strings in the hash key 31 | // back to integers 32 | var actual = bert.decode(bert.encode(+number)); 33 | test.strictEqual(actual, +number); 34 | } 35 | test.done(); 36 | }; 37 | 38 | exports.testNewFloatDecode = function(test) { 39 | pi = 3.14159; 40 | piEncoded = new Buffer([131, 70, 64, 9, 33, 249, 240, 27, 134, 110]); 41 | var actual = bert.decode_buffer(piEncoded); 42 | test.strictEqual(actual, pi); 43 | test.done(); 44 | }; 45 | -------------------------------------------------------------------------------- /test/test_bert_integer.js: -------------------------------------------------------------------------------- 1 | var nodeunit = require('nodeunit'); 2 | var util = require('util'); 3 | var bert = require('../src/bert'); 4 | 5 | exports.testIntegerRepr = function(test) { 6 | test.strictEqual(bert.repr(42),'42'); 7 | test.strictEqual(bert.repr(-42),'-42'); 8 | test.done(); 9 | }; 10 | 11 | var integersEncoded = {}; 12 | 13 | integersEncoded[42] = "<<131,97,42>>"; 14 | integersEncoded[144] = "<<131,97,144>>"; 15 | integersEncoded[-144] = "<<131,98,255,255,255,112>>"; 16 | integersEncoded[5000] = "<<131,98,0,0,19,136>>"; 17 | integersEncoded[-5000] = "<<131,98,255,255,236,120>>"; 18 | 19 | exports.testIntegerEncode = function(test) { 20 | for (var number in integersEncoded) { 21 | if (integersEncoded.hasOwnProperty(number)) { 22 | // use the + notation to cast the strings in the hash key 23 | // back to integers 24 | var actual = bert.encode(+number); 25 | test.strictEqual(bert.bin_repr(actual), integersEncoded[number]); 26 | } 27 | } 28 | test.done(); 29 | }; 30 | 31 | exports.testIntegerDecode = function(test) { 32 | for (var number in integersEncoded) { 33 | // use the + notation to cast the strings in the hash key 34 | // back to integers 35 | var actual = bert.decode(bert.encode(+number)); 36 | test.strictEqual(actual, +number); 37 | } 38 | test.done(); 39 | }; 40 | -------------------------------------------------------------------------------- /test/test_bert_list.js: -------------------------------------------------------------------------------- 1 | var nodeunit = require('nodeunit'); 2 | var bert = require('../src/bert'); 3 | 4 | exports.testListRepr = function(test) { 5 | var data = bert.repr([1,2,3]); 6 | test.strictEqual(data, "[1, 2, 3]"); 7 | test.done(); 8 | }; 9 | 10 | exports.testListEncode = function(test) { 11 | var data = bert.encode([1, 2, 3]); 12 | test.strictEqual(bert.bin_repr(data), "<<131,108,0,0,0,3,97,1,97,2,97,3,106>>"); 13 | test.done(); 14 | }; 15 | 16 | exports.testListDecode = function(test) { 17 | var data = bert.encode([1,2,3]); 18 | var obj = bert.decode(data); 19 | test.strictEqual(typeof(obj), 'object'); 20 | test.strictEqual(obj.length, 3); 21 | test.strictEqual(obj[0], 1); 22 | test.strictEqual(obj[1], 2); 23 | test.strictEqual(obj[2], 3); 24 | test.done(); 25 | }; 26 | 27 | exports.testEmptyListRepr = function(test) { 28 | test.strictEqual('[]', bert.repr([])); 29 | test.done(); 30 | }; 31 | 32 | exports.testEmptyListEncode = function(test) { 33 | var data = bert.encode([]); 34 | test.strictEqual(bert.bin_repr(data), "<<131,108,0,0,0,0,106>>"); 35 | test.done(); 36 | }; 37 | 38 | exports.testEmptyListDecode = function(test) { 39 | var data = bert.encode([]); 40 | var obj = bert.decode(data); 41 | test.strictEqual(obj.length, 0); 42 | test.done(); 43 | }; -------------------------------------------------------------------------------- /test/test_bert_tuple.js: -------------------------------------------------------------------------------- 1 | var nodeunit = require('nodeunit'); 2 | var bert = require('../src/bert'); 3 | 4 | exports.testTupleRepr = function(test) { 5 | var obj = bert.tuple("Hello", 1); 6 | test.strictEqual(bert.repr(obj), '{<<"Hello">>, 1}'); 7 | test.done(); 8 | }; 9 | 10 | exports.testTupleEncode = function(test) { 11 | var data = bert.encode(bert.tuple("Hello", 1)); 12 | test.strictEqual(bert.bin_repr(data), "<<131,104,2,109,0,0,0,5,72,101,108,108,111,97,1>>") 13 | test.done(); 14 | }; 15 | 16 | exports.testTupleDecode = function(test) { 17 | var data = bert.encode(bert.tuple("Hello", 1)); 18 | var obj = bert.decode(data); 19 | test.strictEqual(obj.type, 'tuple'); 20 | test.strictEqual(obj.length, 2); 21 | test.strictEqual(obj[0], "Hello"); 22 | test.strictEqual(obj[1], 1); 23 | test.done(); 24 | }; -------------------------------------------------------------------------------- /test/test_bertrpc.js: -------------------------------------------------------------------------------- 1 | var nodeunit = require('nodeunit'); 2 | 3 | var bertrpc = require('../src/bertrpc'); 4 | var bert = require('../src/bert'); 5 | 6 | var TestModule = { 7 | foo: function (a, b) { 8 | return ['foo', 'bar', a, b]; 9 | }, 10 | 11 | other: 'hello' 12 | }; 13 | 14 | exports.testExpose = function(test) { 15 | bertrpc.expose('test', TestModule); 16 | test.equals(bertrpc.modules.test, TestModule); 17 | test.done(); 18 | }; 19 | 20 | exports.testDispatch = function(test) { 21 | var res = bertrpc.dispatch('call', 'test', 'foo', [1, 2]); 22 | test.strictEqual(res[0], 'foo'); 23 | test.strictEqual(res[1], 'bar'); 24 | test.strictEqual(res[2], 1); 25 | test.strictEqual(res[3], 2); 26 | test.done(); 27 | }; 28 | 29 | //exports.testDispatchMissingArgs = function(test) { 30 | // test.throws(function() { 31 | // bertrpc.dispatch('call', 'test', 'foo', []); 32 | // }); 33 | // test.done(); 34 | //}; 35 | 36 | 37 | exports.testDispatchBadModule = function(test) { 38 | test.throws(function() { 39 | bertrpc.dispatch('call', 'foo', 'bar', []); 40 | }); 41 | test.done(); 42 | }; 43 | 44 | exports.testDispatchBadAttribute = function(test) { 45 | test.throws(function() { 46 | bertrpc.dispatch('call', 'test', 'bar', []); 47 | }); 48 | test.done(); 49 | }; 50 | 51 | exports.testDispatchNonFunction = function(test) { 52 | test.throws(function() { 53 | bertrpc.dispatch('call', 'test', 'other', []); 54 | }); 55 | test.done(); 56 | }; 57 | 58 | exports.testReadBerpFromFD = function(test) { 59 | var packet = bert.encode('hi'); 60 | var did = 0; 61 | var berp = bert.int_to_bytes(packet.length, 4) + packet; 62 | 63 | var fd = { 64 | on: function (event, callback) { 65 | test.equals(event, 'data'); 66 | callback(berp + berp + berp); 67 | } 68 | }; 69 | 70 | bertrpc.read(fd, function (size, term) { 71 | test.strictEqual(size, packet.length); 72 | test.equals(term, 'hi'); 73 | did++; 74 | }); 75 | 76 | test.strictEqual(did, 3); 77 | test.done(); 78 | }; 79 | 80 | exports.testWriteBerpToFD = function(test) { 81 | var buf = ''; 82 | var fd = { 83 | write: function (data) { 84 | buf += data.toString('binary'); 85 | } 86 | }; 87 | 88 | bertrpc.write(fd, 'hello world'); 89 | var packet = bert.encode('hello world'); 90 | var berp = bert.int_to_bytes(packet.length, 4) + packet; 91 | test.strictEqual(buf, berp); 92 | test.done(); 93 | }; 94 | 95 | --------------------------------------------------------------------------------