diff --git a/example/js/app.js b/example/js/app.js new file mode 100644 index 000000000..0ddd16ade --- /dev/null +++ b/example/js/app.js @@ -0,0 +1,87 @@ +server = require("./server") +wire = require("./wire") +util = require("util") + +function CounterApp(){ + this.hashCount = 0; + this.txCount = 0; + this.commitCount = 0; +}; + +CounterApp.prototype.open = function(){ + return new CounterAppContext(this); +} + +function CounterAppContext(app) { + this.hashCount = app.hashCount; + this.txCount = app.txCount; + this.commitCount = app.commitCount; + this.serial = false; +} + +CounterAppContext.prototype.echo = function(msg){ + return {"response": msg, "ret_code":0} +} + +CounterAppContext.prototype.info = function(){ + return {"response": [util.format("hash, tx, commit counts: %d, %d, %d", this.hashCount, this.txCount, this.commitCount)]} +} + +CounterAppContext.prototype.set_option = function(key, value){ + if (key == "serial" && value == "on"){ + this.serial = true; + } + return {"ret_code":0} +} + +CounterAppContext.prototype.append_tx = function(txBytes){ + if (this.serial) { + txByteArray = txBytes + if (txByte.length >= 2 && txBytes.slice(0, 2) == "0x") { + txByteArray = wire.hex2bytes(txBytes.slice(2)); + } + r = new wire.BytesReader(txByteArray) + txValue = decode_big_endian(r, txBytes.length) + if (txValue != this.txcount){ + return {"ret_code":1} + } + } + this.txCount += 1; + return {"ret_code":0} // TODO: return events +} + +CounterAppContext.prototype.get_hash = function(){ + this.hashCount += 1; + if (this.txCount == 0){ + return {"response": "", "ret_code":0} + } + h = wire.encode_big_endian(this.txCount, 8); + h = wire.reverse(h); // TODO + return {"response": h.toString(), "ret_code":0} +} + +CounterAppContext.prototype.commit = function(){ + this.commitCount += 1; + return {"ret_code":0} +} + +CounterAppContext.prototype.rollback = function(){ + return {"ret_code":0} +} + +CounterAppContext.prototype.add_listener = function(){ + return {"ret_code":0} +} + +CounterAppContext.prototype.rm_listener = function(){ + return {"ret_code":0} +} + +CounterAppContext.prototype.event = function(){ +} + +console.log("Counter app in Javascript") + +var app = new CounterApp(); +var appServer = new server.AppServer(app); +appServer.server.listen(46658) diff --git a/example/js/msgs.js b/example/js/msgs.js new file mode 100644 index 000000000..a8b90ecd8 --- /dev/null +++ b/example/js/msgs.js @@ -0,0 +1,64 @@ +wire = require("./wire") + +module.exports = { + types : { + 0x01 : "echo", + 0x02 : "flush", + 0x03 : "info", + 0x04 : "set_option", + 0x21 : "append_tx", + 0x22 : "get_hash", + 0x23 : "commit", + 0x24 : "rollback", + 0x25 : "add_listener", + 0x26 : "rm_listener", + }, + + decoder : RequestDecoder, + + buffer: BytesBuffer + +} + +function RequestDecoder(buf){ + this.buf= buf +} + +var decode_string = wire.decode_string + +// return nothing, one thing, or a list of things +RequestDecoder.prototype.echo = function(){ return decode_string(this.buf) }; +RequestDecoder.prototype.flush = function(){}; +RequestDecoder.prototype.info = function(){}; +RequestDecoder.prototype.set_option = function(){ return [decode_string(this.buf), decode_string(this.buf)] }; +RequestDecoder.prototype.append_tx = function(){ return decode_string(this.buf)}; +RequestDecoder.prototype.get_hash = function(){ }; +RequestDecoder.prototype.commit = function(){ }; +RequestDecoder.prototype.rollback = function(){ }; +RequestDecoder.prototype.add_listener = function(){ }; // TODO +RequestDecoder.prototype.rm_listener = function(){ }; // TODO + +// buffered reader with read(n) method +function BytesBuffer(buf){ + this.buf = buf +} + +BytesBuffer.prototype.read = function(n){ + b = this.buf.slice(0, n) + this.buf = this.buf.slice(n) + return b +}; + +BytesBuffer.prototype.write = function(buf){ + this.buf = Buffer.concat([this.buf, buf]); +}; + + +BytesBuffer.prototype.size = function(){ + return this.buf.length +} + +BytesBuffer.prototype.peek = function(){ + return this.buf[0] +} + diff --git a/example/js/server.js b/example/js/server.js new file mode 100644 index 000000000..57af143e6 --- /dev/null +++ b/example/js/server.js @@ -0,0 +1,130 @@ + +// Load the TCP Library +net = require('net'); +msg = require('./msgs'); +wire = require("./wire") + +// Takes an application and handles tmsp connection +// which invoke methods on the app +function AppServer(app){ + // set the app for the socket handler + this.app = app; + + // create a server by providing callback for + // accepting new connection and callbacks for + // connection events ('data', 'end', etc.) + this.createServer() +} + +module.exports = { AppServer: AppServer }; + +AppServer.prototype.createServer = function(){ + app = this.app + conns = {} // map sockets to their state + + // define the socket handler + this.server = net.createServer(function(socket){ + socket.name = socket.remoteAddress + ":" + socket.remotePort + console.log("new connection from", socket.name) + + appCtx = app.open() + + var conn = { + recBuf: new msg.buffer(new Buffer(0)), + resBuf: new msg.buffer(new Buffer(0)), + msgLength: 0, + inProgress: false + } + conns[socket] = conn + + // Handle tmsp requests. + socket.on('data', function (data) { + + if (data.length == 0){ + // TODO err + console.log("empty data!") + return + } + conn = conns[socket] + + // we received data. append it + conn.recBuf.write(data) + + while ( conn.recBuf.size() > 0 ){ + + if (conn.msgLength == 0){ + ll = conn.recBuf.peek(); + if (conn.recBuf.size() < 1 + ll){ + // don't have enough bytes to read length yet + return + } + conn.msgLength = wire.decode_varint(conn.recBuf) + } + + if (conn.recBuf.size() < conn.msgLength) { + // don't have enough to decode the message + return + } + + // now we can decode + typeByte = conn.recBuf.read(1); + resTypeByte = typeByte[0] + 0x10 + reqType = msg.types[typeByte[0]]; + + if (reqType == "flush"){ + // msgs are length prefixed + conn.resBuf.write(wire.encode(1)); + conn.resBuf.write(new Buffer([resTypeByte])) + n = socket.write(conn.resBuf.buf); + conn.msgLength = 0; + conn.resBuf = new msg.buffer(new Buffer(0)); + return + } + + // decode args + decoder = new msg.decoder(conn.recBuf); + args = decoder[reqType](); + + // done decoding + conn.msgLength = 0 + + // NOTE: this throws of the "this"'s in app.js + //reqFunc = appCtx[reqType]; + var res = function(){ + if (args == null){ + return appCtx[reqType](); + } else if (Array.isArray(args)){ + return appCtx[reqType].apply(this, args); + } else { + return appCtx[reqType](args) + } + }() + + + var retCode = res["ret_code"] + var res = res["response"] + + if (retCode != null && retCode != 0){ + console.log("non-zero ret code", retCode) + } + + + if (reqType == "echo" || reqType == "info"){ + enc = Buffer.concat([new Buffer([resTypeByte]), wire.encode(res)]); + // length prefixed + conn.resBuf.write(wire.encode(enc.length)); + conn.resBuf.write(enc); + } else { + enc = Buffer.concat([new Buffer([resTypeByte]), wire.encode(retCode), wire.encode(res)]); + conn.resBuf.write(wire.encode(enc.length)); + conn.resBuf.write(enc); + } + } + }); + + socket.on('end', function () { + console.log("connection ended") + }); + }) +} + diff --git a/example/js/wire.js b/example/js/wire.js new file mode 100644 index 000000000..87cc9234e --- /dev/null +++ b/example/js/wire.js @@ -0,0 +1,112 @@ +math = require("math") + +module.exports = { + decode_string: decode_string, + decode_varint: decode_varint, + encode_big_endian: encode_big_endian, + encode: encode, + reverse: reverse, +} + +function reverse(buf){ + for (var i = 0; i < buf.length/2; i++){ + a = buf[i]; + b = buf[buf.length-1 - i]; + buf[i] = b; + buf[buf.length-1 - i] = a; + } + return buf +} + +function uvarint_size(i){ + if (i == 0){ + return 0 + } + + for(var j = 1; j < 9; j++) { + if ( i < 1< 0xF0){ negate = true } + if (negate) { size = size - 0xF0 } + i = decode_big_endian(reader, size); + if (negate) { i = i * -1} + return i +} + +function encode_list(l){ + var l2 = l.map(encode); + var buf = new Buffer(encode_varint(l2.length)); + return Buffer.concat([buf, Buffer.concat(l2)]); +} + +function encode(b){ + if (b == null){ + return Buffer(0) + } else if (typeof b == "number"){ + return encode_varint(b) + } else if (typeof b == "string"){ + return encode_string(b) + } else if (Array.isArray(b)){ + return encode_list(b) + } else{ + console.log("UNSUPPORTED TYPE!", typeof b, b) + } +} + + + + + diff --git a/tests/test.sh b/tests/test.sh index 848d14cbc..9ee9be135 100644 --- a/tests/test.sh +++ b/tests/test.sh @@ -11,3 +11,7 @@ bash tests/test_counter.sh # test python counter cd example/python COUNTER_APP="python app.py" bash $ROOT/tests/test_counter.sh + +# test js counter +cd ../js +COUNTER_APP="node app.js" bash $ROOT/tests/test_counter.sh diff --git a/tests/test_counter.sh b/tests/test_counter.sh index 41909dde8..8bbd8d376 100644 --- a/tests/test_counter.sh +++ b/tests/test_counter.sh @@ -11,7 +11,7 @@ $COUNTER_APP &> /dev/null & PID=`echo $!` if [[ "$?" != 0 ]]; then - echo "Error running tmsp command" + echo "Error running tmsp app" echo $OUTPUT exit 1 fi @@ -24,7 +24,7 @@ append_tx abc STDIN` if [[ "$?" != 0 ]]; then - echo "Error running tmsp command" + echo "Error running tmsp batch command" echo $OUTPUT exit 1 fi