├── src ├── _end.js ├── _begin.js ├── _begin_node.js ├── _end_node.js ├── error.js ├── endian_aware_dataview.js ├── rsrv.js ├── robj.js ├── write.js ├── parse.js └── rserve.js ├── .gitignore ├── tests ├── r_files │ ├── start │ ├── no_oc.conf │ ├── start_no_ocap │ ├── oc.conf │ ├── regular_start.R │ ├── oc_start.R │ └── oc.init.R ├── package.json ├── run_all_tests.sh ├── ocap_tests.js └── no_ocap_tests.js ├── package.json ├── LICENSE ├── Makefile ├── README.md ├── rserve.min.js ├── rserve.js └── main.js /src/_end.js: -------------------------------------------------------------------------------- 1 | this.Rserve = Rserve; 2 | })(); 3 | -------------------------------------------------------------------------------- /src/_begin.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var Rserve = {}; 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | node_modules 3 | tests/ocap_tests/js/node_modules 4 | -------------------------------------------------------------------------------- /src/_begin_node.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | var WebSocket = require('ws'); 3 | 4 | -------------------------------------------------------------------------------- /tests/r_files/start: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | R --slave --no-restore --vanilla --file="r_files/oc_start.R" 3 | -------------------------------------------------------------------------------- /src/_end_node.js: -------------------------------------------------------------------------------- 1 | module.exports = Rserve; 2 | // (function () { delete this.Rserve; })(); // unset global 3 | -------------------------------------------------------------------------------- /tests/r_files/no_oc.conf: -------------------------------------------------------------------------------- 1 | websockets.port 8081 2 | websockets enable 3 | oob enable 4 | http.port 8080 5 | 6 | -------------------------------------------------------------------------------- /tests/r_files/start_no_ocap: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | R --slave --no-restore --vanilla --file="r_files/regular_start.R" 3 | -------------------------------------------------------------------------------- /tests/r_files/oc.conf: -------------------------------------------------------------------------------- 1 | websockets.port 8081 2 | websockets enable 3 | oob enable 4 | http.port 8080 5 | qap disable 6 | websockets.qap.oc enable 7 | -------------------------------------------------------------------------------- /tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.2", 4 | "description": "tests", 5 | "keywords": [ "aslkdjfhrserve" ], 6 | "dependencies": { 7 | "rserve": "1.0.2" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/error.js: -------------------------------------------------------------------------------- 1 | Rserve.RserveError = function(message, status_code) { 2 | this.name = "RserveError"; 3 | this.message = message; 4 | this.status_code = status_code; 5 | }; 6 | 7 | Rserve.RserveError.prototype = Object.create(Error); 8 | Rserve.RserveError.prototype.constructor = Rserve.RserveError; 9 | -------------------------------------------------------------------------------- /tests/r_files/regular_start.R: -------------------------------------------------------------------------------- 1 | ## check that RCloud is properly installed 2 | ##installed <- gsub(".*/([^/]+)/DESCRIPTION$","\\1",Sys.glob(paste0(.libPaths(),"/*/DESCRIPTION"))) 3 | 4 | debug <- FALSE # isTRUE(nzchar(Sys.getenv("DEBUG"))) 5 | Rserve::Rserve(debug, args=c("--RS-conf", "r_files/no_oc.conf", "--vanilla", "--no-save")) 6 | -------------------------------------------------------------------------------- /tests/r_files/oc_start.R: -------------------------------------------------------------------------------- 1 | ## check that RCloud is properly installed 2 | ##installed <- gsub(".*/([^/]+)/DESCRIPTION$","\\1",Sys.glob(paste0(.libPaths(),"/*/DESCRIPTION"))) 3 | 4 | debug <- FALSE # isTRUE(nzchar(Sys.getenv("DEBUG"))) 5 | Rserve::Rserve(debug, args=c("--RS-conf", "r_files/oc.conf", "--RS-source", "r_files/oc.init.R", "--vanilla", "--no-save")) 6 | -------------------------------------------------------------------------------- /tests/run_all_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo This test suite will kill all running instances of Rserve! 4 | 5 | killall Rserve 6 | echo Starting no-ocap rserve.. 7 | ./r_files/start_no_ocap 8 | sleep 2 9 | node no_ocap_tests.js 10 | 11 | killall Rserve 12 | echo Starting ocap rserve.. 13 | ./r_files/start 14 | sleep 2 15 | node ocap_tests.js # --debug-brk 16 | killall Rserve 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rserve", 3 | "version": "2.0.0", 4 | "description": "rserve-js provides a javascript implementation of the websocket Rserve protocol", 5 | "author": "Carlos Scheidegger (http://cscheid.net)", 6 | "main": "main.js", 7 | "homepage": "http://github.com/cscheid/rserve-js", 8 | "bugs": "http://github.com/cscheid/rserve-js/issues", 9 | "repository": { 10 | "type": "git", 11 | "url": "http://github.com/cscheid/rserve-js" 12 | }, 13 | "license": "EPL 1.0 or GPL >= 2.0", 14 | "keywords": [ "rserve", "websockets" ], 15 | "devDependencies": { 16 | "uglify-js": "2.6.0" 17 | }, 18 | "dependencies": { 19 | "ws": "0.4.27", 20 | "bluebird": "1.0.3", 21 | "underscore": "1.5.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2014 AT&T Intellectual Property 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | JS_COMPILER = ./node_modules/uglify-js/bin/uglifyjs 2 | 3 | all: rserve.js rserve.min.js main.js 4 | 5 | main.js: \ 6 | src/_begin.js \ 7 | src/_begin_node.js \ 8 | src/robj.js \ 9 | src/rsrv.js \ 10 | src/parse.js \ 11 | src/endian_aware_dataview.js \ 12 | src/rserve.js \ 13 | src/error.js \ 14 | src/write.js \ 15 | src/_end.js \ 16 | src/_end_node.js 17 | 18 | rserve.js: \ 19 | src/_begin.js \ 20 | src/robj.js \ 21 | src/rsrv.js \ 22 | src/parse.js \ 23 | src/endian_aware_dataview.js \ 24 | src/rserve.js \ 25 | src/error.js \ 26 | src/write.js \ 27 | src/_end.js 28 | 29 | rserve.min.js: rserve.js Makefile 30 | @rm -f $@ 31 | $(JS_COMPILER) < $< > $@ 32 | chmod -w $@ 33 | 34 | rserve.js: Makefile 35 | echo $^ 36 | @rm -f $@ 37 | cat $(filter %.js,$^) > $@ 38 | ifeq ($(CHECK),1) 39 | jshint $(filter %.js,$(filter-out lib/%.js,$(filter-out %/_begin.js,$(filter-out %/_end.js, $^)))) 40 | endif 41 | chmod -w $@ 42 | 43 | main.js: Makefile 44 | echo $^ 45 | @rm -f $@ 46 | cat $(filter %.js,$^) > $@ 47 | ifeq ($(CHECK),1) 48 | jshint $(filter %.js,$(filter-out lib/%.js,$(filter-out %/_begin.js,$(filter-out %/_end.js, $^)))) 49 | endif 50 | chmod -w $@ 51 | 52 | clean: 53 | rm -f rserve.js rserve.min.js main.js 54 | -------------------------------------------------------------------------------- /tests/r_files/oc.init.R: -------------------------------------------------------------------------------- 1 | library(Rserve) 2 | 3 | wrap.js.fun <- function(s) 4 | { 5 | if (class(s) != "javascript_function") 6 | stop("Can only wrap javascript_function s"); 7 | function(...) { 8 | self.oobMessage(list(s, ...)) 9 | } 10 | } 11 | 12 | wrap.r.fun <- Rserve:::ocap 13 | 14 | give.first.functions <- function() 15 | { 16 | x <- 3 17 | javascript.function <- NULL 18 | naked.javascript.function <- NULL 19 | cat("INIT!\n") 20 | list(tfail=wrap.r.fun(function(v) { 21 | stop("hammertime") 22 | }, "tfail"), t1=wrap.r.fun(function(v) { 23 | cat("UP!\n") 24 | x <<- x + v 25 | x 26 | }, "t1"), t2=wrap.r.fun(function(v) { 27 | cat("DOWN!\n") 28 | x <<- x - v 29 | x 30 | }, "t2"), t3=wrap.r.fun(function(v) { 31 | javascript.function <<- wrap.js.fun(v) 32 | TRUE 33 | }, "t3"), t4=wrap.r.fun(function(v) { 34 | javascript.function(v) 35 | }, "t4"), t5=wrap.r.fun(function(v) { 36 | naked.javascript.function <<- v 37 | NULL 38 | }, "t5"), t6=wrap.r.fun(function(v) { 39 | list(naked.javascript.function, v) 40 | }, "t6")) 41 | } 42 | 43 | #################################################################################################### 44 | # make.oc turns a function into an object capability accessible from the remote side 45 | 46 | # oc.init must return the first capability accessible to the remote side 47 | oc.init <- function() 48 | { 49 | wrap.r.fun(give.first.functions) 50 | } 51 | -------------------------------------------------------------------------------- /tests/ocap_tests.js: -------------------------------------------------------------------------------- 1 | r = require('../main.js'); 2 | Promise = require('bluebird'); 3 | 4 | s = r.create({ 5 | host: 'http://127.0.0.1:8081', 6 | on_connect: test 7 | }); 8 | 9 | function test() 10 | { 11 | var ocap = s.ocap, funs; 12 | ocap(function(err, funs) { 13 | funs = Promise.promisifyAll(funs); 14 | 15 | funs.tfailAsync(null).then(function() { 16 | throw new Error("This should have failed"); 17 | }).catch(function(e) { 18 | console.log("Nice!"); 19 | var lst = [ 20 | function() { return funs.t1Async(5); }, 21 | function() { return funs.t2Async(4); }, 22 | function() { return funs.t3Async(function(x, k) { k(null, 21 + x); }); }, 23 | function() { return funs.t4Async(5).then(function(v) { 24 | if (v !== 26) 25 | throw new Error("test failed."); 26 | }); }, 27 | function() { return funs.t5Async(function(i) { return i * i; }); }, 28 | function() { return funs.t6Async(5).then(function(v) { 29 | var f = v[0], i = v[1]; 30 | if (f(i) !== 25) 31 | throw new Error("test failed."); 32 | }); }, 33 | function() { process.exit(0); } 34 | ]; 35 | var chain = lst[0](); 36 | for (var i=1; i r = require('../main.js') 27 | { Robj: 28 | ... 29 | write_into_view: [Function] } 30 | 31 | > r = r.create() 32 | { ocap_mode: false, 33 | ... 34 | resolve_hash: [Function] } 35 | 36 | > r.running 37 | true 38 | 39 | > r.eval('rnorm(10)', function(err, a) { if (err === null) console.log(a); }) 40 | undefined 41 | 42 | { type: 'sexp', 43 | value: 44 | { type: 'double_array', 45 | value: 46 | { '0': -1.5626166190555, 47 | '1': -0.16678360090204197, 48 | '2': 1.362470594733813, 49 | '3': 0.2462241937647293, 50 | '4': -0.6439588002729958, 51 | '5': 1.6695940797441013, 52 | '6': -0.8298271898727629, 53 | '7': -0.14431491982950537, 54 | '8': -0.05561817220786299, 55 | '9': -1.5889826020213365, 56 | BYTES_PER_ELEMENT: 8, 57 | get: [Function: get], 58 | set: [Function: set], 59 | slice: [Function: slice], 60 | subarray: [Function: subarray], 61 | buffer: [Object], 62 | length: 10, 63 | byteOffset: 24, 64 | byteLength: 80 }, 65 | attributes: undefined } } 66 | 67 | 68 | ## Security considerations 69 | 70 | *NB: Rserve, in the mode described above, should only be run in 71 | trusted networks!* `eval`, in that example above, is truly `eval`: 72 | 73 | > // RUNNING WITH SCISSORS 74 | > r.eval('readLines(pipe("ls /etc"))', function(err, x) { if (err === null) console.log(x); }) 75 | 76 | { type: 'sexp', 77 | value: 78 | { type: 'string_array', 79 | value: 80 | [ ... 81 | 'apache2', 82 | ... ], 83 | attributes: undefined } } 84 | 85 | Thankfully, Rserve provides a mode which only exposes a fixed set of 86 | entry points. These are known (for 87 | [historical reasons](http://en.wikipedia.org/wiki/Object-capability_model)) 88 | as *object capabilities*. 89 | 90 | 91 | ## Object capabilities 92 | 93 | There's a demo of object-capability support in `tests/ocap_tests.js` 94 | and `tests/oc.init.R`. Roughly speaking, in object-capability mode, 95 | the server involves an initialization function that returns an object. 96 | This object is sent and is accessible by the Javascript side. 97 | 98 | Critically, the serialization process converts any R functions and 99 | closures to *opaque* objects, which in Javascript are converted to 100 | things that behave exactly like asynchronous function calls 101 | (eg. [`XmlHttpRequest`](http://www.w3.org/TR/XMLHttpRequest/)). These 102 | callable objects are known as *capabilities*, since each of them is a 103 | very specific feature accessible by the remote system. Since 104 | capabilities are functions, they return new values. And since both 105 | Javascript and R have functions as first-class values, *capabilities* 106 | are also first-class in this system. Capabilities 107 | can return other capabilities, and so a system can be designed to 108 | provide, by default, a very limited set of features, which can be 109 | increased when appropriate. 110 | 111 | -------------------------------------------------------------------------------- /src/endian_aware_dataview.js: -------------------------------------------------------------------------------- 1 | // we want an endian aware dataview mostly because ARM can be big-endian, and 2 | // that might put us in trouble wrt handheld devices. 3 | ////////////////////////////////////////////////////////////////////////////// 4 | 5 | (function() { 6 | var _is_little_endian; 7 | 8 | (function() { 9 | var x = new ArrayBuffer(4); 10 | var bytes = new Uint8Array(x), 11 | words = new Uint32Array(x); 12 | bytes[0] = 1; 13 | if (words[0] === 1) { 14 | _is_little_endian = true; 15 | } else if (words[0] === 16777216) { 16 | _is_little_endian = false; 17 | } else { 18 | throw "we're bizarro endian, refusing to continue"; 19 | } 20 | })(); 21 | 22 | Rserve.EndianAwareDataView = (function() { 23 | 24 | var proto = { 25 | 'setInt8': function(i, v) { return this.view.setInt8(i, v); }, 26 | 'setUint8': function(i, v) { return this.view.setUint8(i, v); }, 27 | 'getInt8': function(i) { return this.view.getInt8(i); }, 28 | 'getUint8': function(i) { return this.view.getUint8(i); } 29 | }; 30 | 31 | var setters = ['setInt32', 'setInt16', 'setUint32', 'setUint16', 32 | 'setFloat32', 'setFloat64']; 33 | var getters = ['getInt32', 'getInt16', 'getUint32', 'getUint16', 34 | 'getFloat32', 'getFloat64']; 35 | 36 | for (var i=0; i this.buffer.byteLength) 107 | throw new Error("Rserve.my_ArrayBufferView.view: bounds error: size: " + 108 | this.buffer.byteLength + " offset: " + ofs + " length: " + new_length); 109 | return Rserve.my_ArrayBufferView( 110 | this.buffer, ofs, new_length); 111 | } 112 | }; 113 | }; 114 | 115 | })(this); 116 | -------------------------------------------------------------------------------- /src/rsrv.js: -------------------------------------------------------------------------------- 1 | // Simple constants and functions are defined here, 2 | // in correspondence with Rserve's Rsrv.h 3 | 4 | Rserve.Rsrv = { 5 | PAR_TYPE: function(x) { return x & 255; }, 6 | PAR_LEN: function(x) { return x >>> 8; }, 7 | PAR_LENGTH: function(x) { return x >>> 8; }, 8 | par_parse: function(x) { return [Rserve.Rsrv.PAR_TYPE(x), Rserve.Rsrv.PAR_LEN(x)]; }, 9 | SET_PAR: function(ty, len) { return ((len & 0xffffff) << 8 | (ty & 255)); }, 10 | CMD_STAT: function(x) { return (x >>> 24) & 127; }, 11 | SET_STAT: function(x, s) { return x | ((s & 127) << 24); }, 12 | 13 | IS_OOB_SEND: function(x) { return (x & 0xffff000) === Rserve.Rsrv.OOB_SEND; }, 14 | IS_OOB_MSG: function(x) { return (x & 0xffff000) === Rserve.Rsrv.OOB_MSG; }, 15 | OOB_USR_CODE: function(x) { return x & 0xfff; }, 16 | 17 | CMD_RESP : 0x10000, 18 | RESP_OK : 0x10000 | 0x0001, 19 | RESP_ERR : 0x10000 | 0x0002, 20 | OOB_SEND : 0x20000 | 0x1000, 21 | OOB_MSG : 0x20000 | 0x2000, 22 | ERR_auth_failed : 0x41, 23 | ERR_conn_broken : 0x42, 24 | ERR_inv_cmd : 0x43, 25 | ERR_inv_par : 0x44, 26 | ERR_Rerror : 0x45, 27 | ERR_IOerror : 0x46, 28 | ERR_notOpen : 0x47, 29 | ERR_accessDenied : 0x48, 30 | ERR_unsupportedCmd : 0x49, 31 | ERR_unknownCmd : 0x4a, 32 | ERR_data_overflow : 0x4b, 33 | ERR_object_too_big : 0x4c, 34 | ERR_out_of_mem : 0x4d, 35 | ERR_ctrl_closed : 0x4e, 36 | ERR_session_busy : 0x50, 37 | ERR_detach_failed : 0x51, 38 | ERR_disabled : 0x61, 39 | ERR_unavailable : 0x62, 40 | ERR_cryptError : 0x63, 41 | ERR_securityClose : 0x64, 42 | 43 | CMD_login : 0x001, 44 | CMD_voidEval : 0x002, 45 | CMD_eval : 0x003, 46 | CMD_shutdown : 0x004, 47 | CMD_switch : 0x005, 48 | CMD_keyReq : 0x006, 49 | CMD_secLogin : 0x007, 50 | CMD_OCcall : 0x00f, 51 | CMD_openFile : 0x010, 52 | CMD_createFile : 0x011, 53 | CMD_closeFile : 0x012, 54 | CMD_readFile : 0x013, 55 | CMD_writeFile : 0x014, 56 | CMD_removeFile : 0x015, 57 | CMD_setSEXP : 0x020, 58 | CMD_assignSEXP : 0x021, 59 | CMD_detachSession : 0x030, 60 | CMD_detachedVoidEval : 0x031, 61 | CMD_attachSession : 0x032, 62 | CMD_ctrl : 0x40, 63 | CMD_ctrlEval : 0x42, 64 | CMD_ctrlSource : 0x45, 65 | CMD_ctrlShutdown : 0x44, 66 | CMD_setBufferSize : 0x081, 67 | CMD_setEncoding : 0x082, 68 | CMD_SPECIAL_MASK : 0xf0, 69 | CMD_serEval : 0xf5, 70 | CMD_serAssign : 0xf6, 71 | CMD_serEEval : 0xf7, 72 | 73 | DT_INT : 1, 74 | DT_CHAR : 2, 75 | DT_DOUBLE : 3, 76 | DT_STRING : 4, 77 | DT_BYTESTREAM : 5, 78 | DT_SEXP : 10, 79 | DT_ARRAY : 11, 80 | DT_LARGE : 64, 81 | 82 | XT_NULL : 0, 83 | XT_INT : 1, 84 | XT_DOUBLE : 2, 85 | XT_STR : 3, 86 | XT_LANG : 4, 87 | XT_SYM : 5, 88 | XT_BOOL : 6, 89 | XT_S4 : 7, 90 | XT_VECTOR : 16, 91 | XT_LIST : 17, 92 | XT_CLOS : 18, 93 | XT_SYMNAME : 19, 94 | XT_LIST_NOTAG : 20, 95 | XT_LIST_TAG : 21, 96 | XT_LANG_NOTAG : 22, 97 | XT_LANG_TAG : 23, 98 | XT_VECTOR_EXP : 26, 99 | XT_VECTOR_STR : 27, 100 | XT_ARRAY_INT : 32, 101 | XT_ARRAY_DOUBLE : 33, 102 | XT_ARRAY_STR : 34, 103 | XT_ARRAY_BOOL_UA : 35, 104 | XT_ARRAY_BOOL : 36, 105 | XT_RAW : 37, 106 | XT_ARRAY_CPLX : 38, 107 | XT_UNKNOWN : 48, 108 | XT_LARGE : 64, 109 | XT_HAS_ATTR : 128, 110 | 111 | BOOL_TRUE : 1, 112 | BOOL_FALSE : 0, 113 | BOOL_NA : 2, 114 | 115 | GET_XT: function(x) { return x & 63; }, 116 | GET_DT: function(x) { return x & 63; }, 117 | HAS_ATTR: function(x) { return (x & Rserve.Rsrv.XT_HAS_ATTR) > 0; }, 118 | IS_LARGE: function(x) { return (x & Rserve.Rsrv.XT_LARGE) > 0; }, 119 | 120 | // # FIXME A WHOLE LOT OF MACROS HERE WHICH ARE PROBABLY IMPORTANT 121 | // ############################################################################## 122 | 123 | status_codes: { 124 | 0x41 : "ERR_auth_failed" , 125 | 0x42 : "ERR_conn_broken" , 126 | 0x43 : "ERR_inv_cmd" , 127 | 0x44 : "ERR_inv_par" , 128 | 0x45 : "ERR_Rerror" , 129 | 0x46 : "ERR_IOerror" , 130 | 0x47 : "ERR_notOpen" , 131 | 0x48 : "ERR_accessDenied" , 132 | 0x49 : "ERR_unsupportedCmd", 133 | 0x4a : "ERR_unknownCmd" , 134 | 0x4b : "ERR_data_overflow" , 135 | 0x4c : "ERR_object_too_big", 136 | 0x4d : "ERR_out_of_mem" , 137 | 0x4e : "ERR_ctrl_closed" , 138 | 0x50 : "ERR_session_busy" , 139 | 0x51 : "ERR_detach_failed" , 140 | 0x61 : "ERR_disabled" , 141 | 0x62 : "ERR_unavailable" , 142 | 0x63 : "ERR_cryptError" , 143 | 0x64 : "ERR_securityClose" 144 | } 145 | }; 146 | -------------------------------------------------------------------------------- /src/robj.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | function make_basic(type, proto) { 4 | proto = proto || { 5 | json: function() { 6 | throw "json() unsupported for type " + this.type; 7 | } 8 | }; 9 | var wrapped_proto = { 10 | json: function(resolver) { 11 | var result = proto.json.call(this, resolver); 12 | result.r_type = type; 13 | if (!_.isUndefined(this.attributes)) 14 | result.r_attributes = _.object(_.map( 15 | this.attributes.value, 16 | function(v) { return [v.name, v.value.json(resolver)]; })); 17 | return result; 18 | } 19 | }; 20 | return function(v, attrs) { 21 | function r_object() { 22 | this.type = type; 23 | this.value = v; 24 | this.attributes = attrs; 25 | } 26 | r_object.prototype = wrapped_proto; 27 | var result = new r_object(); 28 | return result; 29 | }; 30 | } 31 | 32 | Rserve.Robj = { 33 | "null": function(attributes) { 34 | return { 35 | type: "null", 36 | value: null, 37 | attributes: attributes, 38 | json: function() { return null; } 39 | }; 40 | }, 41 | 42 | clos: function(formals, body, attributes) { 43 | return { 44 | type: "clos", 45 | value: { formals: formals, 46 | body: body }, 47 | attributes: attributes, 48 | json: function() { throw "json() unsupported for type clos"; } 49 | }; 50 | }, 51 | 52 | vector: make_basic("vector", { 53 | json: function(resolver) { 54 | var values = _.map(this.value, function (x) { return x.json(resolver); }); 55 | if (_.isUndefined(this.attributes)) { 56 | return values; 57 | } else { 58 | // FIXME: there is no reason why names should be the first or only 59 | // attribute, so the code should really look 60 | // for "names" and not cry if it doesn't exist 61 | if (this.attributes.value[0].name == "names") { 62 | var keys = this.attributes.value[0].value.value; 63 | var result = {}; 64 | _.each(keys, function(key, i) { 65 | result[key] = values[i]; 66 | }); 67 | return result; 68 | } 69 | // FIXME: how can we pass other important attributes 70 | // like "class" ? 71 | return values; 72 | } 73 | } 74 | }), 75 | symbol: make_basic("symbol", { 76 | json: function() { 77 | return this.value; 78 | } 79 | }), 80 | list: make_basic("list"), 81 | lang: make_basic("lang", { 82 | json: function(resolver) { 83 | var values = _.map(this.value, function (x) { return x.json(resolver); }); 84 | if (_.isUndefined(this.attributes)) { 85 | return values; 86 | } else { 87 | // FIXME: lang doens't have "names" attribute since 88 | // names are sent as tags (langs are pairlists) 89 | // so this seems superfluous (it is dangerous 90 | // if lang ever had attributes since there is 91 | // no reason to fail in that case) 92 | if(this.attributes.value[0].name!="names") 93 | throw "expected names here"; 94 | var keys = this.attributes.value[0].value.value; 95 | var result = {}; 96 | _.each(keys, function(key, i) { 97 | result[key] = values[i]; 98 | }); 99 | return result; 100 | } 101 | } 102 | }), 103 | tagged_list: make_basic("tagged_list", { 104 | json: function(resolver) { 105 | function classify_list(list) { 106 | if (_.all(list, function(elt) { return elt.name === null; })) { 107 | return "plain_list"; 108 | } else if (_.all(list, function(elt) { return elt.name !== null; })) { 109 | return "plain_object"; 110 | } else 111 | return "mixed_list"; 112 | } 113 | var list = this.value.slice(1); 114 | switch (classify_list(list)) { 115 | case "plain_list": 116 | return _.map(list, function(elt) { return elt.value.json(resolver); }); 117 | case "plain_object": 118 | return _.object(_.map(list, function(elt) { 119 | return [elt.name, elt.value.json(resolver)]; 120 | })); 121 | case "mixed_list": 122 | return list; 123 | default: 124 | throw "Internal Error"; 125 | } 126 | } 127 | }), 128 | tagged_lang: make_basic("tagged_lang", { 129 | json: function(resolver) { 130 | var pair_vec = _.map(this.value, function(elt) { return [elt.name, elt.value.json(resolver)]; }); 131 | return pair_vec; 132 | } 133 | }), 134 | vector_exp: make_basic("vector_exp"), 135 | int_array: make_basic("int_array", { 136 | json: function() { 137 | if(this.attributes && this.attributes.type==='tagged_list' 138 | && this.attributes.value[0].name==='levels' 139 | && this.attributes.value[0].value.type==='string_array') { 140 | var levels = this.attributes.value[0].value.value; 141 | var arr = _.map(this.value, function(factor) { return levels[factor-1]; }); 142 | arr.levels = levels; 143 | return arr; 144 | } 145 | else { 146 | if (this.value.length === 1) 147 | return this.value[0]; 148 | else 149 | return this.value; 150 | } 151 | } 152 | }), 153 | double_array: make_basic("double_array", { 154 | json: function() { 155 | if (this.value.length === 1 && _.isUndefined(this.attributes)) 156 | return this.value[0]; 157 | else 158 | return this.value; 159 | } 160 | }), 161 | string_array: make_basic("string_array", { 162 | json: function(resolver) { 163 | if (this.value.length === 1) { 164 | if (_.isUndefined(this.attributes)) 165 | return this.value[0]; 166 | if (this.attributes.value[0].name === 'class' && 167 | this.attributes.value[0].value.value.indexOf("javascript_function") !== -1) 168 | return resolver(this.value[0]); 169 | return this.value; 170 | } else 171 | return this.value; 172 | } 173 | }), 174 | bool_array: make_basic("bool_array", { 175 | json: function() { 176 | if (this.value.length === 1 && _.isUndefined(this.attributes)) 177 | return this.value[0]; 178 | else 179 | return this.value; 180 | } 181 | }), 182 | raw: make_basic("raw", { 183 | json: function() { 184 | if (this.value.length === 1 && _.isUndefined(this.attributes)) 185 | return this.value[0]; 186 | else 187 | return this.value; 188 | } 189 | }), 190 | string: make_basic("string", { 191 | json: function() { 192 | return this.value; 193 | } 194 | }) 195 | }; 196 | 197 | })(); 198 | -------------------------------------------------------------------------------- /src/write.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | _.mixin({ 4 | isTypedArray: function(v) { 5 | if (!_.isObject(v)) 6 | return false; 7 | return !_.isUndefined(v.byteLength) && !_.isUndefined(v.BYTES_PER_ELEMENT); 8 | } 9 | }); 10 | 11 | // type_id tries to match some javascript values to Rserve value types 12 | Rserve.type_id = function(value) 13 | { 14 | if (_.isNull(value) || _.isUndefined(value)) 15 | return Rserve.Rsrv.XT_NULL; 16 | var type_dispatch = { 17 | "boolean": Rserve.Rsrv.XT_ARRAY_BOOL, 18 | "number": Rserve.Rsrv.XT_ARRAY_DOUBLE, 19 | "string": Rserve.Rsrv.XT_ARRAY_STR // base strings need to be array_str or R gets confused? 20 | }; 21 | if (!_.isUndefined(type_dispatch[typeof value])) 22 | return type_dispatch[typeof value]; 23 | 24 | // typed arrays 25 | if (_.isTypedArray(value)) 26 | return Rserve.Rsrv.XT_ARRAY_DOUBLE; 27 | 28 | // arraybuffers 29 | if (!_.isUndefined(value.byteLength) && !_.isUndefined(value.slice)) 30 | return Rserve.Rsrv.XT_RAW; 31 | 32 | // lists of strings (important for tags) 33 | if (_.isArray(value) && _.all(value, function(el) { return typeof el === 'string'; })) 34 | return Rserve.Rsrv.XT_ARRAY_STR; 35 | 36 | if (_.isArray(value) && _.all(value, function(el) { return typeof el === 'boolean'; })) 37 | return Rserve.Rsrv.XT_ARRAY_BOOL; 38 | 39 | // arbitrary lists 40 | if (_.isArray(value)) 41 | return Rserve.Rsrv.XT_VECTOR; 42 | 43 | // functions get passed as an array_str with extra attributes 44 | if (_.isFunction(value)) 45 | return Rserve.Rsrv.XT_ARRAY_STR | Rserve.Rsrv.XT_HAS_ATTR; 46 | 47 | // objects 48 | if (_.isObject(value)) 49 | return Rserve.Rsrv.XT_VECTOR | Rserve.Rsrv.XT_HAS_ATTR; 50 | 51 | throw new Rserve.RServeError("Value type unrecognized by Rserve: " + value); 52 | }; 53 | 54 | // FIXME this is really slow, as it's walking the object many many times. 55 | Rserve.determine_size = function(value, forced_type) 56 | { 57 | function list_size(lst) { 58 | return _.reduce(lst, function(memo, el) { 59 | return memo + Rserve.determine_size(el); 60 | }, 0); 61 | } 62 | function final_size(payload_size) { 63 | if (payload_size > (1 << 24)) 64 | return payload_size + 8; // large header 65 | else 66 | return payload_size + 4; 67 | } 68 | var header_size = 4, t = forced_type || Rserve.type_id(value); 69 | 70 | switch (t & ~Rserve.Rsrv.XT_LARGE) { 71 | case Rserve.Rsrv.XT_NULL: 72 | return final_size(0); 73 | case Rserve.Rsrv.XT_ARRAY_BOOL: 74 | if (_.isBoolean(value)) 75 | return final_size(8); 76 | else 77 | return final_size((value.length + 7) & ~3); 78 | case Rserve.Rsrv.XT_ARRAY_STR: 79 | if (_.isArray(value)) 80 | return final_size(_.reduce(value, function(memo, str) { 81 | // FIXME: this is a bit silly, since we'll be re-encoding this twice: once for the size and second time for the content 82 | var utf8 = unescape(encodeURIComponent(str)); 83 | return memo + utf8.length + 1; 84 | }, 0)); 85 | else { 86 | var utf8 = unescape(encodeURIComponent(value)); 87 | return final_size(utf8.length + 1); 88 | } 89 | case Rserve.Rsrv.XT_ARRAY_DOUBLE: 90 | if (_.isNumber(value)) 91 | return final_size(8); 92 | else 93 | return final_size(8 * value.length); 94 | case Rserve.Rsrv.XT_RAW: 95 | return final_size(4 + value.byteLength); 96 | case Rserve.Rsrv.XT_VECTOR: 97 | case Rserve.Rsrv.XT_LANG_NOTAG: 98 | return final_size(list_size(value)); 99 | case Rserve.Rsrv.XT_VECTOR | Rserve.Rsrv.XT_HAS_ATTR: // a named list (that is, a js object) 100 | var names_size_1 = final_size("names".length + 3); 101 | var names_size_2 = Rserve.determine_size(_.keys(value)); 102 | var names_size = final_size(names_size_1 + names_size_2); 103 | return final_size(names_size + list_size(_.values(value))); 104 | /* return header_size // XT_VECTOR | XT_HAS_ATTR 105 | + header_size // XT_LIST_TAG (attribute) 106 | + header_size + "names".length + 3 // length of 'names' + padding (tag as XT_SYMNAME) 107 | + Rserve.determine_size(_.keys(value)) // length of names 108 | + list_size(_.values(value)); // length of values 109 | */ 110 | case Rserve.Rsrv.XT_ARRAY_STR | Rserve.Rsrv.XT_HAS_ATTR: // js->r ocap (that is, a js function) 111 | return Rserve.determine_size("0403556553") // length of ocap nonce; that number is meaningless aside from length 112 | + header_size // XT_LIST_TAG (attribute) 113 | + header_size + "class".length + 3 // length of 'class' + padding (tag as XT_SYMNAME) 114 | + Rserve.determine_size(["javascript_function"]); // length of class name 115 | 116 | default: 117 | throw new Rserve.RserveError("Internal error, can't handle type " + t); 118 | } 119 | }; 120 | 121 | Rserve.write_into_view = function(value, array_buffer_view, forced_type, convert) 122 | { 123 | var size = Rserve.determine_size(value, forced_type); 124 | var is_large = size > 16777215; 125 | // if (size > 16777215) 126 | // throw new Rserve.RserveError("Can't currently handle objects >16MB"); 127 | var t = forced_type || Rserve.type_id(value), i, current_offset, lbl; 128 | if (is_large) 129 | t = t | Rserve.Rsrv.XT_LARGE; 130 | var read_view; 131 | var write_view = array_buffer_view.data_view(); 132 | var payload_start; 133 | if (is_large) { 134 | payload_start = 8; 135 | write_view.setInt32(0, t + ((size - 8) << 8)); 136 | write_view.setInt32(4, (size - 8) >>> 24); 137 | } else { 138 | payload_start = 4; 139 | write_view.setInt32(0, t + ((size - 4) << 8)); 140 | } 141 | 142 | switch (t & ~Rserve.Rsrv.XT_LARGE) { 143 | case Rserve.Rsrv.XT_NULL: 144 | break; 145 | case Rserve.Rsrv.XT_ARRAY_BOOL: 146 | if (_.isBoolean(value)) { 147 | write_view.setInt32(payload_start, 1); 148 | write_view.setInt8(payload_start + 4, value ? 1 : 0); 149 | } else { 150 | write_view.setInt32(payload_start, value.length); 151 | for (i=0; i 0) { 26 | var t = f.call(that, attributes, length); 27 | result.push(t[0]); 28 | length -= t[1]; 29 | } 30 | return [result, old_length]; 31 | }; 32 | } 33 | 34 | var that = { 35 | offset: 0, 36 | data_view: m.make(Rserve.EndianAwareDataView), 37 | msg: m, 38 | 39 | ////////////////////////////////////////////////////////////////////// 40 | 41 | read_int: function() { 42 | var old_offset = this.offset; 43 | this.offset += 4; 44 | return this.data_view.getInt32(old_offset); 45 | }, 46 | read_string: function(length) { 47 | // FIXME SLOW 48 | var result = ""; 49 | while (length--) { 50 | var c = this.data_view.getInt8(this.offset++); 51 | if (c) result = result + String.fromCharCode(c); 52 | } 53 | return decodeURIComponent(escape(result)); // UTF-8 to UTF-16 54 | }, 55 | read_stream: function(length) { 56 | var old_offset = this.offset; 57 | this.offset += length; 58 | return this.msg.view(old_offset, length); 59 | }, 60 | read_int_vector: function(length) { 61 | var old_offset = this.offset; 62 | this.offset += length; 63 | return this.msg.make(Int32Array, old_offset, length); 64 | }, 65 | read_double_vector: function(length) { 66 | var old_offset = this.offset; 67 | this.offset += length; 68 | return this.msg.make(Float64Array, old_offset, length); 69 | }, 70 | 71 | ////////////////////////////////////////////////////////////////////// 72 | // these are members of the reader monad 73 | 74 | read_null: lift(function(a, l) { return Rserve.Robj.null(a); }), 75 | 76 | read_unknown: lift(function(a, l) { 77 | this.offset += l; 78 | return Rserve.Robj.null(a); 79 | }), 80 | 81 | read_string_array: function(attributes, length) { 82 | var a = this.read_stream(length).make(Uint8Array); 83 | var result = []; 84 | var current_str = ""; 85 | for (var i=0; i>> 24; 216 | var msg_id = header[2]; 217 | result.header = [resp, status_code, msg_id]; 218 | 219 | if (resp === Rserve.Rsrv.RESP_ERR) { 220 | result.ok = false; 221 | result.status_code = status_code; 222 | result.message = "ERROR FROM R SERVER: " + (Rserve.Rsrv.status_codes[status_code] || 223 | status_code) 224 | + " " + result.header[0] + " " + result.header[1] 225 | + " " + msg.byteLength 226 | + " " + msg; 227 | return result; 228 | } 229 | 230 | if (!( resp === Rserve.Rsrv.RESP_OK || Rserve.Rsrv.IS_OOB_SEND(resp) || Rserve.Rsrv.IS_OOB_MSG(resp))) { 231 | result.ok = false; 232 | result.message = "Unexpected response from Rserve: " + resp + " status: " + Rserve.Rsrv.status_codes[status_code]; 233 | return result; 234 | } 235 | try { 236 | result.payload = parse_payload(msg); 237 | result.ok = true; 238 | } catch (e) { 239 | result.ok = false; 240 | result.message = e.message; 241 | } 242 | return result; 243 | } 244 | 245 | function parse_payload(msg) 246 | { 247 | var payload = Rserve.my_ArrayBufferView(msg, 16, msg.byteLength - 16); 248 | if (payload.length === 0) 249 | return null; 250 | 251 | var reader = read(payload); 252 | 253 | var d = reader.read_int(); 254 | var _ = Rserve.Rsrv.par_parse(d); 255 | var t = _[0], l = _[1]; 256 | if (Rserve.Rsrv.IS_LARGE(t)) { 257 | var more_length = reader.read_int(); 258 | l += more_length * Math.pow(2, 24); 259 | if (l > (Math.pow(2, 32))) { // resist the 1 << 32 temptation here! 260 | // total_length is greater than 2^32.. bail out because of node limits 261 | // even though in theory we could go higher than that. 262 | throw new Error("Payload too large: " + l + " bytes"); 263 | } 264 | t &= ~64; 265 | } 266 | if (t === Rserve.Rsrv.DT_INT) { 267 | return { type: "int", value: reader.read_int() }; 268 | } else if (t === Rserve.Rsrv.DT_STRING) { 269 | return { type: "string", value: reader.read_string(l) }; 270 | } else if (t === Rserve.Rsrv.DT_BYTESTREAM) { // NB this returns a my_ArrayBufferView() 271 | return { type: "stream", value: reader.read_stream(l) }; 272 | } else if (t === Rserve.Rsrv.DT_SEXP) { 273 | _ = reader.read_sexp(); 274 | var sexp = _[0], l2 = _[1]; 275 | return { type: "sexp", value: sexp }; 276 | } else 277 | throw new Rserve.RserveError("Bad type for parse? " + t + " " + l, -1); 278 | } 279 | 280 | Rserve.parse_websocket_frame = parse; 281 | Rserve.parse_payload = parse_payload; 282 | 283 | })(); 284 | -------------------------------------------------------------------------------- /src/rserve.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | function _encode_command(command, buffer, msg_id) { 4 | if (!_.isArray(buffer)) 5 | buffer = [buffer]; 6 | if (!msg_id) msg_id = 0; 7 | var length = _.reduce(buffer, 8 | function(memo, val) { 9 | return memo + val.byteLength; 10 | }, 0), 11 | big_buffer = new ArrayBuffer(16 + length), 12 | view = new Rserve.EndianAwareDataView(big_buffer); 13 | view.setInt32(0, command); 14 | view.setInt32(4, length); 15 | view.setInt32(8, msg_id); 16 | view.setInt32(12, 0); 17 | var offset = 16; 18 | _.each(buffer, function(b) { 19 | var source_array = new Uint8Array(b); 20 | for (var i=0; i= 2^31. 87 | if (sz > 16777215) { 88 | var buffer = new ArrayBuffer(sz + 8); 89 | var view = Rserve.my_ArrayBufferView(buffer); 90 | // can't left shift value here because value will have bit 32 set and become signed.. 91 | view.data_view().setInt32(0, Rserve.Rsrv.DT_SEXP + ((sz & 16777215) * Math.pow(2, 8)) + Rserve.Rsrv.DT_LARGE); 92 | // but *can* right shift because we assume sz is less than 2^31 or so to begin with 93 | view.data_view().setInt32(4, sz >>> 24); 94 | Rserve.write_into_view(value, view.skip(8), forced_type, convert_to_hash); 95 | return buffer; 96 | } else { 97 | var buffer = new ArrayBuffer(sz + 4); 98 | var view = Rserve.my_ArrayBufferView(buffer); 99 | view.data_view().setInt32(0, Rserve.Rsrv.DT_SEXP + (sz << 8)); 100 | Rserve.write_into_view(value, view.skip(4), forced_type, convert_to_hash); 101 | return buffer; 102 | } 103 | } 104 | 105 | function hand_shake(msg) 106 | { 107 | msg = msg.data; 108 | if (typeof msg === 'string') { 109 | if (msg.substr(0,4) !== 'Rsrv') { 110 | handle_error("server is not an RServe instance", -1); 111 | } else if (msg.substr(4, 4) !== '0103') { 112 | handle_error("sorry, rserve only speaks the 0103 version of the R server protocol", -1); 113 | } else if (msg.substr(8, 4) !== 'QAP1') { 114 | handle_error("sorry, rserve only speaks QAP1", -1); 115 | } else { 116 | received_handshake = true; 117 | if (opts.login) 118 | result.login(opts.login); 119 | result.running = true; 120 | onconnect && onconnect.call(result); 121 | } 122 | } else { 123 | var view = new DataView(msg); 124 | var header = String.fromCharCode(view.getUint8(0)) + 125 | String.fromCharCode(view.getUint8(1)) + 126 | String.fromCharCode(view.getUint8(2)) + 127 | String.fromCharCode(view.getUint8(3)); 128 | 129 | if (header === 'RsOC') { 130 | received_handshake = true; 131 | result.ocap_mode = true; 132 | result.bare_ocap = Rserve.parse_payload(msg).value; 133 | result.ocap = Rserve.wrap_ocap(result, result.bare_ocap); 134 | result.running = true; 135 | onconnect && onconnect.call(result); 136 | } else 137 | handle_error("Unrecognized server answer: " + header, -1); 138 | } 139 | } 140 | 141 | socket.onclose = function(msg) { 142 | result.running = false; 143 | result.closed = true; 144 | opts.on_close && opts.on_close(msg); 145 | }; 146 | 147 | socket.onmessage = function(msg) { 148 | // node.js Buffer vs ArrayBuffer workaround 149 | if (msg.data.constructor.name === 'Buffer') 150 | msg.data = (new Uint8Array(msg.data)).buffer; 151 | if (opts.debug) 152 | opts.debug.message_in && opts.debug.message_in(msg); 153 | if (!received_handshake) { 154 | hand_shake(msg); 155 | return; 156 | } 157 | if (typeof msg.data === 'string') { 158 | opts.on_raw_string && opts.on_raw_string(msg.data); 159 | return; 160 | } 161 | var v = Rserve.parse_websocket_frame(msg.data); 162 | var msg_id = v.header[2], cmd = v.header[0] & 0xffffff; 163 | var queue = _.find(queues, function(queue) { return queue.msg_id == msg_id; }); 164 | // console.log("onmessage, queue=" + (queue ? queue.name : "") + ", ok= " + v.ok+ ", cmd=" + cmd +", msg_id="+ msg_id); 165 | // FIXME: in theory we should not need a fallback, but in case we miss some 166 | // odd edge case, we revert to the old behavior. 167 | // The way things work, the queue will be undefined only for OOB messages: 168 | // SEND doesn't need reply, so it's irrelevant, MSG is handled separately below and 169 | // enforces the right queue. 170 | if (!queue) queue = queues[0]; 171 | if (!v.ok) { 172 | queue.result_callback([v.message, v.status_code], undefined); 173 | // handle_error(v.message, v.status_code); 174 | } else if (cmd === Rserve.Rsrv.RESP_OK) { 175 | queue.result_callback(null, v.payload); 176 | } else if (Rserve.Rsrv.IS_OOB_SEND(cmd)) { 177 | opts.on_data && opts.on_data(v.payload); 178 | } else if (Rserve.Rsrv.IS_OOB_MSG(cmd)) { 179 | // OOB MSG may use random msg_id, so we have to use the USR_CODE to detect the right queue 180 | // FIXME: we may want to consider adjusting the protocol specs to require msg_id 181 | // to be retained by OOB based on the outer OCcall message (thus inheriting 182 | // the msg_id), but curretnly it's not mandated. 183 | queue = (Rserve.Rsrv.OOB_USR_CODE(cmd) > 255) ? compute_queue : ctrl_queue; 184 | // console.log("OOB MSG result on queue "+ queue.name); 185 | if (result.ocap_mode) { 186 | var p; 187 | try { 188 | p = Rserve.wrap_all_ocaps(result, v.payload); // .value.json(result.resolve_hash); 189 | } catch (e) { 190 | _send_cmd_now(Rserve.Rsrv.RESP_ERR | cmd, 191 | _encode_string(String(e)), msg_id); 192 | return; 193 | } 194 | if (!_.isFunction(p[0])) { 195 | _send_cmd_now(Rserve.Rsrv.RESP_ERR | cmd, 196 | _encode_string("OOB Messages on ocap-mode must be javascript function calls"), msg_id); 197 | return; 198 | } 199 | var captured_function = p[0], params = p.slice(1); 200 | params.push(function(err, result) { 201 | if (err) { 202 | _send_cmd_now(Rserve.Rsrv.RESP_ERR | cmd, _encode_value(err), msg_id); 203 | } else { 204 | _send_cmd_now(cmd, _encode_value(result), msg_id); 205 | } 206 | }); 207 | captured_function.apply(undefined, params); 208 | } else { 209 | if (_.isUndefined(opts.on_oob_message)) { 210 | _send_cmd_now(Rserve.Rsrv.RESP_ERR | cmd, 211 | _encode_string("No handler installed"), msg_id); 212 | } else { 213 | queue.in_oob_message = true; 214 | opts.on_oob_message(v.payload, function(message, error) { 215 | if (!queue.in_oob_message) { 216 | handle_error("Don't call oob_message_handler more than once."); 217 | return; 218 | } 219 | queue.in_oob_message = false; 220 | var header = cmd | 221 | (error ? Rserve.Rsrv.RESP_ERR : Rserve.Rsrv.RESP_OK); 222 | _send_cmd_now(header, _encode_string(message), msg_id); 223 | bump_queues(); 224 | }); 225 | } 226 | } 227 | } else { 228 | handle_error("Internal Error, parse returned unexpected type " + v.header[0], -1); 229 | } 230 | }; 231 | 232 | function _send_cmd_now(command, buffer, msg_id) { 233 | var big_buffer = _encode_command(command, buffer, msg_id); 234 | if (opts.debug) 235 | opts.debug.message_out && opts.debug.message_out(big_buffer[0], command); 236 | socket.send(big_buffer); 237 | return big_buffer; 238 | }; 239 | 240 | var ctrl_queue = { 241 | queue: [], 242 | in_oob_message: false, 243 | awaiting_result: false, 244 | msg_id: 0, 245 | name: "control" 246 | }; 247 | 248 | var compute_queue = { 249 | queue: [], 250 | in_oob_message: false, 251 | awaiting_result: false, 252 | msg_id: 1, 253 | name: "compute" 254 | }; 255 | 256 | // the order matters - the first queue is used if the association cannot be determined from the msg_id/cmd 257 | var queues = [ ctrl_queue, compute_queue ]; 258 | 259 | function queue_can_send(queue) { return !queue.in_oob_message && !queue.awaiting_result && queue.queue.length; } 260 | 261 | function bump_queues() { 262 | var available = _.filter(queues, queue_can_send); 263 | // nothing in the queues (or all busy)? get out 264 | if (!available.length) return; 265 | if (result.closed) { 266 | handle_error("Cannot send messages on a closed socket!", -1); 267 | } else { 268 | var queue = _.sortBy(available, function(queue) { return queue.queue[0].timestamp; })[0]; 269 | var lst = queue.queue.shift(); 270 | queue.result_callback = lst.callback; 271 | queue.awaiting_result = true; 272 | if (opts.debug) 273 | opts.debug.message_out && opts.debug.message_out(lst.buffer, lst.command); 274 | socket.send(lst.buffer); 275 | } 276 | } 277 | 278 | function enqueue(buffer, k, command, queue) { 279 | queue.queue.push({ 280 | buffer: buffer, 281 | callback: function(error, result) { 282 | queue.awaiting_result = false; 283 | bump_queues(); 284 | k(error, result); 285 | }, 286 | command: command, 287 | timestamp: Date.now() 288 | }); 289 | bump_queues(); 290 | }; 291 | 292 | function _cmd(command, buffer, k, string, queue) { 293 | // default to the first queue - only used in non-OCAP mode which doesn't support multiple queues 294 | if (!queue) queue = queues[0]; 295 | 296 | k = k || function() {}; 297 | var big_buffer = _encode_command(command, buffer, queue.msg_id); 298 | return enqueue(big_buffer, k, string, queue); 299 | }; 300 | 301 | result = { 302 | ocap_mode: false, 303 | running: false, 304 | closed: false, 305 | close: function() { 306 | socket.close(); 307 | }, 308 | 309 | ////////////////////////////////////////////////////////////////////// 310 | // non-ocap mode 311 | 312 | login: function(command, k) { 313 | _cmd(Rserve.Rsrv.CMD_login, _encode_string(command), k, command); 314 | }, 315 | eval: function(command, k) { 316 | _cmd(Rserve.Rsrv.CMD_eval, _encode_string(command), k, command); 317 | }, 318 | createFile: function(command, k) { 319 | _cmd(Rserve.Rsrv.CMD_createFile, _encode_string(command), k, command); 320 | }, 321 | writeFile: function(chunk, k) { 322 | _cmd(Rserve.Rsrv.CMD_writeFile, _encode_bytes(chunk), k, ""); 323 | }, 324 | closeFile: function(k) { 325 | _cmd(Rserve.Rsrv.CMD_closeFile, new ArrayBuffer(0), k, ""); 326 | }, 327 | set: function(key, value, k) { 328 | _cmd(Rserve.Rsrv.CMD_setSEXP, [_encode_string(key), _encode_value(value)], k, ""); 329 | }, 330 | 331 | ////////////////////////////////////////////////////////////////////// 332 | // ocap mode 333 | 334 | OCcall: function(ocap, values, k) { 335 | var is_ocap = false, str; 336 | try { 337 | is_ocap |= ocap.r_attributes['class'] === 'OCref'; 338 | str = ocap[0]; 339 | } catch (e) {}; 340 | if(!is_ocap) { 341 | try { 342 | is_ocap |= ocap.attributes.value[0].value.value[0] === 'OCref'; 343 | str = ocap.value[0]; 344 | } catch (e) {}; 345 | } 346 | if (!is_ocap) { 347 | k(new Error("Expected an ocap, instead got " + ocap), undefined); 348 | return; 349 | } 350 | var params = [str]; 351 | params.push.apply(params, values); 352 | // determine the proper queue from the OCAP prefix 353 | var queue = (str.charCodeAt(0) == 64) ? compute_queue : ctrl_queue; 354 | _cmd(Rserve.Rsrv.CMD_OCcall, _encode_value(params, Rserve.Rsrv.XT_LANG_NOTAG), 355 | k, "", queue); 356 | }, 357 | 358 | wrap_ocap: function(ocap) { 359 | return Rserve.wrap_ocap(this, ocap); 360 | }, 361 | 362 | resolve_hash: function(hash) { 363 | if (!(hash in captured_functions)) 364 | throw new Error("hash " + hash + " not found."); 365 | return captured_functions[hash]; 366 | } 367 | }; 368 | return result; 369 | }; 370 | 371 | Rserve.wrap_all_ocaps = function(s, v) { 372 | v = v.value.json(s.resolve_hash); 373 | function replace(obj) { 374 | var result = obj; 375 | if (_.isArray(obj) && 376 | obj.r_attributes && 377 | obj.r_attributes['class'] == 'OCref') { 378 | return Rserve.wrap_ocap(s, obj); 379 | } else if (_.isArray(obj)) { 380 | result = _.map(obj, replace); 381 | result.r_type = obj.r_type; 382 | result.r_attributes = obj.r_attributes; 383 | } else if (_.isTypedArray(obj)) { 384 | return obj; 385 | } else if (_.isFunction(obj)) { 386 | return obj; 387 | } else if (_.isObject(obj)) { 388 | result = _.object(_.map(obj, function(v, k) { 389 | return [k, replace(v)]; 390 | })); 391 | } 392 | return result; 393 | } 394 | return replace(v); 395 | }; 396 | 397 | Rserve.wrap_ocap = function(s, ocap) { 398 | var wrapped_ocap = function() { 399 | var values = _.toArray(arguments); 400 | // common error (tho this won't catch the case where last arg is a function) 401 | if(!values.length || !_.isFunction(values[values.length-1])) 402 | throw new Error("forgot to pass continuation to ocap"); 403 | var k = values.pop(); 404 | s.OCcall(ocap, values, function(err, v) { 405 | if (!_.isUndefined(v)) 406 | v = Rserve.wrap_all_ocaps(s, v); 407 | k(err, v); 408 | }); 409 | }; 410 | wrapped_ocap.bare_ocap = ocap; 411 | return wrapped_ocap; 412 | }; 413 | 414 | })(); 415 | -------------------------------------------------------------------------------- /rserve.min.js: -------------------------------------------------------------------------------- 1 | !function(){var Rserve={};!function(){function make_basic(type,proto){proto=proto||{json:function(){throw"json() unsupported for type "+this.type}};var wrapped_proto={json:function(resolver){var result=proto.json.call(this,resolver);result.r_type=type;if(!_.isUndefined(this.attributes))result.r_attributes=_.object(_.map(this.attributes.value,function(v){return[v.name,v.value.json(resolver)]}));return result}};return function(v,attrs){function r_object(){this.type=type;this.value=v;this.attributes=attrs}r_object.prototype=wrapped_proto;var result=new r_object;return result}}Rserve.Robj={"null":function(attributes){return{type:"null",value:null,attributes:attributes,json:function(){return null}}},clos:function(formals,body,attributes){return{type:"clos",value:{formals:formals,body:body},attributes:attributes,json:function(){throw"json() unsupported for type clos"}}},vector:make_basic("vector",{json:function(resolver){var values=_.map(this.value,function(x){return x.json(resolver)});if(_.isUndefined(this.attributes)){return values}else{if(this.attributes.value[0].name!="names")throw"expected names here";var keys=this.attributes.value[0].value.value;var result={};_.each(keys,function(key,i){result[key]=values[i]});return result}}}),symbol:make_basic("symbol",{json:function(){return this.value}}),list:make_basic("list"),lang:make_basic("lang",{json:function(resolver){var values=_.map(this.value,function(x){return x.json(resolver)});if(_.isUndefined(this.attributes)){return values}else{if(this.attributes.value[0].name!="names")throw"expected names here";var keys=this.attributes.value[0].value.value;var result={};_.each(keys,function(key,i){result[key]=values[i]});return result}}}),tagged_list:make_basic("tagged_list",{json:function(resolver){function classify_list(list){if(_.all(list,function(elt){return elt.name===null})){return"plain_list"}else if(_.all(list,function(elt){return elt.name!==null})){return"plain_object"}else return"mixed_list"}var list=this.value.slice(1);switch(classify_list(list)){case"plain_list":return _.map(list,function(elt){return elt.value.json(resolver)});case"plain_object":return _.object(_.map(list,function(elt){return[elt.name,elt.value.json(resolver)]}));case"mixed_list":return list;default:throw"Internal Error"}}}),tagged_lang:make_basic("tagged_lang",{json:function(resolver){var pair_vec=_.map(this.value,function(elt){return[elt.name,elt.value.json(resolver)]});return pair_vec}}),vector_exp:make_basic("vector_exp"),int_array:make_basic("int_array",{json:function(){if(this.attributes&&this.attributes.type==="tagged_list"&&this.attributes.value[0].name==="levels"&&this.attributes.value[0].value.type==="string_array"){var levels=this.attributes.value[0].value.value;var arr=_.map(this.value,function(factor){return levels[factor-1]});arr.levels=levels;return arr}else{if(this.value.length===1)return this.value[0];else return this.value}}}),double_array:make_basic("double_array",{json:function(){if(this.value.length===1&&_.isUndefined(this.attributes))return this.value[0];else return this.value}}),string_array:make_basic("string_array",{json:function(resolver){if(this.value.length===1){if(_.isUndefined(this.attributes))return this.value[0];if(this.attributes.value[0].name==="class"&&this.attributes.value[0].value.value.indexOf("javascript_function")!==-1)return resolver(this.value[0]);return this.value}else return this.value}}),bool_array:make_basic("bool_array",{json:function(){if(this.value.length===1&&_.isUndefined(this.attributes))return this.value[0];else return this.value}}),raw:make_basic("raw",{json:function(){if(this.value.length===1&&_.isUndefined(this.attributes))return this.value[0];else return this.value}}),string:make_basic("string",{json:function(){return this.value}})}}();Rserve.Rsrv={PAR_TYPE:function(x){return x&255},PAR_LEN:function(x){return x>>8},PAR_LENGTH:function(x){return x>>8},par_parse:function(x){return[Rserve.Rsrv.PAR_TYPE(x),Rserve.Rsrv.PAR_LEN(x)]},SET_PAR:function(ty,len){return(len&16777215)<<8|ty&255},CMD_STAT:function(x){return x>>24&127},SET_STAT:function(x,s){return x|(s&127)<<24},CMD_RESP:65536,RESP_OK:65536|1,RESP_ERR:65536|2,OOB_SEND:131072|4096,OOB_MSG:131072|8192,ERR_auth_failed:65,ERR_conn_broken:66,ERR_inv_cmd:67,ERR_inv_par:68,ERR_Rerror:69,ERR_IOerror:70,ERR_notOpen:71,ERR_accessDenied:72,ERR_unsupportedCmd:73,ERR_unknownCmd:74,ERR_data_overflow:75,ERR_object_too_big:76,ERR_out_of_mem:77,ERR_ctrl_closed:78,ERR_session_busy:80,ERR_detach_failed:81,ERR_disabled:97,ERR_unavailable:98,ERR_cryptError:99,ERR_securityClose:100,CMD_login:1,CMD_voidEval:2,CMD_eval:3,CMD_shutdown:4,CMD_switch:5,CMD_keyReq:6,CMD_secLogin:7,CMD_OCcall:15,CMD_openFile:16,CMD_createFile:17,CMD_closeFile:18,CMD_readFile:19,CMD_writeFile:20,CMD_removeFile:21,CMD_setSEXP:32,CMD_assignSEXP:33,CMD_detachSession:48,CMD_detachedVoidEval:49,CMD_attachSession:50,CMD_ctrl:64,CMD_ctrlEval:66,CMD_ctrlSource:69,CMD_ctrlShutdown:68,CMD_setBufferSize:129,CMD_setEncoding:130,CMD_SPECIAL_MASK:240,CMD_serEval:245,CMD_serAssign:246,CMD_serEEval:247,DT_INT:1,DT_CHAR:2,DT_DOUBLE:3,DT_STRING:4,DT_BYTESTREAM:5,DT_SEXP:10,DT_ARRAY:11,DT_LARGE:64,XT_NULL:0,XT_INT:1,XT_DOUBLE:2,XT_STR:3,XT_LANG:4,XT_SYM:5,XT_BOOL:6,XT_S4:7,XT_VECTOR:16,XT_LIST:17,XT_CLOS:18,XT_SYMNAME:19,XT_LIST_NOTAG:20,XT_LIST_TAG:21,XT_LANG_NOTAG:22,XT_LANG_TAG:23,XT_VECTOR_EXP:26,XT_VECTOR_STR:27,XT_ARRAY_INT:32,XT_ARRAY_DOUBLE:33,XT_ARRAY_STR:34,XT_ARRAY_BOOL_UA:35,XT_ARRAY_BOOL:36,XT_RAW:37,XT_ARRAY_CPLX:38,XT_UNKNOWN:48,XT_LARGE:64,XT_HAS_ATTR:128,BOOL_TRUE:1,BOOL_FALSE:0,BOOL_NA:2,GET_XT:function(x){return x&63},GET_DT:function(x){return x&63},HAS_ATTR:function(x){return(x&Rserve.Rsrv.XT_HAS_ATTR)>0},IS_LARGE:function(x){return(x&Rserve.Rsrv.XT_LARGE)>0},status_codes:{65:"ERR_auth_failed",66:"ERR_conn_broken",67:"ERR_inv_cmd",68:"ERR_inv_par",69:"ERR_Rerror",70:"ERR_IOerror",71:"ERR_notOpen",72:"ERR_accessDenied",73:"ERR_unsupportedCmd",74:"ERR_unknownCmd",75:"ERR_data_overflow",76:"ERR_object_too_big",77:"ERR_out_of_mem",78:"ERR_ctrl_closed",80:"ERR_session_busy",81:"ERR_detach_failed",97:"ERR_disabled",98:"ERR_unavailable",99:"ERR_cryptError",100:"ERR_securityClose"}};!function(){function read(m){var handlers={};function lift(f,amount){return function(attributes,length){return[f.call(that,attributes,length),amount||length]}}function bind(m,f){return function(attributes,length){var t=m.call(that,attributes,length);var t2=f(t[0])(attributes,length-t[1]);return[t2[0],t[1]+t2[1]]}}function unfold(f){return function(attributes,length){var result=[];var old_length=length;while(length>0){var t=f.call(that,attributes,length);result.push(t[0]);length-=t[1]}return[result,old_length]}}var that={offset:0,data_view:m.make(Rserve.EndianAwareDataView),msg:m,read_int:function(){var old_offset=this.offset;this.offset+=4;return this.data_view.getInt32(old_offset)},read_string:function(length){var result="";while(length--){var c=this.data_view.getInt8(this.offset++);if(c)result=result+String.fromCharCode(c)}return result},read_stream:function(length){var old_offset=this.offset;this.offset+=length;return this.msg.view(old_offset,length)},read_int_vector:function(length){var old_offset=this.offset;this.offset+=length;return this.msg.make(Int32Array,old_offset,length)},read_double_vector:function(length){var old_offset=this.offset;this.offset+=length;return this.msg.make(Float64Array,old_offset,length)},read_null:lift(function(a,l){return Rserve.Robj.null(a)}),read_unknown:lift(function(a,l){this.offset+=l;return Rserve.Robj.null(a)}),read_string_array:function(attributes,length){var a=this.read_stream(length).make(Uint8Array);var result=[];var current_str="";for(var i=0;i>24;result.header=[resp,status_code];if(result.header[0]===Rserve.Rsrv.RESP_ERR){result.ok=false;result.status_code=status_code;result.message="ERROR FROM R SERVER: "+(Rserve.Rsrv.status_codes[status_code]||status_code)+" "+result.header[0]+" "+result.header[1]+" "+msg.byteLength+" "+msg;return result}if(!_.contains([Rserve.Rsrv.RESP_OK,Rserve.Rsrv.OOB_SEND,Rserve.Rsrv.OOB_MSG],result.header[0])){result.ok=false;result.message="Unexpected response from RServe: "+result.header[0]+" status: "+Rserve.Rsrv.status_codes[status_code];return result}try{result.payload=parse_payload(msg);result.ok=true}catch(e){result.ok=false;result.message=e.message}return result}function parse_payload(msg){var payload=Rserve.my_ArrayBufferView(msg,16,msg.byteLength-16);if(payload.length===0)return null;var reader=read(payload);var d=reader.read_int();var _=Rserve.Rsrv.par_parse(d);var t=_[0],l=_[1];if(Rserve.Rsrv.IS_LARGE(t)){var more_length=reader.read_int();l+=more_length*Math.pow(2,24);if(l>Math.pow(2,32)){throw new Error("Payload too large: "+l+" bytes")}t&=~64}if(t===Rserve.Rsrv.DT_INT){return{type:"int",value:reader.read_int()}}else if(t===Rserve.Rsrv.DT_STRING){return{type:"string",value:reader.read_string(l)}}else if(t===Rserve.Rsrv.DT_BYTESTREAM){return{type:"stream",value:reader.read_stream(l)}}else if(t===Rserve.Rsrv.DT_SEXP){_=reader.read_sexp();var sexp=_[0],l2=_[1];return{type:"sexp",value:sexp}}else throw new Rserve.RserveError("Bad type for parse? "+t+" "+l,-1)}Rserve.parse_websocket_frame=parse;Rserve.parse_payload=parse_payload}();!function(){var _is_little_endian;!function(){var x=new ArrayBuffer(4);var bytes=new Uint8Array(x),words=new Uint32Array(x);bytes[0]=1;if(words[0]===1){_is_little_endian=true}else if(words[0]===16777216){_is_little_endian=false}else{throw"we're bizarro endian, refusing to continue"}}();Rserve.EndianAwareDataView=function(){var proto={setInt8:function(i,v){return this.view.setInt8(i,v)},setUint8:function(i,v){return this.view.setUint8(i,v)},getInt8:function(i){return this.view.getInt8(i)},getUint8:function(i){return this.view.getUint8(i)}};var setters=["setInt32","setInt16","setUint32","setUint16","setFloat32","setFloat64"];var getters=["getInt32","getInt16","getUint32","getUint16","getFloat32","getFloat64"];for(var i=0;i16777215){var buffer=new ArrayBuffer(sz+8);var view=Rserve.my_ArrayBufferView(buffer);view.data_view().setInt32(0,Rserve.Rsrv.DT_SEXP+(sz&16777215)*Math.pow(2,8)+Rserve.Rsrv.DT_LARGE);view.data_view().setInt32(4,sz>>24);Rserve.write_into_view(value,view.skip(8),forced_type,convert_to_hash);return buffer}else{var buffer=new ArrayBuffer(sz+4);var view=Rserve.my_ArrayBufferView(buffer);view.data_view().setInt32(0,Rserve.Rsrv.DT_SEXP+(sz<<8));Rserve.write_into_view(value,view.skip(4),forced_type,convert_to_hash);return buffer}}function hand_shake(msg){msg=msg.data;if(typeof msg==="string"){if(msg.substr(0,4)!=="Rsrv"){handle_error("server is not an RServe instance",-1)}else if(msg.substr(4,4)!=="0103"){handle_error("sorry, rserve only speaks the 0103 version of the R server protocol",-1)}else if(msg.substr(8,4)!=="QAP1"){handle_error("sorry, rserve only speaks QAP1",-1)}else{received_handshake=true;if(opts.login)result.login(opts.login);result.running=true;onconnect&&onconnect.call(result)}}else{var view=new DataView(msg);var header=String.fromCharCode(view.getUint8(0))+String.fromCharCode(view.getUint8(1))+String.fromCharCode(view.getUint8(2))+String.fromCharCode(view.getUint8(3));if(header==="RsOC"){received_handshake=true;result.ocap_mode=true;result.bare_ocap=Rserve.parse_payload(msg).value;result.ocap=Rserve.wrap_ocap(result,result.bare_ocap);result.running=true;onconnect&&onconnect.call(result)}else handle_error("Unrecognized server answer: "+header,-1)}}socket.onclose=function(msg){result.running=false;result.closed=true;opts.on_close&&opts.on_close(msg)};socket.onmessage=function(msg){if(msg.data.constructor.name==="Buffer")msg.data=new Uint8Array(msg.data).buffer;if(opts.debug)opts.debug.message_in&&opts.debug.message_in(msg);if(!received_handshake){hand_shake(msg);return}if(typeof msg.data==="string"){opts.on_raw_string&&opts.on_raw_string(msg.data);return}var v=Rserve.parse_websocket_frame(msg.data);if(!v.ok){result_callback([v.message,v.status_code],undefined)}else if(v.header[0]===Rserve.Rsrv.RESP_OK){result_callback(null,v.payload)}else if(v.header[0]===Rserve.Rsrv.OOB_SEND){opts.on_data&&opts.on_data(v.payload)}else if(v.header[0]===Rserve.Rsrv.OOB_MSG){if(result.ocap_mode){var p;try{p=Rserve.wrap_all_ocaps(result,v.payload)}catch(e){_send_cmd_now(Rserve.Rsrv.RESP_ERR|Rserve.Rsrv.OOB_MSG,_encode_string(String(e)));return}if(!_.isFunction(p[0])){_send_cmd_now(Rserve.Rsrv.RESP_ERR|Rserve.Rsrv.OOB_MSG,_encode_string("OOB Messages on ocap-mode must be javascript function calls"));return}var captured_function=p[0],params=p.slice(1);params.push(function(err,result){if(err){_send_cmd_now(Rserve.Rsrv.RESP_ERR|Rserve.Rsrv.OOB_MSG,_encode_value(err))}else{_send_cmd_now(Rserve.Rsrv.OOB_MSG,_encode_value(result))}});captured_function.apply(undefined,params)}else{if(_.isUndefined(opts.on_oob_message)){_send_cmd_now(Rserve.Rsrv.RESP_ERR|Rserve.Rsrv.OOB_MSG,_encode_string("No handler installed"))}else{in_oob_message=true;opts.on_oob_message(v.payload,function(message,error){if(!in_oob_message){handle_error("Don't call oob_message_handler more than once.");return}in_oob_message=false;var header=Rserve.Rsrv.OOB_MSG|(error?Rserve.Rsrv.RESP_ERR:Rserve.Rsrv.RESP_OK);_send_cmd_now(header,_encode_string(message));bump_queue()})}}}else{handle_error("Internal Error, parse returned unexpected type "+v.header[0],-1)}};function _send_cmd_now(command,buffer){var big_buffer=_encode_command(command,buffer);if(opts.debug)opts.debug.message_out&&opts.debug.message_out(big_buffer[0],command);socket.send(big_buffer);return big_buffer}var queue=[];var in_oob_message=false;var awaiting_result=false;var result_callback;function bump_queue(){if(result.closed&&queue.length){handle_error("Cannot send messages on a closed socket!",-1)}else if(!awaiting_result&&!in_oob_message&&queue.length){var lst=queue.shift();result_callback=lst[1];awaiting_result=true;if(opts.debug)opts.debug.message_out&&opts.debug.message_out(lst[0],lst[2]);socket.send(lst[0])}}function enqueue(buffer,k,command){queue.push([buffer,function(error,result){awaiting_result=false;bump_queue();k(error,result)},command]);bump_queue()}function _cmd(command,buffer,k,string){k=k||function(){};var big_buffer=_encode_command(command,buffer);return enqueue(big_buffer,k,string)}result={ocap_mode:false,running:false,closed:false,close:function(){socket.close()},login:function(command,k){_cmd(Rserve.Rsrv.CMD_login,_encode_string(command),k,command)},eval:function(command,k){_cmd(Rserve.Rsrv.CMD_eval,_encode_string(command),k,command)},createFile:function(command,k){_cmd(Rserve.Rsrv.CMD_createFile,_encode_string(command),k,command)},writeFile:function(chunk,k){_cmd(Rserve.Rsrv.CMD_writeFile,_encode_bytes(chunk),k,"")},closeFile:function(k){_cmd(Rserve.Rsrv.CMD_closeFile,new ArrayBuffer(0),k,"")},set:function(key,value,k){_cmd(Rserve.Rsrv.CMD_setSEXP,[_encode_string(key),_encode_value(value)],k,"")},OCcall:function(ocap,values,k){var is_ocap=false,str;try{is_ocap|=ocap.r_attributes["class"]==="OCref";str=ocap[0]}catch(e){}if(!is_ocap){try{is_ocap|=ocap.attributes.value[0].value.value[0]==="OCref";str=ocap.value[0]}catch(e){}}if(!is_ocap){k(new Error("Expected an ocap, instead got "+ocap),undefined);return}var params=[str];params.push.apply(params,values);_cmd(Rserve.Rsrv.CMD_OCcall,_encode_value(params,Rserve.Rsrv.XT_LANG_NOTAG),k,"")},wrap_ocap:function(ocap){return Rserve.wrap_ocap(this,ocap)},resolve_hash:function(hash){if(!(hash in captured_functions))throw new Error("hash "+hash+" not found.");return captured_functions[hash]}};return result};Rserve.wrap_all_ocaps=function(s,v){v=v.value.json(s.resolve_hash);function replace(obj){var result=obj;if(_.isArray(obj)&&obj.r_attributes&&obj.r_attributes["class"]=="OCref"){return Rserve.wrap_ocap(s,obj)}else if(_.isArray(obj)){result=_.map(obj,replace);result.r_type=obj.r_type;result.r_attributes=obj.r_attributes}else if(_.isTypedArray(obj)){return obj}else if(_.isFunction(obj)){return obj}else if(_.isObject(obj)){result=_.object(_.map(obj,function(v,k){return[k,replace(v)]}))}return result}return replace(v)};Rserve.wrap_ocap=function(s,ocap){var wrapped_ocap=function(){var values=_.toArray(arguments);if(!values.length||!_.isFunction(values[values.length-1]))throw new Error("forgot to pass continuation to ocap");var k=values.pop();s.OCcall(ocap,values,function(err,v){if(!_.isUndefined(v))v=Rserve.wrap_all_ocaps(s,v);k(err,v)})};wrapped_ocap.bare_ocap=ocap;return wrapped_ocap}}();Rserve.RserveError=function(message,status_code){this.name="RserveError";this.message=message;this.status_code=status_code};Rserve.RserveError.prototype=Object.create(Error);Rserve.RserveError.prototype.constructor=Rserve.RserveError;!function(){_.mixin({isTypedArray:function(v){if(!_.isObject(v))return false;return!_.isUndefined(v.byteLength)&&!_.isUndefined(v.BYTES_PER_ELEMENT)}});Rserve.type_id=function(value){if(_.isNull(value)||_.isUndefined(value))return Rserve.Rsrv.XT_NULL;var type_dispatch={"boolean":Rserve.Rsrv.XT_ARRAY_BOOL,number:Rserve.Rsrv.XT_ARRAY_DOUBLE,string:Rserve.Rsrv.XT_ARRAY_STR};if(!_.isUndefined(type_dispatch[typeof value]))return type_dispatch[typeof value];if(_.isTypedArray(value))return Rserve.Rsrv.XT_ARRAY_DOUBLE;if(!_.isUndefined(value.byteLength)&&!_.isUndefined(value.slice))return Rserve.Rsrv.XT_RAW;if(_.isArray(value)&&_.all(value,function(el){return typeof el==="string"}))return Rserve.Rsrv.XT_ARRAY_STR;if(_.isArray(value)&&_.all(value,function(el){return typeof el==="boolean"}))return Rserve.Rsrv.XT_ARRAY_BOOL;if(_.isArray(value))return Rserve.Rsrv.XT_VECTOR;if(_.isFunction(value))return Rserve.Rsrv.XT_ARRAY_STR|Rserve.Rsrv.XT_HAS_ATTR;if(_.isObject(value))return Rserve.Rsrv.XT_VECTOR|Rserve.Rsrv.XT_HAS_ATTR;throw new Rserve.RServeError("Value type unrecognized by Rserve: "+value)};Rserve.determine_size=function(value,forced_type){function list_size(lst){return _.reduce(lst,function(memo,el){return memo+Rserve.determine_size(el)},0)}function final_size(payload_size){if(payload_size>1<<24)return payload_size+8;else return payload_size+4}var header_size=4,t=forced_type||Rserve.type_id(value);switch(t&~Rserve.Rsrv.XT_LARGE){case Rserve.Rsrv.XT_NULL:return final_size(0);case Rserve.Rsrv.XT_ARRAY_BOOL:if(_.isBoolean(value))return final_size(8);else return final_size(value.length+7&~3);case Rserve.Rsrv.XT_ARRAY_STR:if(_.isArray(value))return final_size(_.reduce(value,function(memo,str){return memo+str.length+1},0));else return final_size(value.length+1);case Rserve.Rsrv.XT_ARRAY_DOUBLE:if(_.isNumber(value))return final_size(8);else return final_size(8*value.length);case Rserve.Rsrv.XT_RAW:return final_size(4+value.byteLength);case Rserve.Rsrv.XT_VECTOR:case Rserve.Rsrv.XT_LANG_NOTAG:return final_size(list_size(value));case Rserve.Rsrv.XT_VECTOR|Rserve.Rsrv.XT_HAS_ATTR:var names_size_1=final_size("names".length+3);var names_size_2=Rserve.determine_size(_.keys(value));var names_size=final_size(names_size_1+names_size_2);return final_size(names_size+list_size(_.values(value)));case Rserve.Rsrv.XT_ARRAY_STR|Rserve.Rsrv.XT_HAS_ATTR:return Rserve.determine_size("0403556553")+header_size+header_size+"class".length+3+Rserve.determine_size(["javascript_function"]);default:throw new Rserve.RserveError("Internal error, can't handle type "+t)}};Rserve.write_into_view=function(value,array_buffer_view,forced_type,convert){var size=Rserve.determine_size(value,forced_type);var is_large=size>16777215;var t=forced_type||Rserve.type_id(value),i,current_offset,lbl;if(is_large)t=t|Rserve.Rsrv.XT_LARGE;var read_view;var write_view=array_buffer_view.data_view();var payload_start;if(is_large){payload_start=8;write_view.setInt32(0,t+(size-8<<8));write_view.setInt32(4,size-8>>24)}else{payload_start=4;write_view.setInt32(0,t+(size-4<<8))}switch(t&~Rserve.Rsrv.XT_LARGE){case Rserve.Rsrv.XT_NULL:break;case Rserve.Rsrv.XT_ARRAY_BOOL:if(_.isBoolean(value)){write_view.setInt32(payload_start,1);write_view.setInt8(payload_start+4,value?1:0)}else{write_view.setInt32(payload_start,value.length);for(i=0;i> 8; }, 197 | PAR_LENGTH: function(x) { return x >> 8; }, 198 | par_parse: function(x) { return [Rserve.Rsrv.PAR_TYPE(x), Rserve.Rsrv.PAR_LEN(x)]; }, 199 | SET_PAR: function(ty, len) { return ((len & 0xffffff) << 8 | (ty & 255)); }, 200 | CMD_STAT: function(x) { return (x >> 24) & 127; }, 201 | SET_STAT: function(x, s) { return x | ((s & 127) << 24); }, 202 | 203 | CMD_RESP : 0x10000, 204 | RESP_OK : 0x10000 | 0x0001, 205 | RESP_ERR : 0x10000 | 0x0002, 206 | OOB_SEND : 0x20000 | 0x1000, 207 | OOB_MSG : 0x20000 | 0x2000, 208 | ERR_auth_failed : 0x41, 209 | ERR_conn_broken : 0x42, 210 | ERR_inv_cmd : 0x43, 211 | ERR_inv_par : 0x44, 212 | ERR_Rerror : 0x45, 213 | ERR_IOerror : 0x46, 214 | ERR_notOpen : 0x47, 215 | ERR_accessDenied : 0x48, 216 | ERR_unsupportedCmd : 0x49, 217 | ERR_unknownCmd : 0x4a, 218 | ERR_data_overflow : 0x4b, 219 | ERR_object_too_big : 0x4c, 220 | ERR_out_of_mem : 0x4d, 221 | ERR_ctrl_closed : 0x4e, 222 | ERR_session_busy : 0x50, 223 | ERR_detach_failed : 0x51, 224 | ERR_disabled : 0x61, 225 | ERR_unavailable : 0x62, 226 | ERR_cryptError : 0x63, 227 | ERR_securityClose : 0x64, 228 | 229 | CMD_login : 0x001, 230 | CMD_voidEval : 0x002, 231 | CMD_eval : 0x003, 232 | CMD_shutdown : 0x004, 233 | CMD_switch : 0x005, 234 | CMD_keyReq : 0x006, 235 | CMD_secLogin : 0x007, 236 | CMD_OCcall : 0x00f, 237 | CMD_openFile : 0x010, 238 | CMD_createFile : 0x011, 239 | CMD_closeFile : 0x012, 240 | CMD_readFile : 0x013, 241 | CMD_writeFile : 0x014, 242 | CMD_removeFile : 0x015, 243 | CMD_setSEXP : 0x020, 244 | CMD_assignSEXP : 0x021, 245 | CMD_detachSession : 0x030, 246 | CMD_detachedVoidEval : 0x031, 247 | CMD_attachSession : 0x032, 248 | CMD_ctrl : 0x40, 249 | CMD_ctrlEval : 0x42, 250 | CMD_ctrlSource : 0x45, 251 | CMD_ctrlShutdown : 0x44, 252 | CMD_setBufferSize : 0x081, 253 | CMD_setEncoding : 0x082, 254 | CMD_SPECIAL_MASK : 0xf0, 255 | CMD_serEval : 0xf5, 256 | CMD_serAssign : 0xf6, 257 | CMD_serEEval : 0xf7, 258 | 259 | DT_INT : 1, 260 | DT_CHAR : 2, 261 | DT_DOUBLE : 3, 262 | DT_STRING : 4, 263 | DT_BYTESTREAM : 5, 264 | DT_SEXP : 10, 265 | DT_ARRAY : 11, 266 | DT_LARGE : 64, 267 | 268 | XT_NULL : 0, 269 | XT_INT : 1, 270 | XT_DOUBLE : 2, 271 | XT_STR : 3, 272 | XT_LANG : 4, 273 | XT_SYM : 5, 274 | XT_BOOL : 6, 275 | XT_S4 : 7, 276 | XT_VECTOR : 16, 277 | XT_LIST : 17, 278 | XT_CLOS : 18, 279 | XT_SYMNAME : 19, 280 | XT_LIST_NOTAG : 20, 281 | XT_LIST_TAG : 21, 282 | XT_LANG_NOTAG : 22, 283 | XT_LANG_TAG : 23, 284 | XT_VECTOR_EXP : 26, 285 | XT_VECTOR_STR : 27, 286 | XT_ARRAY_INT : 32, 287 | XT_ARRAY_DOUBLE : 33, 288 | XT_ARRAY_STR : 34, 289 | XT_ARRAY_BOOL_UA : 35, 290 | XT_ARRAY_BOOL : 36, 291 | XT_RAW : 37, 292 | XT_ARRAY_CPLX : 38, 293 | XT_UNKNOWN : 48, 294 | XT_LARGE : 64, 295 | XT_HAS_ATTR : 128, 296 | 297 | BOOL_TRUE : 1, 298 | BOOL_FALSE : 0, 299 | BOOL_NA : 2, 300 | 301 | GET_XT: function(x) { return x & 63; }, 302 | GET_DT: function(x) { return x & 63; }, 303 | HAS_ATTR: function(x) { return (x & Rserve.Rsrv.XT_HAS_ATTR) > 0; }, 304 | IS_LARGE: function(x) { return (x & Rserve.Rsrv.XT_LARGE) > 0; }, 305 | 306 | // # FIXME A WHOLE LOT OF MACROS HERE WHICH ARE PROBABLY IMPORTANT 307 | // ############################################################################## 308 | 309 | status_codes: { 310 | 0x41 : "ERR_auth_failed" , 311 | 0x42 : "ERR_conn_broken" , 312 | 0x43 : "ERR_inv_cmd" , 313 | 0x44 : "ERR_inv_par" , 314 | 0x45 : "ERR_Rerror" , 315 | 0x46 : "ERR_IOerror" , 316 | 0x47 : "ERR_notOpen" , 317 | 0x48 : "ERR_accessDenied" , 318 | 0x49 : "ERR_unsupportedCmd", 319 | 0x4a : "ERR_unknownCmd" , 320 | 0x4b : "ERR_data_overflow" , 321 | 0x4c : "ERR_object_too_big", 322 | 0x4d : "ERR_out_of_mem" , 323 | 0x4e : "ERR_ctrl_closed" , 324 | 0x50 : "ERR_session_busy" , 325 | 0x51 : "ERR_detach_failed" , 326 | 0x61 : "ERR_disabled" , 327 | 0x62 : "ERR_unavailable" , 328 | 0x63 : "ERR_cryptError" , 329 | 0x64 : "ERR_securityClose" 330 | } 331 | }; 332 | (function() { 333 | 334 | function read(m) 335 | { 336 | var handlers = {}; 337 | 338 | function lift(f, amount) { 339 | return function(attributes, length) { 340 | return [f.call(that, attributes, length), amount || length]; 341 | }; 342 | } 343 | 344 | function bind(m, f) { 345 | return function(attributes, length) { 346 | var t = m.call(that, attributes, length); 347 | var t2 = f(t[0])(attributes, length - t[1]); 348 | return [t2[0], t[1] + t2[1]]; 349 | }; 350 | } 351 | 352 | function unfold(f) { 353 | return function(attributes, length) { 354 | var result = []; 355 | var old_length = length; 356 | while (length > 0) { 357 | var t = f.call(that, attributes, length); 358 | result.push(t[0]); 359 | length -= t[1]; 360 | } 361 | return [result, old_length]; 362 | }; 363 | } 364 | 365 | var that = { 366 | offset: 0, 367 | data_view: m.make(Rserve.EndianAwareDataView), 368 | msg: m, 369 | 370 | ////////////////////////////////////////////////////////////////////// 371 | 372 | read_int: function() { 373 | var old_offset = this.offset; 374 | this.offset += 4; 375 | return this.data_view.getInt32(old_offset); 376 | }, 377 | read_string: function(length) { 378 | // FIXME SLOW 379 | var result = ""; 380 | while (length--) { 381 | var c = this.data_view.getInt8(this.offset++); 382 | if (c) result = result + String.fromCharCode(c); 383 | } 384 | return result; 385 | }, 386 | read_stream: function(length) { 387 | var old_offset = this.offset; 388 | this.offset += length; 389 | return this.msg.view(old_offset, length); 390 | }, 391 | read_int_vector: function(length) { 392 | var old_offset = this.offset; 393 | this.offset += length; 394 | return this.msg.make(Int32Array, old_offset, length); 395 | }, 396 | read_double_vector: function(length) { 397 | var old_offset = this.offset; 398 | this.offset += length; 399 | return this.msg.make(Float64Array, old_offset, length); 400 | }, 401 | 402 | ////////////////////////////////////////////////////////////////////// 403 | // these are members of the reader monad 404 | 405 | read_null: lift(function(a, l) { return Rserve.Robj.null(a); }), 406 | 407 | read_unknown: lift(function(a, l) { 408 | this.offset += l; 409 | return Rserve.Robj.null(a); 410 | }), 411 | 412 | read_string_array: function(attributes, length) { 413 | var a = this.read_stream(length).make(Uint8Array); 414 | var result = []; 415 | var current_str = ""; 416 | for (var i=0; i> 24; 546 | result.header = [resp, status_code]; 547 | 548 | if (result.header[0] === Rserve.Rsrv.RESP_ERR) { 549 | result.ok = false; 550 | result.status_code = status_code; 551 | result.message = "ERROR FROM R SERVER: " + (Rserve.Rsrv.status_codes[status_code] || 552 | status_code) 553 | + " " + result.header[0] + " " + result.header[1] 554 | + " " + msg.byteLength 555 | + " " + msg; 556 | return result; 557 | } 558 | 559 | if (!_.contains([Rserve.Rsrv.RESP_OK, Rserve.Rsrv.OOB_SEND, Rserve.Rsrv.OOB_MSG], result.header[0])) { 560 | result.ok = false; 561 | result.message = "Unexpected response from RServe: " + result.header[0] + " status: " + Rserve.Rsrv.status_codes[status_code]; 562 | return result; 563 | } 564 | try { 565 | result.payload = parse_payload(msg); 566 | result.ok = true; 567 | } catch (e) { 568 | result.ok = false; 569 | result.message = e.message; 570 | } 571 | return result; 572 | } 573 | 574 | function parse_payload(msg) 575 | { 576 | var payload = Rserve.my_ArrayBufferView(msg, 16, msg.byteLength - 16); 577 | if (payload.length === 0) 578 | return null; 579 | 580 | var reader = read(payload); 581 | 582 | var d = reader.read_int(); 583 | var _ = Rserve.Rsrv.par_parse(d); 584 | var t = _[0], l = _[1]; 585 | if (Rserve.Rsrv.IS_LARGE(t)) { 586 | var more_length = reader.read_int(); 587 | l += more_length * Math.pow(2, 24); 588 | if (l > (Math.pow(2, 32))) { // resist the 1 << 32 temptation here! 589 | // total_length is greater than 2^32.. bail out because of node limits 590 | // even though in theory we could go higher than that. 591 | throw new Error("Payload too large: " + l + " bytes"); 592 | } 593 | t &= ~64; 594 | } 595 | if (t === Rserve.Rsrv.DT_INT) { 596 | return { type: "int", value: reader.read_int() }; 597 | } else if (t === Rserve.Rsrv.DT_STRING) { 598 | return { type: "string", value: reader.read_string(l) }; 599 | } else if (t === Rserve.Rsrv.DT_BYTESTREAM) { // NB this returns a my_ArrayBufferView() 600 | return { type: "stream", value: reader.read_stream(l) }; 601 | } else if (t === Rserve.Rsrv.DT_SEXP) { 602 | _ = reader.read_sexp(); 603 | var sexp = _[0], l2 = _[1]; 604 | return { type: "sexp", value: sexp }; 605 | } else 606 | throw new Rserve.RserveError("Bad type for parse? " + t + " " + l, -1); 607 | } 608 | 609 | Rserve.parse_websocket_frame = parse; 610 | Rserve.parse_payload = parse_payload; 611 | 612 | })(); 613 | // we want an endian aware dataview mostly because ARM can be big-endian, and 614 | // that might put us in trouble wrt handheld devices. 615 | ////////////////////////////////////////////////////////////////////////////// 616 | 617 | (function() { 618 | var _is_little_endian; 619 | 620 | (function() { 621 | var x = new ArrayBuffer(4); 622 | var bytes = new Uint8Array(x), 623 | words = new Uint32Array(x); 624 | bytes[0] = 1; 625 | if (words[0] === 1) { 626 | _is_little_endian = true; 627 | } else if (words[0] === 16777216) { 628 | _is_little_endian = false; 629 | } else { 630 | throw "we're bizarro endian, refusing to continue"; 631 | } 632 | })(); 633 | 634 | Rserve.EndianAwareDataView = (function() { 635 | 636 | var proto = { 637 | 'setInt8': function(i, v) { return this.view.setInt8(i, v); }, 638 | 'setUint8': function(i, v) { return this.view.setUint8(i, v); }, 639 | 'getInt8': function(i) { return this.view.getInt8(i); }, 640 | 'getUint8': function(i) { return this.view.getUint8(i); } 641 | }; 642 | 643 | var setters = ['setInt32', 'setInt16', 'setUint32', 'setUint16', 644 | 'setFloat32', 'setFloat64']; 645 | var getters = ['getInt32', 'getInt16', 'getUint32', 'getUint16', 646 | 'getFloat32', 'getFloat64']; 647 | 648 | for (var i=0; i= 2^31. 811 | if (sz > 16777215) { 812 | var buffer = new ArrayBuffer(sz + 8); 813 | var view = Rserve.my_ArrayBufferView(buffer); 814 | // can't left shift value here because value will have bit 32 set and become signed.. 815 | view.data_view().setInt32(0, Rserve.Rsrv.DT_SEXP + ((sz & 16777215) * Math.pow(2, 8)) + Rserve.Rsrv.DT_LARGE); 816 | // but *can* right shift because we assume sz is less than 2^31 or so to begin with 817 | view.data_view().setInt32(4, sz >> 24); 818 | Rserve.write_into_view(value, view.skip(8), forced_type, convert_to_hash); 819 | return buffer; 820 | } else { 821 | var buffer = new ArrayBuffer(sz + 4); 822 | var view = Rserve.my_ArrayBufferView(buffer); 823 | view.data_view().setInt32(0, Rserve.Rsrv.DT_SEXP + (sz << 8)); 824 | Rserve.write_into_view(value, view.skip(4), forced_type, convert_to_hash); 825 | return buffer; 826 | } 827 | } 828 | 829 | function hand_shake(msg) 830 | { 831 | msg = msg.data; 832 | if (typeof msg === 'string') { 833 | if (msg.substr(0,4) !== 'Rsrv') { 834 | handle_error("server is not an RServe instance", -1); 835 | } else if (msg.substr(4, 4) !== '0103') { 836 | handle_error("sorry, rserve only speaks the 0103 version of the R server protocol", -1); 837 | } else if (msg.substr(8, 4) !== 'QAP1') { 838 | handle_error("sorry, rserve only speaks QAP1", -1); 839 | } else { 840 | received_handshake = true; 841 | if (opts.login) 842 | result.login(opts.login); 843 | result.running = true; 844 | onconnect && onconnect.call(result); 845 | } 846 | } else { 847 | var view = new DataView(msg); 848 | var header = String.fromCharCode(view.getUint8(0)) + 849 | String.fromCharCode(view.getUint8(1)) + 850 | String.fromCharCode(view.getUint8(2)) + 851 | String.fromCharCode(view.getUint8(3)); 852 | 853 | if (header === 'RsOC') { 854 | received_handshake = true; 855 | result.ocap_mode = true; 856 | result.bare_ocap = Rserve.parse_payload(msg).value; 857 | result.ocap = Rserve.wrap_ocap(result, result.bare_ocap); 858 | result.running = true; 859 | onconnect && onconnect.call(result); 860 | } else 861 | handle_error("Unrecognized server answer: " + header, -1); 862 | } 863 | } 864 | 865 | socket.onclose = function(msg) { 866 | result.running = false; 867 | result.closed = true; 868 | opts.on_close && opts.on_close(msg); 869 | }; 870 | 871 | socket.onmessage = function(msg) { 872 | // node.js Buffer vs ArrayBuffer workaround 873 | if (msg.data.constructor.name === 'Buffer') 874 | msg.data = (new Uint8Array(msg.data)).buffer; 875 | if (opts.debug) 876 | opts.debug.message_in && opts.debug.message_in(msg); 877 | if (!received_handshake) { 878 | hand_shake(msg); 879 | return; 880 | } 881 | if (typeof msg.data === 'string') { 882 | opts.on_raw_string && opts.on_raw_string(msg.data); 883 | return; 884 | } 885 | var v = Rserve.parse_websocket_frame(msg.data); 886 | if (!v.ok) { 887 | result_callback([v.message, v.status_code], undefined); 888 | // handle_error(v.message, v.status_code); 889 | } else if (v.header[0] === Rserve.Rsrv.RESP_OK) { 890 | result_callback(null, v.payload); 891 | } else if (v.header[0] === Rserve.Rsrv.OOB_SEND) { 892 | opts.on_data && opts.on_data(v.payload); 893 | } else if (v.header[0] === Rserve.Rsrv.OOB_MSG) { 894 | if (result.ocap_mode) { 895 | var p; 896 | try { 897 | p = Rserve.wrap_all_ocaps(result, v.payload); // .value.json(result.resolve_hash); 898 | } catch (e) { 899 | _send_cmd_now(Rserve.Rsrv.RESP_ERR | Rserve.Rsrv.OOB_MSG, 900 | _encode_string(String(e))); 901 | return; 902 | } 903 | if (!_.isFunction(p[0])) { 904 | _send_cmd_now(Rserve.Rsrv.RESP_ERR | Rserve.Rsrv.OOB_MSG, 905 | _encode_string("OOB Messages on ocap-mode must be javascript function calls")); 906 | return; 907 | } 908 | var captured_function = p[0], params = p.slice(1); 909 | params.push(function(err, result) { 910 | if (err) { 911 | _send_cmd_now(Rserve.Rsrv.RESP_ERR | Rserve.Rsrv.OOB_MSG, _encode_value(err)); 912 | } else { 913 | _send_cmd_now(Rserve.Rsrv.OOB_MSG, _encode_value(result)); 914 | } 915 | }); 916 | captured_function.apply(undefined, params); 917 | } else { 918 | if (_.isUndefined(opts.on_oob_message)) { 919 | _send_cmd_now(Rserve.Rsrv.RESP_ERR | Rserve.Rsrv.OOB_MSG, 920 | _encode_string("No handler installed")); 921 | } else { 922 | in_oob_message = true; 923 | opts.on_oob_message(v.payload, function(message, error) { 924 | if (!in_oob_message) { 925 | handle_error("Don't call oob_message_handler more than once."); 926 | return; 927 | } 928 | in_oob_message = false; 929 | var header = Rserve.Rsrv.OOB_MSG | 930 | (error ? Rserve.Rsrv.RESP_ERR : Rserve.Rsrv.RESP_OK); 931 | _send_cmd_now(header, _encode_string(message)); 932 | bump_queue(); 933 | }); 934 | } 935 | } 936 | } else { 937 | handle_error("Internal Error, parse returned unexpected type " + v.header[0], -1); 938 | } 939 | }; 940 | 941 | function _send_cmd_now(command, buffer) { 942 | var big_buffer = _encode_command(command, buffer); 943 | if (opts.debug) 944 | opts.debug.message_out && opts.debug.message_out(big_buffer[0], command); 945 | socket.send(big_buffer); 946 | return big_buffer; 947 | }; 948 | 949 | var queue = []; 950 | var in_oob_message = false; 951 | var awaiting_result = false; 952 | var result_callback; 953 | function bump_queue() { 954 | if (result.closed && queue.length) { 955 | handle_error("Cannot send messages on a closed socket!", -1); 956 | } else if (!awaiting_result && !in_oob_message && queue.length) { 957 | var lst = queue.shift(); 958 | result_callback = lst[1]; 959 | awaiting_result = true; 960 | if (opts.debug) 961 | opts.debug.message_out && opts.debug.message_out(lst[0], lst[2]); 962 | socket.send(lst[0]); 963 | } 964 | } 965 | function enqueue(buffer, k, command) { 966 | queue.push([buffer, function(error, result) { 967 | awaiting_result = false; 968 | bump_queue(); 969 | k(error, result); 970 | }, command]); 971 | bump_queue(); 972 | }; 973 | 974 | function _cmd(command, buffer, k, string) { 975 | k = k || function() {}; 976 | var big_buffer = _encode_command(command, buffer); 977 | return enqueue(big_buffer, k, string); 978 | }; 979 | 980 | result = { 981 | ocap_mode: false, 982 | running: false, 983 | closed: false, 984 | close: function() { 985 | socket.close(); 986 | }, 987 | 988 | ////////////////////////////////////////////////////////////////////// 989 | // non-ocap mode 990 | 991 | login: function(command, k) { 992 | _cmd(Rserve.Rsrv.CMD_login, _encode_string(command), k, command); 993 | }, 994 | eval: function(command, k) { 995 | _cmd(Rserve.Rsrv.CMD_eval, _encode_string(command), k, command); 996 | }, 997 | createFile: function(command, k) { 998 | _cmd(Rserve.Rsrv.CMD_createFile, _encode_string(command), k, command); 999 | }, 1000 | writeFile: function(chunk, k) { 1001 | _cmd(Rserve.Rsrv.CMD_writeFile, _encode_bytes(chunk), k, ""); 1002 | }, 1003 | closeFile: function(k) { 1004 | _cmd(Rserve.Rsrv.CMD_closeFile, new ArrayBuffer(0), k, ""); 1005 | }, 1006 | set: function(key, value, k) { 1007 | _cmd(Rserve.Rsrv.CMD_setSEXP, [_encode_string(key), _encode_value(value)], k, ""); 1008 | }, 1009 | 1010 | ////////////////////////////////////////////////////////////////////// 1011 | // ocap mode 1012 | 1013 | OCcall: function(ocap, values, k) { 1014 | var is_ocap = false, str; 1015 | try { 1016 | is_ocap |= ocap.r_attributes['class'] === 'OCref'; 1017 | str = ocap[0]; 1018 | } catch (e) {}; 1019 | if(!is_ocap) { 1020 | try { 1021 | is_ocap |= ocap.attributes.value[0].value.value[0] === 'OCref'; 1022 | str = ocap.value[0]; 1023 | } catch (e) {}; 1024 | } 1025 | if (!is_ocap) { 1026 | k(new Error("Expected an ocap, instead got " + ocap), undefined); 1027 | return; 1028 | } 1029 | var params = [str]; 1030 | params.push.apply(params, values); 1031 | _cmd(Rserve.Rsrv.CMD_OCcall, _encode_value(params, Rserve.Rsrv.XT_LANG_NOTAG), 1032 | k, 1033 | ""); 1034 | }, 1035 | 1036 | wrap_ocap: function(ocap) { 1037 | return Rserve.wrap_ocap(this, ocap); 1038 | }, 1039 | 1040 | resolve_hash: function(hash) { 1041 | if (!(hash in captured_functions)) 1042 | throw new Error("hash " + hash + " not found."); 1043 | return captured_functions[hash]; 1044 | } 1045 | }; 1046 | return result; 1047 | }; 1048 | 1049 | Rserve.wrap_all_ocaps = function(s, v) { 1050 | v = v.value.json(s.resolve_hash); 1051 | function replace(obj) { 1052 | var result = obj; 1053 | if (_.isArray(obj) && 1054 | obj.r_attributes && 1055 | obj.r_attributes['class'] == 'OCref') { 1056 | return Rserve.wrap_ocap(s, obj); 1057 | } else if (_.isArray(obj)) { 1058 | result = _.map(obj, replace); 1059 | result.r_type = obj.r_type; 1060 | result.r_attributes = obj.r_attributes; 1061 | } else if (_.isTypedArray(obj)) { 1062 | return obj; 1063 | } else if (_.isFunction(obj)) { 1064 | return obj; 1065 | } else if (_.isObject(obj)) { 1066 | result = _.object(_.map(obj, function(v, k) { 1067 | return [k, replace(v)]; 1068 | })); 1069 | } 1070 | return result; 1071 | } 1072 | return replace(v); 1073 | }; 1074 | 1075 | Rserve.wrap_ocap = function(s, ocap) { 1076 | var wrapped_ocap = function() { 1077 | var values = _.toArray(arguments); 1078 | // common error (tho this won't catch the case where last arg is a function) 1079 | if(!values.length || !_.isFunction(values[values.length-1])) 1080 | throw new Error("forgot to pass continuation to ocap"); 1081 | var k = values.pop(); 1082 | s.OCcall(ocap, values, function(err, v) { 1083 | if (!_.isUndefined(v)) 1084 | v = Rserve.wrap_all_ocaps(s, v); 1085 | k(err, v); 1086 | }); 1087 | }; 1088 | wrapped_ocap.bare_ocap = ocap; 1089 | return wrapped_ocap; 1090 | }; 1091 | 1092 | })(); 1093 | Rserve.RserveError = function(message, status_code) { 1094 | this.name = "RserveError"; 1095 | this.message = message; 1096 | this.status_code = status_code; 1097 | }; 1098 | 1099 | Rserve.RserveError.prototype = Object.create(Error); 1100 | Rserve.RserveError.prototype.constructor = Rserve.RserveError; 1101 | (function () { 1102 | 1103 | _.mixin({ 1104 | isTypedArray: function(v) { 1105 | if (!_.isObject(v)) 1106 | return false; 1107 | return !_.isUndefined(v.byteLength) && !_.isUndefined(v.BYTES_PER_ELEMENT); 1108 | } 1109 | }); 1110 | 1111 | // type_id tries to match some javascript values to Rserve value types 1112 | Rserve.type_id = function(value) 1113 | { 1114 | if (_.isNull(value) || _.isUndefined(value)) 1115 | return Rserve.Rsrv.XT_NULL; 1116 | var type_dispatch = { 1117 | "boolean": Rserve.Rsrv.XT_ARRAY_BOOL, 1118 | "number": Rserve.Rsrv.XT_ARRAY_DOUBLE, 1119 | "string": Rserve.Rsrv.XT_ARRAY_STR // base strings need to be array_str or R gets confused? 1120 | }; 1121 | if (!_.isUndefined(type_dispatch[typeof value])) 1122 | return type_dispatch[typeof value]; 1123 | 1124 | // typed arrays 1125 | if (_.isTypedArray(value)) 1126 | return Rserve.Rsrv.XT_ARRAY_DOUBLE; 1127 | 1128 | // arraybuffers 1129 | if (!_.isUndefined(value.byteLength) && !_.isUndefined(value.slice)) 1130 | return Rserve.Rsrv.XT_RAW; 1131 | 1132 | // lists of strings (important for tags) 1133 | if (_.isArray(value) && _.all(value, function(el) { return typeof el === 'string'; })) 1134 | return Rserve.Rsrv.XT_ARRAY_STR; 1135 | 1136 | if (_.isArray(value) && _.all(value, function(el) { return typeof el === 'boolean'; })) 1137 | return Rserve.Rsrv.XT_ARRAY_BOOL; 1138 | 1139 | // arbitrary lists 1140 | if (_.isArray(value)) 1141 | return Rserve.Rsrv.XT_VECTOR; 1142 | 1143 | // functions get passed as an array_str with extra attributes 1144 | if (_.isFunction(value)) 1145 | return Rserve.Rsrv.XT_ARRAY_STR | Rserve.Rsrv.XT_HAS_ATTR; 1146 | 1147 | // objects 1148 | if (_.isObject(value)) 1149 | return Rserve.Rsrv.XT_VECTOR | Rserve.Rsrv.XT_HAS_ATTR; 1150 | 1151 | throw new Rserve.RServeError("Value type unrecognized by Rserve: " + value); 1152 | }; 1153 | 1154 | // FIXME this is really slow, as it's walking the object many many times. 1155 | Rserve.determine_size = function(value, forced_type) 1156 | { 1157 | function list_size(lst) { 1158 | return _.reduce(lst, function(memo, el) { 1159 | return memo + Rserve.determine_size(el); 1160 | }, 0); 1161 | } 1162 | function final_size(payload_size) { 1163 | if (payload_size > (1 << 24)) 1164 | return payload_size + 8; // large header 1165 | else 1166 | return payload_size + 4; 1167 | } 1168 | var header_size = 4, t = forced_type || Rserve.type_id(value); 1169 | 1170 | switch (t & ~Rserve.Rsrv.XT_LARGE) { 1171 | case Rserve.Rsrv.XT_NULL: 1172 | return final_size(0); 1173 | case Rserve.Rsrv.XT_ARRAY_BOOL: 1174 | if (_.isBoolean(value)) 1175 | return final_size(8); 1176 | else 1177 | return final_size((value.length + 7) & ~3); 1178 | case Rserve.Rsrv.XT_ARRAY_STR: 1179 | if (_.isArray(value)) 1180 | return final_size(_.reduce(value, function(memo, str) { 1181 | return memo + str.length + 1; 1182 | }, 0)); 1183 | else 1184 | return final_size(value.length + 1); 1185 | case Rserve.Rsrv.XT_ARRAY_DOUBLE: 1186 | if (_.isNumber(value)) 1187 | return final_size(8); 1188 | else 1189 | return final_size(8 * value.length); 1190 | case Rserve.Rsrv.XT_RAW: 1191 | return final_size(4 + value.byteLength); 1192 | case Rserve.Rsrv.XT_VECTOR: 1193 | case Rserve.Rsrv.XT_LANG_NOTAG: 1194 | return final_size(list_size(value)); 1195 | case Rserve.Rsrv.XT_VECTOR | Rserve.Rsrv.XT_HAS_ATTR: // a named list (that is, a js object) 1196 | var names_size_1 = final_size("names".length + 3); 1197 | var names_size_2 = Rserve.determine_size(_.keys(value)); 1198 | var names_size = final_size(names_size_1 + names_size_2); 1199 | return final_size(names_size + list_size(_.values(value))); 1200 | /* return header_size // XT_VECTOR | XT_HAS_ATTR 1201 | + header_size // XT_LIST_TAG (attribute) 1202 | + header_size + "names".length + 3 // length of 'names' + padding (tag as XT_SYMNAME) 1203 | + Rserve.determine_size(_.keys(value)) // length of names 1204 | + list_size(_.values(value)); // length of values 1205 | */ 1206 | case Rserve.Rsrv.XT_ARRAY_STR | Rserve.Rsrv.XT_HAS_ATTR: // js->r ocap (that is, a js function) 1207 | return Rserve.determine_size("0403556553") // length of ocap nonce; that number is meaningless aside from length 1208 | + header_size // XT_LIST_TAG (attribute) 1209 | + header_size + "class".length + 3 // length of 'class' + padding (tag as XT_SYMNAME) 1210 | + Rserve.determine_size(["javascript_function"]); // length of class name 1211 | 1212 | default: 1213 | throw new Rserve.RserveError("Internal error, can't handle type " + t); 1214 | } 1215 | }; 1216 | 1217 | Rserve.write_into_view = function(value, array_buffer_view, forced_type, convert) 1218 | { 1219 | var size = Rserve.determine_size(value, forced_type); 1220 | var is_large = size > 16777215; 1221 | // if (size > 16777215) 1222 | // throw new Rserve.RserveError("Can't currently handle objects >16MB"); 1223 | var t = forced_type || Rserve.type_id(value), i, current_offset, lbl; 1224 | if (is_large) 1225 | t = t | Rserve.Rsrv.XT_LARGE; 1226 | var read_view; 1227 | var write_view = array_buffer_view.data_view(); 1228 | var payload_start; 1229 | if (is_large) { 1230 | payload_start = 8; 1231 | write_view.setInt32(0, t + ((size - 8) << 8)); 1232 | write_view.setInt32(4, (size - 8) >> 24); 1233 | } else { 1234 | payload_start = 4; 1235 | write_view.setInt32(0, t + ((size - 4) << 8)); 1236 | } 1237 | 1238 | switch (t & ~Rserve.Rsrv.XT_LARGE) { 1239 | case Rserve.Rsrv.XT_NULL: 1240 | break; 1241 | case Rserve.Rsrv.XT_ARRAY_BOOL: 1242 | if (_.isBoolean(value)) { 1243 | write_view.setInt32(payload_start, 1); 1244 | write_view.setInt8(payload_start + 4, value ? 1 : 0); 1245 | } else { 1246 | write_view.setInt32(payload_start, value.length); 1247 | for (i=0; i> 8; }, 200 | PAR_LENGTH: function(x) { return x >> 8; }, 201 | par_parse: function(x) { return [Rserve.Rsrv.PAR_TYPE(x), Rserve.Rsrv.PAR_LEN(x)]; }, 202 | SET_PAR: function(ty, len) { return ((len & 0xffffff) << 8 | (ty & 255)); }, 203 | CMD_STAT: function(x) { return (x >> 24) & 127; }, 204 | SET_STAT: function(x, s) { return x | ((s & 127) << 24); }, 205 | 206 | CMD_RESP : 0x10000, 207 | RESP_OK : 0x10000 | 0x0001, 208 | RESP_ERR : 0x10000 | 0x0002, 209 | OOB_SEND : 0x20000 | 0x1000, 210 | OOB_MSG : 0x20000 | 0x2000, 211 | ERR_auth_failed : 0x41, 212 | ERR_conn_broken : 0x42, 213 | ERR_inv_cmd : 0x43, 214 | ERR_inv_par : 0x44, 215 | ERR_Rerror : 0x45, 216 | ERR_IOerror : 0x46, 217 | ERR_notOpen : 0x47, 218 | ERR_accessDenied : 0x48, 219 | ERR_unsupportedCmd : 0x49, 220 | ERR_unknownCmd : 0x4a, 221 | ERR_data_overflow : 0x4b, 222 | ERR_object_too_big : 0x4c, 223 | ERR_out_of_mem : 0x4d, 224 | ERR_ctrl_closed : 0x4e, 225 | ERR_session_busy : 0x50, 226 | ERR_detach_failed : 0x51, 227 | ERR_disabled : 0x61, 228 | ERR_unavailable : 0x62, 229 | ERR_cryptError : 0x63, 230 | ERR_securityClose : 0x64, 231 | 232 | CMD_login : 0x001, 233 | CMD_voidEval : 0x002, 234 | CMD_eval : 0x003, 235 | CMD_shutdown : 0x004, 236 | CMD_switch : 0x005, 237 | CMD_keyReq : 0x006, 238 | CMD_secLogin : 0x007, 239 | CMD_OCcall : 0x00f, 240 | CMD_openFile : 0x010, 241 | CMD_createFile : 0x011, 242 | CMD_closeFile : 0x012, 243 | CMD_readFile : 0x013, 244 | CMD_writeFile : 0x014, 245 | CMD_removeFile : 0x015, 246 | CMD_setSEXP : 0x020, 247 | CMD_assignSEXP : 0x021, 248 | CMD_detachSession : 0x030, 249 | CMD_detachedVoidEval : 0x031, 250 | CMD_attachSession : 0x032, 251 | CMD_ctrl : 0x40, 252 | CMD_ctrlEval : 0x42, 253 | CMD_ctrlSource : 0x45, 254 | CMD_ctrlShutdown : 0x44, 255 | CMD_setBufferSize : 0x081, 256 | CMD_setEncoding : 0x082, 257 | CMD_SPECIAL_MASK : 0xf0, 258 | CMD_serEval : 0xf5, 259 | CMD_serAssign : 0xf6, 260 | CMD_serEEval : 0xf7, 261 | 262 | DT_INT : 1, 263 | DT_CHAR : 2, 264 | DT_DOUBLE : 3, 265 | DT_STRING : 4, 266 | DT_BYTESTREAM : 5, 267 | DT_SEXP : 10, 268 | DT_ARRAY : 11, 269 | DT_LARGE : 64, 270 | 271 | XT_NULL : 0, 272 | XT_INT : 1, 273 | XT_DOUBLE : 2, 274 | XT_STR : 3, 275 | XT_LANG : 4, 276 | XT_SYM : 5, 277 | XT_BOOL : 6, 278 | XT_S4 : 7, 279 | XT_VECTOR : 16, 280 | XT_LIST : 17, 281 | XT_CLOS : 18, 282 | XT_SYMNAME : 19, 283 | XT_LIST_NOTAG : 20, 284 | XT_LIST_TAG : 21, 285 | XT_LANG_NOTAG : 22, 286 | XT_LANG_TAG : 23, 287 | XT_VECTOR_EXP : 26, 288 | XT_VECTOR_STR : 27, 289 | XT_ARRAY_INT : 32, 290 | XT_ARRAY_DOUBLE : 33, 291 | XT_ARRAY_STR : 34, 292 | XT_ARRAY_BOOL_UA : 35, 293 | XT_ARRAY_BOOL : 36, 294 | XT_RAW : 37, 295 | XT_ARRAY_CPLX : 38, 296 | XT_UNKNOWN : 48, 297 | XT_LARGE : 64, 298 | XT_HAS_ATTR : 128, 299 | 300 | BOOL_TRUE : 1, 301 | BOOL_FALSE : 0, 302 | BOOL_NA : 2, 303 | 304 | GET_XT: function(x) { return x & 63; }, 305 | GET_DT: function(x) { return x & 63; }, 306 | HAS_ATTR: function(x) { return (x & Rserve.Rsrv.XT_HAS_ATTR) > 0; }, 307 | IS_LARGE: function(x) { return (x & Rserve.Rsrv.XT_LARGE) > 0; }, 308 | 309 | // # FIXME A WHOLE LOT OF MACROS HERE WHICH ARE PROBABLY IMPORTANT 310 | // ############################################################################## 311 | 312 | status_codes: { 313 | 0x41 : "ERR_auth_failed" , 314 | 0x42 : "ERR_conn_broken" , 315 | 0x43 : "ERR_inv_cmd" , 316 | 0x44 : "ERR_inv_par" , 317 | 0x45 : "ERR_Rerror" , 318 | 0x46 : "ERR_IOerror" , 319 | 0x47 : "ERR_notOpen" , 320 | 0x48 : "ERR_accessDenied" , 321 | 0x49 : "ERR_unsupportedCmd", 322 | 0x4a : "ERR_unknownCmd" , 323 | 0x4b : "ERR_data_overflow" , 324 | 0x4c : "ERR_object_too_big", 325 | 0x4d : "ERR_out_of_mem" , 326 | 0x4e : "ERR_ctrl_closed" , 327 | 0x50 : "ERR_session_busy" , 328 | 0x51 : "ERR_detach_failed" , 329 | 0x61 : "ERR_disabled" , 330 | 0x62 : "ERR_unavailable" , 331 | 0x63 : "ERR_cryptError" , 332 | 0x64 : "ERR_securityClose" 333 | } 334 | }; 335 | (function() { 336 | 337 | function read(m) 338 | { 339 | var handlers = {}; 340 | 341 | function lift(f, amount) { 342 | return function(attributes, length) { 343 | return [f.call(that, attributes, length), amount || length]; 344 | }; 345 | } 346 | 347 | function bind(m, f) { 348 | return function(attributes, length) { 349 | var t = m.call(that, attributes, length); 350 | var t2 = f(t[0])(attributes, length - t[1]); 351 | return [t2[0], t[1] + t2[1]]; 352 | }; 353 | } 354 | 355 | function unfold(f) { 356 | return function(attributes, length) { 357 | var result = []; 358 | var old_length = length; 359 | while (length > 0) { 360 | var t = f.call(that, attributes, length); 361 | result.push(t[0]); 362 | length -= t[1]; 363 | } 364 | return [result, old_length]; 365 | }; 366 | } 367 | 368 | var that = { 369 | offset: 0, 370 | data_view: m.make(Rserve.EndianAwareDataView), 371 | msg: m, 372 | 373 | ////////////////////////////////////////////////////////////////////// 374 | 375 | read_int: function() { 376 | var old_offset = this.offset; 377 | this.offset += 4; 378 | return this.data_view.getInt32(old_offset); 379 | }, 380 | read_string: function(length) { 381 | // FIXME SLOW 382 | var result = ""; 383 | while (length--) { 384 | var c = this.data_view.getInt8(this.offset++); 385 | if (c) result = result + String.fromCharCode(c); 386 | } 387 | return result; 388 | }, 389 | read_stream: function(length) { 390 | var old_offset = this.offset; 391 | this.offset += length; 392 | return this.msg.view(old_offset, length); 393 | }, 394 | read_int_vector: function(length) { 395 | var old_offset = this.offset; 396 | this.offset += length; 397 | return this.msg.make(Int32Array, old_offset, length); 398 | }, 399 | read_double_vector: function(length) { 400 | var old_offset = this.offset; 401 | this.offset += length; 402 | return this.msg.make(Float64Array, old_offset, length); 403 | }, 404 | 405 | ////////////////////////////////////////////////////////////////////// 406 | // these are members of the reader monad 407 | 408 | read_null: lift(function(a, l) { return Rserve.Robj.null(a); }), 409 | 410 | read_unknown: lift(function(a, l) { 411 | this.offset += l; 412 | return Rserve.Robj.null(a); 413 | }), 414 | 415 | read_string_array: function(attributes, length) { 416 | var a = this.read_stream(length).make(Uint8Array); 417 | var result = []; 418 | var current_str = ""; 419 | for (var i=0; i> 24; 549 | result.header = [resp, status_code]; 550 | 551 | if (result.header[0] === Rserve.Rsrv.RESP_ERR) { 552 | result.ok = false; 553 | result.status_code = status_code; 554 | result.message = "ERROR FROM R SERVER: " + (Rserve.Rsrv.status_codes[status_code] || 555 | status_code) 556 | + " " + result.header[0] + " " + result.header[1] 557 | + " " + msg.byteLength 558 | + " " + msg; 559 | return result; 560 | } 561 | 562 | if (!_.contains([Rserve.Rsrv.RESP_OK, Rserve.Rsrv.OOB_SEND, Rserve.Rsrv.OOB_MSG], result.header[0])) { 563 | result.ok = false; 564 | result.message = "Unexpected response from RServe: " + result.header[0] + " status: " + Rserve.Rsrv.status_codes[status_code]; 565 | return result; 566 | } 567 | try { 568 | result.payload = parse_payload(msg); 569 | result.ok = true; 570 | } catch (e) { 571 | result.ok = false; 572 | result.message = e.message; 573 | } 574 | return result; 575 | } 576 | 577 | function parse_payload(msg) 578 | { 579 | var payload = Rserve.my_ArrayBufferView(msg, 16, msg.byteLength - 16); 580 | if (payload.length === 0) 581 | return null; 582 | 583 | var reader = read(payload); 584 | 585 | var d = reader.read_int(); 586 | var _ = Rserve.Rsrv.par_parse(d); 587 | var t = _[0], l = _[1]; 588 | if (Rserve.Rsrv.IS_LARGE(t)) { 589 | var more_length = reader.read_int(); 590 | l += more_length * Math.pow(2, 24); 591 | if (l > (Math.pow(2, 32))) { // resist the 1 << 32 temptation here! 592 | // total_length is greater than 2^32.. bail out because of node limits 593 | // even though in theory we could go higher than that. 594 | throw new Error("Payload too large: " + l + " bytes"); 595 | } 596 | t &= ~64; 597 | } 598 | if (t === Rserve.Rsrv.DT_INT) { 599 | return { type: "int", value: reader.read_int() }; 600 | } else if (t === Rserve.Rsrv.DT_STRING) { 601 | return { type: "string", value: reader.read_string(l) }; 602 | } else if (t === Rserve.Rsrv.DT_BYTESTREAM) { // NB this returns a my_ArrayBufferView() 603 | return { type: "stream", value: reader.read_stream(l) }; 604 | } else if (t === Rserve.Rsrv.DT_SEXP) { 605 | _ = reader.read_sexp(); 606 | var sexp = _[0], l2 = _[1]; 607 | return { type: "sexp", value: sexp }; 608 | } else 609 | throw new Rserve.RserveError("Bad type for parse? " + t + " " + l, -1); 610 | } 611 | 612 | Rserve.parse_websocket_frame = parse; 613 | Rserve.parse_payload = parse_payload; 614 | 615 | })(); 616 | // we want an endian aware dataview mostly because ARM can be big-endian, and 617 | // that might put us in trouble wrt handheld devices. 618 | ////////////////////////////////////////////////////////////////////////////// 619 | 620 | (function() { 621 | var _is_little_endian; 622 | 623 | (function() { 624 | var x = new ArrayBuffer(4); 625 | var bytes = new Uint8Array(x), 626 | words = new Uint32Array(x); 627 | bytes[0] = 1; 628 | if (words[0] === 1) { 629 | _is_little_endian = true; 630 | } else if (words[0] === 16777216) { 631 | _is_little_endian = false; 632 | } else { 633 | throw "we're bizarro endian, refusing to continue"; 634 | } 635 | })(); 636 | 637 | Rserve.EndianAwareDataView = (function() { 638 | 639 | var proto = { 640 | 'setInt8': function(i, v) { return this.view.setInt8(i, v); }, 641 | 'setUint8': function(i, v) { return this.view.setUint8(i, v); }, 642 | 'getInt8': function(i) { return this.view.getInt8(i); }, 643 | 'getUint8': function(i) { return this.view.getUint8(i); } 644 | }; 645 | 646 | var setters = ['setInt32', 'setInt16', 'setUint32', 'setUint16', 647 | 'setFloat32', 'setFloat64']; 648 | var getters = ['getInt32', 'getInt16', 'getUint32', 'getUint16', 649 | 'getFloat32', 'getFloat64']; 650 | 651 | for (var i=0; i= 2^31. 814 | if (sz > 16777215) { 815 | var buffer = new ArrayBuffer(sz + 8); 816 | var view = Rserve.my_ArrayBufferView(buffer); 817 | // can't left shift value here because value will have bit 32 set and become signed.. 818 | view.data_view().setInt32(0, Rserve.Rsrv.DT_SEXP + ((sz & 16777215) * Math.pow(2, 8)) + Rserve.Rsrv.DT_LARGE); 819 | // but *can* right shift because we assume sz is less than 2^31 or so to begin with 820 | view.data_view().setInt32(4, sz >> 24); 821 | Rserve.write_into_view(value, view.skip(8), forced_type, convert_to_hash); 822 | return buffer; 823 | } else { 824 | var buffer = new ArrayBuffer(sz + 4); 825 | var view = Rserve.my_ArrayBufferView(buffer); 826 | view.data_view().setInt32(0, Rserve.Rsrv.DT_SEXP + (sz << 8)); 827 | Rserve.write_into_view(value, view.skip(4), forced_type, convert_to_hash); 828 | return buffer; 829 | } 830 | } 831 | 832 | function hand_shake(msg) 833 | { 834 | msg = msg.data; 835 | if (typeof msg === 'string') { 836 | if (msg.substr(0,4) !== 'Rsrv') { 837 | handle_error("server is not an RServe instance", -1); 838 | } else if (msg.substr(4, 4) !== '0103') { 839 | handle_error("sorry, rserve only speaks the 0103 version of the R server protocol", -1); 840 | } else if (msg.substr(8, 4) !== 'QAP1') { 841 | handle_error("sorry, rserve only speaks QAP1", -1); 842 | } else { 843 | received_handshake = true; 844 | if (opts.login) 845 | result.login(opts.login); 846 | result.running = true; 847 | onconnect && onconnect.call(result); 848 | } 849 | } else { 850 | var view = new DataView(msg); 851 | var header = String.fromCharCode(view.getUint8(0)) + 852 | String.fromCharCode(view.getUint8(1)) + 853 | String.fromCharCode(view.getUint8(2)) + 854 | String.fromCharCode(view.getUint8(3)); 855 | 856 | if (header === 'RsOC') { 857 | received_handshake = true; 858 | result.ocap_mode = true; 859 | result.bare_ocap = Rserve.parse_payload(msg).value; 860 | result.ocap = Rserve.wrap_ocap(result, result.bare_ocap); 861 | result.running = true; 862 | onconnect && onconnect.call(result); 863 | } else 864 | handle_error("Unrecognized server answer: " + header, -1); 865 | } 866 | } 867 | 868 | socket.onclose = function(msg) { 869 | result.running = false; 870 | result.closed = true; 871 | opts.on_close && opts.on_close(msg); 872 | }; 873 | 874 | socket.onmessage = function(msg) { 875 | // node.js Buffer vs ArrayBuffer workaround 876 | if (msg.data.constructor.name === 'Buffer') 877 | msg.data = (new Uint8Array(msg.data)).buffer; 878 | if (opts.debug) 879 | opts.debug.message_in && opts.debug.message_in(msg); 880 | if (!received_handshake) { 881 | hand_shake(msg); 882 | return; 883 | } 884 | if (typeof msg.data === 'string') { 885 | opts.on_raw_string && opts.on_raw_string(msg.data); 886 | return; 887 | } 888 | var v = Rserve.parse_websocket_frame(msg.data); 889 | if (!v.ok) { 890 | result_callback([v.message, v.status_code], undefined); 891 | // handle_error(v.message, v.status_code); 892 | } else if (v.header[0] === Rserve.Rsrv.RESP_OK) { 893 | result_callback(null, v.payload); 894 | } else if (v.header[0] === Rserve.Rsrv.OOB_SEND) { 895 | opts.on_data && opts.on_data(v.payload); 896 | } else if (v.header[0] === Rserve.Rsrv.OOB_MSG) { 897 | if (result.ocap_mode) { 898 | var p; 899 | try { 900 | p = Rserve.wrap_all_ocaps(result, v.payload); // .value.json(result.resolve_hash); 901 | } catch (e) { 902 | _send_cmd_now(Rserve.Rsrv.RESP_ERR | Rserve.Rsrv.OOB_MSG, 903 | _encode_string(String(e))); 904 | return; 905 | } 906 | if (!_.isFunction(p[0])) { 907 | _send_cmd_now(Rserve.Rsrv.RESP_ERR | Rserve.Rsrv.OOB_MSG, 908 | _encode_string("OOB Messages on ocap-mode must be javascript function calls")); 909 | return; 910 | } 911 | var captured_function = p[0], params = p.slice(1); 912 | params.push(function(err, result) { 913 | if (err) { 914 | _send_cmd_now(Rserve.Rsrv.RESP_ERR | Rserve.Rsrv.OOB_MSG, _encode_value(err)); 915 | } else { 916 | _send_cmd_now(Rserve.Rsrv.OOB_MSG, _encode_value(result)); 917 | } 918 | }); 919 | captured_function.apply(undefined, params); 920 | } else { 921 | if (_.isUndefined(opts.on_oob_message)) { 922 | _send_cmd_now(Rserve.Rsrv.RESP_ERR | Rserve.Rsrv.OOB_MSG, 923 | _encode_string("No handler installed")); 924 | } else { 925 | in_oob_message = true; 926 | opts.on_oob_message(v.payload, function(message, error) { 927 | if (!in_oob_message) { 928 | handle_error("Don't call oob_message_handler more than once."); 929 | return; 930 | } 931 | in_oob_message = false; 932 | var header = Rserve.Rsrv.OOB_MSG | 933 | (error ? Rserve.Rsrv.RESP_ERR : Rserve.Rsrv.RESP_OK); 934 | _send_cmd_now(header, _encode_string(message)); 935 | bump_queue(); 936 | }); 937 | } 938 | } 939 | } else { 940 | handle_error("Internal Error, parse returned unexpected type " + v.header[0], -1); 941 | } 942 | }; 943 | 944 | function _send_cmd_now(command, buffer) { 945 | var big_buffer = _encode_command(command, buffer); 946 | if (opts.debug) 947 | opts.debug.message_out && opts.debug.message_out(big_buffer[0], command); 948 | socket.send(big_buffer); 949 | return big_buffer; 950 | }; 951 | 952 | var queue = []; 953 | var in_oob_message = false; 954 | var awaiting_result = false; 955 | var result_callback; 956 | function bump_queue() { 957 | if (result.closed && queue.length) { 958 | handle_error("Cannot send messages on a closed socket!", -1); 959 | } else if (!awaiting_result && !in_oob_message && queue.length) { 960 | var lst = queue.shift(); 961 | result_callback = lst[1]; 962 | awaiting_result = true; 963 | if (opts.debug) 964 | opts.debug.message_out && opts.debug.message_out(lst[0], lst[2]); 965 | socket.send(lst[0]); 966 | } 967 | } 968 | function enqueue(buffer, k, command) { 969 | queue.push([buffer, function(error, result) { 970 | awaiting_result = false; 971 | bump_queue(); 972 | k(error, result); 973 | }, command]); 974 | bump_queue(); 975 | }; 976 | 977 | function _cmd(command, buffer, k, string) { 978 | k = k || function() {}; 979 | var big_buffer = _encode_command(command, buffer); 980 | return enqueue(big_buffer, k, string); 981 | }; 982 | 983 | result = { 984 | ocap_mode: false, 985 | running: false, 986 | closed: false, 987 | close: function() { 988 | socket.close(); 989 | }, 990 | 991 | ////////////////////////////////////////////////////////////////////// 992 | // non-ocap mode 993 | 994 | login: function(command, k) { 995 | _cmd(Rserve.Rsrv.CMD_login, _encode_string(command), k, command); 996 | }, 997 | eval: function(command, k) { 998 | _cmd(Rserve.Rsrv.CMD_eval, _encode_string(command), k, command); 999 | }, 1000 | createFile: function(command, k) { 1001 | _cmd(Rserve.Rsrv.CMD_createFile, _encode_string(command), k, command); 1002 | }, 1003 | writeFile: function(chunk, k) { 1004 | _cmd(Rserve.Rsrv.CMD_writeFile, _encode_bytes(chunk), k, ""); 1005 | }, 1006 | closeFile: function(k) { 1007 | _cmd(Rserve.Rsrv.CMD_closeFile, new ArrayBuffer(0), k, ""); 1008 | }, 1009 | set: function(key, value, k) { 1010 | _cmd(Rserve.Rsrv.CMD_setSEXP, [_encode_string(key), _encode_value(value)], k, ""); 1011 | }, 1012 | 1013 | ////////////////////////////////////////////////////////////////////// 1014 | // ocap mode 1015 | 1016 | OCcall: function(ocap, values, k) { 1017 | var is_ocap = false, str; 1018 | try { 1019 | is_ocap |= ocap.r_attributes['class'] === 'OCref'; 1020 | str = ocap[0]; 1021 | } catch (e) {}; 1022 | if(!is_ocap) { 1023 | try { 1024 | is_ocap |= ocap.attributes.value[0].value.value[0] === 'OCref'; 1025 | str = ocap.value[0]; 1026 | } catch (e) {}; 1027 | } 1028 | if (!is_ocap) { 1029 | k(new Error("Expected an ocap, instead got " + ocap), undefined); 1030 | return; 1031 | } 1032 | var params = [str]; 1033 | params.push.apply(params, values); 1034 | _cmd(Rserve.Rsrv.CMD_OCcall, _encode_value(params, Rserve.Rsrv.XT_LANG_NOTAG), 1035 | k, 1036 | ""); 1037 | }, 1038 | 1039 | wrap_ocap: function(ocap) { 1040 | return Rserve.wrap_ocap(this, ocap); 1041 | }, 1042 | 1043 | resolve_hash: function(hash) { 1044 | if (!(hash in captured_functions)) 1045 | throw new Error("hash " + hash + " not found."); 1046 | return captured_functions[hash]; 1047 | } 1048 | }; 1049 | return result; 1050 | }; 1051 | 1052 | Rserve.wrap_all_ocaps = function(s, v) { 1053 | v = v.value.json(s.resolve_hash); 1054 | function replace(obj) { 1055 | var result = obj; 1056 | if (_.isArray(obj) && 1057 | obj.r_attributes && 1058 | obj.r_attributes['class'] == 'OCref') { 1059 | return Rserve.wrap_ocap(s, obj); 1060 | } else if (_.isArray(obj)) { 1061 | result = _.map(obj, replace); 1062 | result.r_type = obj.r_type; 1063 | result.r_attributes = obj.r_attributes; 1064 | } else if (_.isTypedArray(obj)) { 1065 | return obj; 1066 | } else if (_.isFunction(obj)) { 1067 | return obj; 1068 | } else if (_.isObject(obj)) { 1069 | result = _.object(_.map(obj, function(v, k) { 1070 | return [k, replace(v)]; 1071 | })); 1072 | } 1073 | return result; 1074 | } 1075 | return replace(v); 1076 | }; 1077 | 1078 | Rserve.wrap_ocap = function(s, ocap) { 1079 | var wrapped_ocap = function() { 1080 | var values = _.toArray(arguments); 1081 | // common error (tho this won't catch the case where last arg is a function) 1082 | if(!values.length || !_.isFunction(values[values.length-1])) 1083 | throw new Error("forgot to pass continuation to ocap"); 1084 | var k = values.pop(); 1085 | s.OCcall(ocap, values, function(err, v) { 1086 | if (!_.isUndefined(v)) 1087 | v = Rserve.wrap_all_ocaps(s, v); 1088 | k(err, v); 1089 | }); 1090 | }; 1091 | wrapped_ocap.bare_ocap = ocap; 1092 | return wrapped_ocap; 1093 | }; 1094 | 1095 | })(); 1096 | Rserve.RserveError = function(message, status_code) { 1097 | this.name = "RserveError"; 1098 | this.message = message; 1099 | this.status_code = status_code; 1100 | }; 1101 | 1102 | Rserve.RserveError.prototype = Object.create(Error); 1103 | Rserve.RserveError.prototype.constructor = Rserve.RserveError; 1104 | (function () { 1105 | 1106 | _.mixin({ 1107 | isTypedArray: function(v) { 1108 | if (!_.isObject(v)) 1109 | return false; 1110 | return !_.isUndefined(v.byteLength) && !_.isUndefined(v.BYTES_PER_ELEMENT); 1111 | } 1112 | }); 1113 | 1114 | // type_id tries to match some javascript values to Rserve value types 1115 | Rserve.type_id = function(value) 1116 | { 1117 | if (_.isNull(value) || _.isUndefined(value)) 1118 | return Rserve.Rsrv.XT_NULL; 1119 | var type_dispatch = { 1120 | "boolean": Rserve.Rsrv.XT_ARRAY_BOOL, 1121 | "number": Rserve.Rsrv.XT_ARRAY_DOUBLE, 1122 | "string": Rserve.Rsrv.XT_ARRAY_STR // base strings need to be array_str or R gets confused? 1123 | }; 1124 | if (!_.isUndefined(type_dispatch[typeof value])) 1125 | return type_dispatch[typeof value]; 1126 | 1127 | // typed arrays 1128 | if (_.isTypedArray(value)) 1129 | return Rserve.Rsrv.XT_ARRAY_DOUBLE; 1130 | 1131 | // arraybuffers 1132 | if (!_.isUndefined(value.byteLength) && !_.isUndefined(value.slice)) 1133 | return Rserve.Rsrv.XT_RAW; 1134 | 1135 | // lists of strings (important for tags) 1136 | if (_.isArray(value) && _.all(value, function(el) { return typeof el === 'string'; })) 1137 | return Rserve.Rsrv.XT_ARRAY_STR; 1138 | 1139 | if (_.isArray(value) && _.all(value, function(el) { return typeof el === 'boolean'; })) 1140 | return Rserve.Rsrv.XT_ARRAY_BOOL; 1141 | 1142 | // arbitrary lists 1143 | if (_.isArray(value)) 1144 | return Rserve.Rsrv.XT_VECTOR; 1145 | 1146 | // functions get passed as an array_str with extra attributes 1147 | if (_.isFunction(value)) 1148 | return Rserve.Rsrv.XT_ARRAY_STR | Rserve.Rsrv.XT_HAS_ATTR; 1149 | 1150 | // objects 1151 | if (_.isObject(value)) 1152 | return Rserve.Rsrv.XT_VECTOR | Rserve.Rsrv.XT_HAS_ATTR; 1153 | 1154 | throw new Rserve.RServeError("Value type unrecognized by Rserve: " + value); 1155 | }; 1156 | 1157 | // FIXME this is really slow, as it's walking the object many many times. 1158 | Rserve.determine_size = function(value, forced_type) 1159 | { 1160 | function list_size(lst) { 1161 | return _.reduce(lst, function(memo, el) { 1162 | return memo + Rserve.determine_size(el); 1163 | }, 0); 1164 | } 1165 | function final_size(payload_size) { 1166 | if (payload_size > (1 << 24)) 1167 | return payload_size + 8; // large header 1168 | else 1169 | return payload_size + 4; 1170 | } 1171 | var header_size = 4, t = forced_type || Rserve.type_id(value); 1172 | 1173 | switch (t & ~Rserve.Rsrv.XT_LARGE) { 1174 | case Rserve.Rsrv.XT_NULL: 1175 | return final_size(0); 1176 | case Rserve.Rsrv.XT_ARRAY_BOOL: 1177 | if (_.isBoolean(value)) 1178 | return final_size(8); 1179 | else 1180 | return final_size((value.length + 7) & ~3); 1181 | case Rserve.Rsrv.XT_ARRAY_STR: 1182 | if (_.isArray(value)) 1183 | return final_size(_.reduce(value, function(memo, str) { 1184 | return memo + str.length + 1; 1185 | }, 0)); 1186 | else 1187 | return final_size(value.length + 1); 1188 | case Rserve.Rsrv.XT_ARRAY_DOUBLE: 1189 | if (_.isNumber(value)) 1190 | return final_size(8); 1191 | else 1192 | return final_size(8 * value.length); 1193 | case Rserve.Rsrv.XT_RAW: 1194 | return final_size(4 + value.byteLength); 1195 | case Rserve.Rsrv.XT_VECTOR: 1196 | case Rserve.Rsrv.XT_LANG_NOTAG: 1197 | return final_size(list_size(value)); 1198 | case Rserve.Rsrv.XT_VECTOR | Rserve.Rsrv.XT_HAS_ATTR: // a named list (that is, a js object) 1199 | var names_size_1 = final_size("names".length + 3); 1200 | var names_size_2 = Rserve.determine_size(_.keys(value)); 1201 | var names_size = final_size(names_size_1 + names_size_2); 1202 | return final_size(names_size + list_size(_.values(value))); 1203 | /* return header_size // XT_VECTOR | XT_HAS_ATTR 1204 | + header_size // XT_LIST_TAG (attribute) 1205 | + header_size + "names".length + 3 // length of 'names' + padding (tag as XT_SYMNAME) 1206 | + Rserve.determine_size(_.keys(value)) // length of names 1207 | + list_size(_.values(value)); // length of values 1208 | */ 1209 | case Rserve.Rsrv.XT_ARRAY_STR | Rserve.Rsrv.XT_HAS_ATTR: // js->r ocap (that is, a js function) 1210 | return Rserve.determine_size("0403556553") // length of ocap nonce; that number is meaningless aside from length 1211 | + header_size // XT_LIST_TAG (attribute) 1212 | + header_size + "class".length + 3 // length of 'class' + padding (tag as XT_SYMNAME) 1213 | + Rserve.determine_size(["javascript_function"]); // length of class name 1214 | 1215 | default: 1216 | throw new Rserve.RserveError("Internal error, can't handle type " + t); 1217 | } 1218 | }; 1219 | 1220 | Rserve.write_into_view = function(value, array_buffer_view, forced_type, convert) 1221 | { 1222 | var size = Rserve.determine_size(value, forced_type); 1223 | var is_large = size > 16777215; 1224 | // if (size > 16777215) 1225 | // throw new Rserve.RserveError("Can't currently handle objects >16MB"); 1226 | var t = forced_type || Rserve.type_id(value), i, current_offset, lbl; 1227 | if (is_large) 1228 | t = t | Rserve.Rsrv.XT_LARGE; 1229 | var read_view; 1230 | var write_view = array_buffer_view.data_view(); 1231 | var payload_start; 1232 | if (is_large) { 1233 | payload_start = 8; 1234 | write_view.setInt32(0, t + ((size - 8) << 8)); 1235 | write_view.setInt32(4, (size - 8) >> 24); 1236 | } else { 1237 | payload_start = 4; 1238 | write_view.setInt32(0, t + ((size - 4) << 8)); 1239 | } 1240 | 1241 | switch (t & ~Rserve.Rsrv.XT_LARGE) { 1242 | case Rserve.Rsrv.XT_NULL: 1243 | break; 1244 | case Rserve.Rsrv.XT_ARRAY_BOOL: 1245 | if (_.isBoolean(value)) { 1246 | write_view.setInt32(payload_start, 1); 1247 | write_view.setInt8(payload_start + 4, value ? 1 : 0); 1248 | } else { 1249 | write_view.setInt32(payload_start, value.length); 1250 | for (i=0; i