lnsocket

A minimal C library for connecting to the lightning network
git clone git://jb55.com/lnsocket
Log | Files | Refs | Submodules | README | LICENSE

lnsocket_lib.js (11045B)


      1 
      2 async function lnsocket_init() {
      3 	const module = await Module()
      4 
      5 	function SocketImpl(host) {
      6 		if (!(this instanceof SocketImpl))
      7 			return new SocketImpl(host)
      8 
      9 		if (typeof WebSocket !== 'undefined') {
     10 			console.log("WebSocket", typeof WebSocket)
     11 			const ok = host.startsWith("ws://") || host.startsWith("wss://")
     12 			if (!ok)
     13 				throw new Error("host must start with ws:// or wss://")
     14 			const ws = new WebSocket(host)
     15 			ws.ondata = function(fn) {
     16 				ws.onmessage = (v) => {
     17 					const data = v.data.arrayBuffer()
     18 					fn(data)
     19 				}
     20 			}
     21 			return ws
     22 		}
     23 
     24 		//
     25 		// we're in nodejs
     26 		//
     27 		const net = require('net')
     28 		let [hostname,port] = host.split(":")
     29 		port = +port || 9735
     30 		const socket = net.createConnection(port, hostname, () => {
     31 			socket.emit("open")
     32 		})
     33 		socket.addEventListener = socket.on.bind(socket)
     34 
     35 		if (socket.onmessage)
     36 			throw new Error("socket already has onmessage?")
     37 
     38 		socket.ondata = (fn) => {
     39 			socket.on('data', fn)
     40 		}
     41 
     42 		socket.close = () => {
     43 			socket.destroy()
     44 		}
     45 
     46 		if (socket.send)
     47 			throw new Error("socket already has send?")
     48 
     49 		socket.send = function socket_send(data) {
     50 			return new Promise((resolve, reject) => {
     51 				socket.write(data, resolve)
     52 			});
     53 		}
     54 
     55 		return socket
     56 	}
     57 
     58 	const ACT_ONE_SIZE = 50
     59 	const ACT_TWO_SIZE = 50
     60 	const ACT_THREE_SIZE = 66
     61 	const DEFAULT_TIMEOUT = 15000
     62 
     63 	const COMMANDO_REPLY_CONTINUES = 0x594b
     64 	const COMMANDO_REPLY_TERM = 0x594d
     65 
     66 	const lnsocket_create = module.cwrap("lnsocket_create", "number")
     67 	const lnsocket_destroy = module.cwrap("lnsocket_destroy", "number")
     68 	const lnsocket_encrypt = module.cwrap("lnsocket_encrypt", "number", ["int", "array", "int", "int"])
     69 	const lnsocket_decrypt = module.cwrap("lnsocket_decrypt", "number", ["int", "array", "int"])
     70 	const lnsocket_decrypt_header = module.cwrap("lnsocket_decrypt_header", "number", ["number", "array"])
     71 	const lnsocket_msgbuf = module.cwrap("lnsocket_msgbuf", "number", ["int"])
     72 	const lnsocket_act_one = module.cwrap("lnsocket_act_one", "number", ["number", "string"])
     73 	const lnsocket_act_two = module.cwrap("lnsocket_act_two", "number", ["number", "array"])
     74 	const lnsocket_print_errors = module.cwrap("lnsocket_print_errors", "int")
     75 	const lnsocket_genkey = module.cwrap("lnsocket_genkey", null, ["number"])
     76 	const lnsocket_setkey = module.cwrap("lnsocket_setkey", "number", ["number", "array"])
     77 	const lnsocket_make_default_initmsg = module.cwrap("lnsocket_make_default_initmsg", "int", ["int", "int"])
     78 	const lnsocket_make_ping_msg = module.cwrap("lnsocket_make_ping_msg", "int", ["int", "int", "int", "int"])
     79 	const commando_make_rpc_msg = module.cwrap("commando_make_rpc_msg", "int", ["string", "string", "string", "number", "int", "int"])
     80 
     81 	function concat_u8_arrays(arrays) {
     82 		// sum of individual array lengths
     83 		let totalLength = arrays.reduce((acc, value) =>
     84 			acc + (value.length || value.byteLength)
     85 		, 0);
     86 
     87 		if (!arrays.length) return null;
     88 
     89 		let result = new Uint8Array(totalLength);
     90 
     91 		let length = 0;
     92 		for (let array of arrays) {
     93 			if (array instanceof ArrayBuffer)
     94 				result.set(new Uint8Array(array), length);
     95 			else
     96 				result.set(array, length);
     97 
     98 			length += (array.length || array.byteLength);
     99 		}
    100 
    101 		return result;
    102 	}
    103 
    104 	function parse_msgtype(buf) {
    105 		return buf[0] << 8 | buf[1]
    106 	}
    107 
    108 	function wasm_free(buf) {
    109 		module._free(buf);
    110 	}
    111 
    112 	function char_to_hex(cstr) {
    113 		const c = cstr.charCodeAt(0)
    114 		// c >= 0 && c <= 9
    115 		if (c >= 48 && c <= 57) {
    116 			return c - 48;
    117 		}
    118 		// c >= a && c <= f
    119 		if (c >= 97 && c <= 102) {
    120 			return c - 97 + 10;
    121 		}
    122 		// c >= A && c <= F
    123 		if (c >= 65 && c <= 70) {
    124 			return c - 65 + 10;
    125 		}
    126 		return -1;
    127 	}
    128 
    129 
    130 	function hex_decode(str, buflen)
    131 	{
    132 		let bufsize = buflen || 33
    133 		let c1, c2
    134 		let i = 0
    135 		let j = 0
    136 		let buf = new Uint8Array(bufsize)
    137 		let slen = str.length
    138 		while (slen > 1) {
    139 			if (-1==(c1 = char_to_hex(str[j])) || -1==(c2 = char_to_hex(str[j+1])))
    140 				return null;
    141 			if (!bufsize)
    142 				return null;
    143 			j += 2
    144 			slen -= 2
    145 			buf[i++] = (c1 << 4) | c2
    146 			bufsize--;
    147 		}
    148 
    149 		return buf
    150 	}
    151 
    152 	function wasm_alloc(len) {
    153 		const buf = module._malloc(len);
    154 		module.HEAPU8.set(Uint8Array, buf);
    155 		return buf
    156 	}
    157 
    158 	function wasm_mem(ptr, size) {
    159 		return new Uint8Array(module.HEAPU8.buffer, ptr, size);
    160 	}
    161 
    162 	function LNSocket(opts) {
    163 		if (!(this instanceof LNSocket))
    164 			return new LNSocket(opts)
    165 
    166 		this.opts = opts || {
    167 			timeout: DEFAULT_TIMEOUT
    168 		}
    169 		this.queue = []
    170 		this.ln = lnsocket_create()
    171 	}
    172 
    173 	LNSocket.prototype.queue_recv = function() {
    174 		let self = this
    175 		return new Promise((resolve, reject) => {
    176 			const checker = setInterval(() => {
    177 				const val = self.queue.shift()
    178 				if (val) {
    179 					clearInterval(checker)
    180 					resolve(val)
    181 				} else if (!self.connected) {
    182 					clearInterval(checker)
    183 					reject()
    184 				}
    185 			}, 5);
    186 		})
    187 	}
    188 
    189 	LNSocket.prototype.print_errors = function _lnsocket_print_errors() {
    190 		lnsocket_print_errors(this.ln)
    191 	}
    192 
    193 	LNSocket.prototype.genkey = function _lnsocket_genkey() {
    194 		lnsocket_genkey(this.ln)
    195 	}
    196 
    197 	LNSocket.prototype.setkeyraw = function lnsocket_setkeyraw(rawkey) {
    198 		return lnsocket_setkey(this.ln, rawkey)
    199 	}
    200 
    201 	LNSocket.prototype.setkey = function _lnsocket_setkey(key) {
    202 		const rawkey = hex_decode(key)
    203 		return this.setkeyraw(rawkey)
    204 	}
    205 
    206 	LNSocket.prototype.act_one_data = function _lnsocket_act_one(node_id) {
    207 		const act_one_ptr = lnsocket_act_one(this.ln, node_id)
    208 		if (act_one_ptr === 0)
    209 			return null
    210 		return wasm_mem(act_one_ptr, ACT_ONE_SIZE)
    211 	}
    212 
    213 	LNSocket.prototype.act_two = function _lnsocket_act_two(act2) {
    214 		const act_three_ptr = lnsocket_act_two(this.ln, new Uint8Array(act2))
    215 		if (act_three_ptr === 0) {
    216 			this.print_errors()
    217 			return null
    218 		}
    219 		return wasm_mem(act_three_ptr, ACT_THREE_SIZE)
    220 	}
    221 
    222 	LNSocket.prototype.connect = async function lnsocket_connect(node_id, host) {
    223 		await handle_connect(this, node_id, host)
    224 
    225 		const act1 = this.act_one_data(node_id)
    226 		this.ws.send(act1)
    227 		const act2 = await this.read_all(ACT_TWO_SIZE)
    228 		if (act2.length != ACT_TWO_SIZE) {
    229 			throw new Error(`expected act2 to be ${ACT_TWO_SIZE} long, got ${act2.length}`)
    230 		}
    231 		const act3 = this.act_two(act2)
    232 		this.ws.send(act3)
    233 	}
    234 
    235 	LNSocket.prototype.connect_and_init = async function _connect_and_init(node_id, host) {
    236 		await this.connect(node_id, host)
    237 		await this.perform_init()
    238 	}
    239 
    240 	LNSocket.prototype.read_all = async function read_all(n) {
    241 		let count = 0
    242 		let chunks = []
    243 		if (!this.connected)
    244 			throw new Error("read_all: not connected")
    245 		while (true) {
    246 			let res = await this.queue_recv()
    247 
    248 			const remaining = n - count
    249 
    250 			if (res.byteLength > remaining) {
    251 				chunks.push(res.slice(0, remaining))
    252 				this.queue.unshift(res.slice(remaining))
    253 				break
    254 			} else if (res.byteLength === remaining) {
    255 				chunks.push(res)
    256 				break
    257 			}
    258 
    259 			chunks.push(res)
    260 			count += res.byteLength
    261 		}
    262 
    263 		return concat_u8_arrays(chunks)
    264 	}
    265 
    266 	LNSocket.prototype.read_header = async function read_header() {
    267 		const header = await this.read_all(18)
    268 		if (header.length != 18)
    269 			throw new Error("Failed to read header")
    270 		return lnsocket_decrypt_header(this.ln, header)
    271 	}
    272 
    273 	LNSocket.prototype.rpc = async function lnsocket_rpc(opts) {
    274 		const msg = this.make_commando_msg(opts)
    275 		this.write(msg)
    276 		const res = await this.read_all_rpc()
    277 		return JSON.parse(res)
    278 	}
    279 
    280 	LNSocket.prototype.recv = async function lnsocket_recv() {
    281 		const msg = await this.read()
    282 		const msgtype = parse_msgtype(msg.slice(0,2))
    283 		const res = [msgtype, msg.slice(2)]
    284 		return res
    285 	}
    286 
    287 	LNSocket.prototype.read_all_rpc = async function read_all_rpc() {
    288 		let chunks = []
    289 		while (true) {
    290 			const [typ, msg] = await this.recv()
    291 			switch (typ) {
    292 			case COMMANDO_REPLY_TERM:
    293 				chunks.push(msg.slice(8))
    294 				return new TextDecoder().decode(concat_u8_arrays(chunks));
    295 			case COMMANDO_REPLY_CONTINUES:
    296 				chunks.push(msg.slice(8))
    297 				break
    298 			default:
    299 				console.log("got unknown type", typ)
    300 				continue
    301 			}
    302 		}
    303 	}
    304 
    305 	LNSocket.prototype.make_commando_msg = function _lnsocket_make_commando_msg(opts) {
    306 		const buflen = 4096
    307 		let len = 0;
    308 		const buf = wasm_alloc(buflen);
    309 
    310 		const params = JSON.stringify(opts.params||{})
    311 		if (!(len = commando_make_rpc_msg(opts.method, params, opts.rune,
    312 			0, buf, buflen))) {
    313 			throw new Error("couldn't make commando msg");
    314 		}
    315 
    316 		const dat = wasm_mem(buf, len)
    317 		wasm_free(buf);
    318 		return dat
    319 	}
    320 
    321 	LNSocket.prototype.make_ping_msg = function _lnsocket_make_ping_msg(num_pong_bytes=1, ignored_bytes=1)  {
    322 		const buflen = 32
    323 		let len = 0;
    324 		const buf = wasm_alloc(buflen)
    325 
    326 		if (!(len = lnsocket_make_ping_msg(buf, buflen, num_pong_bytes, ignored_bytes)))
    327 			throw new Error("couldn't make ping msg");
    328 
    329 		const dat = wasm_mem(buf, len)
    330 		wasm_free(buf);
    331 		return dat
    332 	}
    333 
    334 	LNSocket.prototype.encrypt = function _lnsocket_encrypt(dat) {
    335 		const len = lnsocket_encrypt(this.ln, dat, dat.length)
    336 		if (len === 0) {
    337 			this.print_errors()
    338 			throw new Error("encrypt error")
    339 		}
    340 		const enc = wasm_mem(lnsocket_msgbuf(this.ln), len)
    341 		return enc
    342 	}
    343 
    344 	LNSocket.prototype.decrypt = function _lnsocket_decrypt(dat) {
    345 		const len = lnsocket_decrypt(this.ln, dat, dat.length)
    346 		if (len === 0) {
    347 			this.print_errors()
    348 			throw new Error("decrypt error")
    349 		}
    350 		return wasm_mem(lnsocket_msgbuf(this.ln), len)
    351 	}
    352 
    353 	LNSocket.prototype.write = function _lnsocket_write(dat) {
    354 		this.ws.send(this.encrypt(dat))
    355 	}
    356 
    357 	LNSocket.prototype.read = async function _lnsocket_read() {
    358 		const size = await this.read_header()
    359 		const enc = await this.read_all(size+16)
    360 		return this.decrypt(enc)
    361 	}
    362 
    363 	LNSocket.prototype.make_default_initmsg = function _lnsocket_make_default_initmsg() {
    364 		const buflen = 1024
    365 		let len = 0;
    366 		const buf = module._malloc(buflen);
    367 		module.HEAPU8.set(Uint8Array, buf);
    368 
    369 		if (!(len = lnsocket_make_default_initmsg(buf, buflen)))
    370 			throw new Error("couldn't make initmsg");
    371 
    372 		const dat = wasm_mem(buf, len)
    373 		module._free(buf);
    374 		return dat
    375 	}
    376 
    377 	LNSocket.prototype.perform_init = async function lnsocket_connect() {
    378 		await this.read()
    379 		const our_init = this.make_default_initmsg()
    380 		this.write(our_init)
    381 	}
    382 
    383 	LNSocket.prototype.ping_pong = async function lnsocket_ping_pong() {
    384 		const pingmsg = this.make_ping_msg()
    385 		this.write(pingmsg)
    386 		return await this.read()
    387 	}
    388 
    389 	LNSocket.prototype.disconnect = function lnsocket_disconnect() {
    390 		if (this.connected === true && this.ws) {
    391 			this.ws.close()
    392 			return true
    393 		}
    394 		return false
    395 	}
    396 
    397 	LNSocket.prototype.destroy = function _lnsocket_destroy() {
    398 		this.disconnect()
    399 		lnsocket_destroy(this.ln)
    400 	}
    401 
    402 	function handle_connect(ln, node_id, host) {
    403 		const ws = new SocketImpl(host)
    404 		return new Promise((resolve, reject) => {
    405 			const timeout = ln.opts.timeout || DEFAULT_TIMEOUT
    406 			const timer = setTimeout(reject, timeout);
    407 
    408 			ws.ondata((v) => {
    409 				ln.queue.push(v)
    410 			});
    411 
    412 			ws.addEventListener('open', function(ev) {
    413 				ln.ws = ws
    414 				ln.connected = true
    415 				clearTimeout(timer)
    416 				resolve(ws)
    417 			});
    418 
    419 			ws.addEventListener('close', function(ev) {
    420 				ln.connected = false
    421 			});
    422 		})
    423 	}
    424 
    425 	return LNSocket
    426 }
    427 
    428 Module.init = Module.lnsocket_init = lnsocket_init