@ -0,0 +1 @@ | |||
node_modules |
@ -1,59 +1,88 @@ | |||
wire = require("./wire") | |||
var wire = require("js-wire"); | |||
var types = require("./types"); | |||
module.exports = { | |||
types : { | |||
0x01 : "echo", | |||
0x02 : "flush", | |||
0x03 : "info", | |||
0x04 : "set_option", | |||
0x21 : "append_tx", | |||
0x22 : "check_tx", | |||
0x23 : "get_hash", | |||
0x24 : "add_listener", | |||
0x25 : "rm_listener", | |||
}, | |||
decoder : RequestDecoder, | |||
buffer: BytesBuffer | |||
} | |||
function RequestDecoder(buf){ | |||
this.buf= buf | |||
} | |||
var decode_string = wire.decode_string | |||
var readRequestInfo = function(r) { return []; }; | |||
var readRequestSetOption = function(r) { return [r.readString(), r.readString()]; }; | |||
var readRequestAppendTx = function(r) { return [r.readByteArray()]; }; | |||
var readRequestCheckTx = function(r) { return [r.readByteArray()]; }; | |||
var readRequestGetHash = function(r) { return []; }; | |||
var readRequestQuery = function(r) { return [r.readByteArray()]; }; | |||
// 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.check_tx = function(){ return decode_string(this.buf)}; | |||
RequestDecoder.prototype.get_hash = 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 | |||
var runOnce = function(name, f) { | |||
var ran = false; | |||
return function() { | |||
if (ran) { | |||
console.log("Error: response was already written for "+name); | |||
return | |||
} else { | |||
ran = true; | |||
} | |||
return f.apply(this, arguments); | |||
}; | |||
}; | |||
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] | |||
} | |||
var makeWriteResponseInfo = function(w, cb) { return runOnce("info", function(info) { | |||
w.writeUint8(types.ResponseTypeInfo); | |||
w.writeString(info); | |||
cb(w); | |||
});}; | |||
var makeWriteResponseSetOption = function(w, cb) { return runOnce("set_option", function(log) { | |||
w.writeUint8(types.ResponseTypeSetOption); | |||
w.writeString(log); | |||
cb(w); | |||
});}; | |||
var makeWriteResponseAppendTx = function(w, cb) { return runOnce("append_tx", function(code, result, log) { | |||
w.writeUint8(types.ResponseTypeAppendTx); | |||
w.writeUint8(code); | |||
w.writeByteArray(result); | |||
w.writeString(log); | |||
cb(w); | |||
});}; | |||
var makeWriteResponseCheckTx = function(w, cb) { return runOnce("check_tx", function(code, result, log) { | |||
w.writeUint8(types.ResponseTypeCheckTx); | |||
w.writeUint8(code); | |||
w.writeByteArray(result); | |||
w.writeString(log); | |||
cb(w); | |||
});}; | |||
var makeWriteResponseGetHash = function(w, cb) { return runOnce("get_hash", function(hash, log) { | |||
w.writeUint8(types.ResponseTypeGetHash); | |||
w.writeByteArray(hash); | |||
w.writeString(log); | |||
cb(w); | |||
});}; | |||
var makeWriteResponseQuery = function(w, cb) { return runOnce("query", function(result, log) { | |||
w.writeUint8(types.ResponseTypeQuery); | |||
w.writeByteArray(result); | |||
w.writeString(log); | |||
cb(w); | |||
});}; | |||
module.exports = { | |||
types : { | |||
0x01 : "echo", | |||
0x02 : "flush", | |||
0x03 : "info", | |||
0x04 : "set_option", | |||
0x21 : "append_tx", | |||
0x22 : "check_tx", | |||
0x23 : "get_hash", | |||
0x24 : "query", | |||
}, | |||
readers : { | |||
"info": readRequestInfo, | |||
"set_option": readRequestSetOption, | |||
"append_tx": readRequestAppendTx, | |||
"check_tx": readRequestCheckTx, | |||
"get_hash": readRequestGetHash, | |||
"query": readRequestQuery, | |||
}, | |||
writerGenerators: { | |||
"info": makeWriteResponseInfo, | |||
"set_option": makeWriteResponseSetOption, | |||
"append_tx": makeWriteResponseAppendTx, | |||
"check_tx": makeWriteResponseCheckTx, | |||
"get_hash": makeWriteResponseGetHash, | |||
"query": makeWriteResponseQuery, | |||
}, | |||
}; |
@ -0,0 +1,10 @@ | |||
{ | |||
"name": "example", | |||
"version": "0.0.1", | |||
"description": "Example javascript TMSP application", | |||
"main": "index.js", | |||
"dependencies": { | |||
"js-wire": "0.0.2" | |||
} | |||
} | |||
@ -1,126 +1,141 @@ | |||
var net = require("net"); | |||
var wire = require("js-wire"); | |||
var msg = require("./msgs"); | |||
var types = require("./types"); | |||
// Load the TCP Library | |||
net = require('net'); | |||
msg = require('./msgs'); | |||
wire = require("./wire") | |||
var maxWriteBufferLength = 4096; // Any more and flush | |||
// Takes an application and handles tmsp connection | |||
// 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; | |||
// 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() | |||
// 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() { | |||
var app = this.app; | |||
// Define the socket handler | |||
this.server = net.createServer(function(socket) { | |||
socket.name = socket.remoteAddress + ":" + socket.remotePort; | |||
console.log("new connection from", socket.name); | |||
var conn = new Connection(socket, function(msgBytes, cb) { | |||
var r = new wire.Reader(msgBytes); | |||
// Now we can decode | |||
var typeByte = r.readByte(); | |||
var reqType = msg.types[typeByte]; | |||
// Special messages. | |||
// NOTE: msgs are length prefixed | |||
if (reqType == "flush") { | |||
var w = new wire.Writer(); | |||
w.writeByte(types.ResponseTypeFlush); | |||
conn.writeMessage(w.getBuffer()); | |||
conn.flush(); | |||
return cb(); | |||
} else if (reqType == "echo") { | |||
var message = r.readString(); | |||
var w = new wire.Writer(); | |||
w.writeByte(types.ResponseTypeEcho); | |||
w.writeString(message); | |||
conn.writeMessage(w.getBuffer()); | |||
return cb(); | |||
} | |||
// Make callback by wrapping cp | |||
var resCb = msg.writerGenerators[reqType](new wire.Writer(), function(w) { | |||
conn.writeMessage(w.getBuffer()); | |||
return cb(); | |||
}); | |||
// Decode arguments | |||
var args = msg.readers[reqType](r); | |||
args.unshift(resCb); | |||
// Call function | |||
var res = app[reqType].apply(app, args); | |||
if (res != undefined) { | |||
console.log("Message handler shouldn't return anything!"); | |||
} | |||
}); | |||
}); | |||
} | |||
//---------------------------------------- | |||
function Connection(socket, msgCb) { | |||
this.socket = socket; | |||
this.recvBuf = new Buffer(0); | |||
this.sendBuf = new Buffer(0); | |||
this.msgCb = msgCb; | |||
this.waitingResult = false; | |||
var conn = this; | |||
// Handle TMSP requests. | |||
socket.on('data', function(data) { | |||
conn.appendData(data); | |||
}); | |||
socket.on('end', function() { | |||
console.log("connection ended"); | |||
}); | |||
} | |||
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) | |||
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 | |||
var res = function(){ | |||
if (args == null){ | |||
return app[reqType](); | |||
} else if (Array.isArray(args)){ | |||
return app[reqType].apply(app, args); | |||
} else { | |||
return app[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") | |||
}); | |||
}) | |||
Connection.prototype.appendData = function(bytes) { | |||
var conn = this; | |||
if (bytes.length > 0) { | |||
this.recvBuf = Buffer.concat([this.recvBuf, new Buffer(bytes)]); | |||
} | |||
if (this.waitingResult) { | |||
return; | |||
} | |||
var r = new wire.Reader(this.recvBuf); | |||
var msg; | |||
try { | |||
msg = r.readByteArray(); | |||
} catch(e) { | |||
return; | |||
} | |||
this.recvBuf = r.buf.slice(r.offset); | |||
this.waitingResult = true; | |||
this.socket.pause(); | |||
//try { | |||
this.msgCb(msg, function() { | |||
// This gets called after msg handler is finished with response. | |||
conn.waitingResult = false; | |||
conn.socket.resume(); | |||
if (conn.recvBuf.length > 0) { | |||
conn.appendData(""); | |||
} | |||
}); | |||
//} catch(e) { | |||
// console.log("FATAL ERROR: ", e); | |||
//} | |||
}; | |||
Connection.prototype.writeMessage = function(msgBytes) { | |||
var msgLength = wire.uvarintSize(msgBytes.length); | |||
var buf = new Buffer(1+msgLength+msgBytes.length); | |||
var w = new wire.Writer(buf); | |||
w.writeByteArray(msgBytes); // TODO technically should be writeVarint | |||
this.sendBuf = Buffer.concat([this.sendBuf, w.getBuffer()]); | |||
if (this.sendBuf.length >= maxWriteBufferLength) { | |||
this.flush(); | |||
} | |||
}; | |||
Connection.prototype.flush = function() { | |||
var n = this.socket.write(this.sendBuf); | |||
this.sendBuf = new Buffer(0); | |||
} | |||
//---------------------------------------- | |||
module.exports = { AppServer: AppServer }; |
@ -0,0 +1,27 @@ | |||
module.exports = { | |||
RetCodeOK: 0, | |||
RetCodeInternalError: 1, | |||
RetCodeUnauthorized: 2, | |||
RetCodeInsufficientFees: 3, | |||
RetCodeUnknownRequest: 4, | |||
RetCodeEncodingError: 5, | |||
RetCodeNonce: 6, | |||
RequestTypeEcho: 0x01, | |||
RequestTypeFlush: 0x02, | |||
RequestTypeInfo: 0x03, | |||
RequestTypeSetOption: 0x04, | |||
RequestTypeAppendTx: 0x21, | |||
RequestTypeCheckTx: 0x22, | |||
RequestTypeGetHash: 0x23, | |||
RequestTypeQuery: 0x24, | |||
ResponseTypeEcho: 0x11, | |||
ResponseTypeFlush: 0x12, | |||
ResponseTypeInfo: 0x13, | |||
ResponseTypeSetOption: 0x14, | |||
ResponseTypeAppendTx: 0x31, | |||
ResponseTypeCheckTx: 0x32, | |||
ResponseTypeGetHash: 0x33, | |||
ResponseTypeQuery: 0x34, | |||
}; |
@ -1,111 +0,0 @@ | |||
module.exports = { | |||
decode_string: decode_string, | |||
decode_varint: decode_varint, | |||
decode_big_endian: decode_big_endian, | |||
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<<j*8 ) { | |||
return j | |||
} | |||
} | |||
return 8 | |||
} | |||
function encode_big_endian(i, size){ | |||
if (size == 0){ | |||
return new Buffer(0); | |||
} | |||
b = encode_big_endian(Math.floor(i/256), size-1); | |||
return Buffer.concat([b, new Buffer([i%256])]); | |||
} | |||
function decode_big_endian(reader, size){ | |||
if (size == 0){ return 0 } | |||
firstByte = reader.read(1)[0]; | |||
return firstByte*(Math.pow(256, size-1)) + decode_big_endian(reader, size-1) | |||
} | |||
function encode_string(s){ | |||
size = encode_varint(s.length); | |||
return Buffer.concat([size, new Buffer(s)]) | |||
} | |||
function decode_string(reader){ | |||
length = decode_varint(reader); | |||
return reader.read(length).toString() | |||
} | |||
function encode_varint(i){ | |||
var negate = false; | |||
if (i < 0){ | |||
negate = true; | |||
i = -i; | |||
} | |||
size = uvarint_size(i); | |||
if (size == 0){ | |||
return new Buffer([0]) | |||
} | |||
big_end = encode_big_endian(i, size); | |||
if (negate){ size += 0xF0 } | |||
var buf = new Buffer([size]); | |||
return Buffer.concat([buf, big_end]) | |||
} | |||
function decode_varint(reader){ | |||
size = reader.read(1)[0]; | |||
if (size == 0 ){ | |||
return 0 | |||
} | |||
var negate = false; | |||
if (size > 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) | |||
} | |||
} | |||
@ -1,6 +0,0 @@ | |||
package types | |||
type Event struct { | |||
Key string | |||
Data []byte | |||
} |