├── .gitignore ├── libs ├── assert.js ├── codec-tools.js ├── codec-http.js └── bintools.js ├── binding.gyp ├── package.json ├── examples ├── udp.js ├── client.js ├── server.js ├── http.js └── timers.js ├── tests ├── test-udp.js └── test-tcp.js ├── src ├── nuv.js └── nuv.c └── .eslintrc.json /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules 3 | -------------------------------------------------------------------------------- /libs/assert.js: -------------------------------------------------------------------------------- 1 | exports.assert = assert 2 | 3 | // lua-style assert helper 4 | function assert (val, message) { 5 | if (!val) throw new Error(message || 'Assertion Failed') 6 | return val 7 | } 8 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [{ 3 | "target_name": "nuv", 4 | "sources": [ "./src/nuv.c" ], 5 | "include_dirs": [ 6 | "", 7 | "license": "MIT", 8 | "dependencies": { 9 | "bindings": "^1.3.0", 10 | "napi-macros": "^1.3.0" 11 | }, 12 | "devDependencies": { 13 | "eslint": "^4.19.1", 14 | "eslint-plugin-import": "^2.11.0", 15 | "eslint-plugin-node": "^6.0.1", 16 | "eslint-plugin-promise": "^3.7.0", 17 | "eslint-plugin-standard": "^3.1.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/udp.js: -------------------------------------------------------------------------------- 1 | const { Udp } = require('..') 2 | 3 | async function serverMain () { 4 | let server = new Udp() 5 | server.bind('0.0.0.0', 1337) 6 | console.log(server.sockname) 7 | for await (let frame of server) { 8 | console.log(frame) 9 | server.send(frame.ip, frame.port, frame.data) 10 | await server.close() 11 | } 12 | } 13 | 14 | async function clientMain () { 15 | let client = new Udp() 16 | console.log('Sending') 17 | await client.send('127.0.0.1', 1337, 'Hello World') 18 | console.log('Sent!') 19 | console.log('Echo back', await client.recv()) 20 | await client.close() 21 | } 22 | 23 | serverMain().catch(console.error) 24 | clientMain().catch(console.error) 25 | -------------------------------------------------------------------------------- /examples/client.js: -------------------------------------------------------------------------------- 1 | const { Client } = require('..') 2 | 3 | let messages = [ 'Hello', 'How are you', 'Goodbye' ] 4 | 5 | async function main () { 6 | // Create a new client and connect to the echo server. 7 | let client = await new Client() 8 | .connect('127.0.0.1', 8080) 9 | 10 | // Show the local and remote ports 11 | console.log('Connected', client.sockname, client.peername) 12 | 13 | // Send several messages to the echo server 14 | for (let message of messages) { 15 | await client.write(message) 16 | console.log('written', {message}) 17 | let reply = await client.read() 18 | console.log('read', {reply: reply.toString()}) 19 | } 20 | 21 | // Shutdown the write stream 22 | await client.write() 23 | 24 | // Read any extra messages from the server. 25 | for await (let message of client) { 26 | console.log('extra', { message: message.toString() }) 27 | } 28 | 29 | await client.close() 30 | console.log('Socket closed') 31 | } 32 | 33 | main().catch(console.error) 34 | -------------------------------------------------------------------------------- /tests/test-udp.js: -------------------------------------------------------------------------------- 1 | const { 2 | /* eslint camelcase: 0 */ 3 | sizeof_uv_udp_t, 4 | sizeof_uv_udp_send_t, 5 | nuv_udp_init, 6 | nuv_udp_bind, 7 | nuv_udp_getsockname, 8 | nuv_udp_send, 9 | nuv_udp_recv_start, 10 | nuv_udp_recv_stop, 11 | nuv_close 12 | } = require('bindings')('nuv.node') 13 | 14 | let server = {} 15 | nuv_udp_init(Buffer.alloc(sizeof_uv_udp_t), server) 16 | nuv_udp_bind(server.handle, '0.0.0.0', 0) 17 | console.log(server) 18 | let addr = nuv_udp_getsockname(server.handle) 19 | console.log(addr) 20 | 21 | server.readBuffer = Buffer.alloc(1024) 22 | server.onRecv = (...args) => { 23 | console.log('onRecv', args) 24 | nuv_udp_recv_stop(server.handle) 25 | nuv_close(server.handle) 26 | } 27 | nuv_udp_recv_start(server.handle) 28 | server.onClose = () => { 29 | console.log('Closed') 30 | } 31 | 32 | let client = {} 33 | nuv_udp_init(Buffer.alloc(sizeof_uv_udp_t), client) 34 | client.onSend = (...args) => { 35 | console.log('onSend', args) 36 | } 37 | nuv_udp_send(Buffer.alloc(sizeof_uv_udp_send_t), client.handle, Buffer.from('Hello'), '127.0.0.1', addr.port) 38 | nuv_close(client.handle) 39 | -------------------------------------------------------------------------------- /examples/server.js: -------------------------------------------------------------------------------- 1 | const { Server } = require('..') 2 | 3 | // Easy server creation with chainable methods. 4 | console.log('Listening:', new Server({ onConnection }) 5 | .bind('0.0.0.0', 8080) 6 | .listen(128) 7 | .sockname) 8 | 9 | async function onConnection () { 10 | // Accept the incoming connection and create a socket 11 | let socket = this.accept() 12 | console.log('Accepted:', socket.sockname, socket.peername) 13 | 14 | // Use async iterators to read TCP packets off the socket. 15 | for await (let chunk of socket) { 16 | console.log('Echo:', { chunk: chunk.toString() }) 17 | 18 | // Since we're waiting on writes to flush, there is automatic backpressure 19 | // all the way back. The iterator automatically calls read_start/read-stop. 20 | await socket.write(chunk) 21 | } 22 | 23 | // When the iterator exits, it means the remote side sent an EOF 24 | console.log('Client sent EOF, shutting down...') 25 | 26 | // Send an extra message to client 27 | await socket.write('Server shutting down...') 28 | 29 | // Writing nothing flushes any pending data and shuts down the write side. 30 | await socket.write() 31 | 32 | // We can now close our socket and the server in general 33 | await socket.close() 34 | await this.close() 35 | } 36 | -------------------------------------------------------------------------------- /examples/http.js: -------------------------------------------------------------------------------- 1 | const { Server } = require('..') 2 | const { encoder, decoder } = require('../libs/codec-http') 3 | 4 | // Easy server creation with chainable methods. 5 | console.log('Listening:', new Server({ onConnection }) 6 | .bind('0.0.0.0', 8080) 7 | .listen(128) 8 | .sockname) 9 | 10 | async function onConnection () { 11 | // Accept the incoming connection and create a socket 12 | let socket = this.accept() 13 | console.log('Accepted:', socket.sockname, socket.peername) 14 | const socketRead = async () => socket.read() 15 | const socketWrite = async value => socket.write(value) 16 | const read = decoder(socketRead) 17 | const write = encoder(socketWrite) 18 | 19 | // Use async iterators to read TCP packets off the socket. 20 | console.log(read) 21 | for await (const head of read) { 22 | console.log(read) 23 | const chunks = [] 24 | console.log(head) 25 | chunks.push(Buffer.from(`${head.method} ${head.path} HTTP/${head.version}\n`)) 26 | let i = 0 27 | for (const val of head.headers) { 28 | chunks.push(Buffer.from(val + (i++ % 2 ? '\n' : ': '))) 29 | } 30 | chunks.push(Buffer.from('\n')) 31 | for await (const chunk of read) { 32 | console.log(read) 33 | console.log(chunk) 34 | chunks.push(chunk) 35 | chunks.push(Buffer.from('\n')) 36 | } 37 | const body = Buffer.concat(chunks) 38 | console.log(read) 39 | console.log(body) 40 | console.log(write) 41 | await write({ 42 | code: 200, 43 | headers: [ 44 | 'Content-Length', body.length 45 | ] 46 | }) 47 | console.log(write) 48 | await write(body) 49 | console.log(write) 50 | } 51 | await socket.write() 52 | } 53 | -------------------------------------------------------------------------------- /examples/timers.js: -------------------------------------------------------------------------------- 1 | const { Timer } = require('..') 2 | 3 | function setTimeout (fn, ms = 0, ...args) { 4 | let timer = new Timer({ 5 | onTimeout () { 6 | clearTimeout(timer) 7 | fn(...args) 8 | } 9 | }) 10 | timer.start(ms) 11 | return timer 12 | } 13 | 14 | function setInterval (fn, ms = 0, ...args) { 15 | let timer = new Timer({ 16 | onTimeout () { fn(...args) } 17 | }) 18 | timer.start(ms, ms) 19 | return timer 20 | } 21 | 22 | function clearTimeout (timer) { 23 | timer.stop() 24 | timer.close() 25 | } 26 | 27 | let clearInterval = clearTimeout 28 | 29 | let idle = new Timer({ 30 | onTimeout () { 31 | console.log('Finally idle') 32 | idle.close() 33 | } 34 | }) 35 | idle.start(0, 100) 36 | console.log('Starting idle timer...', idle) 37 | 38 | console.log('This timeout should cancel') 39 | let timeout = setTimeout(() => { 40 | throw new Error("OOPS! This shouldn't happen") 41 | }, 1000) 42 | console.log('First timeout', timeout) 43 | console.log('first repeat', timeout.repeat) 44 | 45 | console.log('Starting another timeout...') 46 | setTimeout((...args) => { 47 | idle.again() 48 | console.log('Canceling first timeout') 49 | clearTimeout(timeout) 50 | console.log('Second timeout fired', args) 51 | }, 500, 1, 2, 3) 52 | 53 | console.log('Starting interval') 54 | let count = 10 55 | let interval 56 | interval = setInterval((...args) => { 57 | idle.again() 58 | console.log('tick...', count, args) 59 | if (!(--count)) { 60 | clearInterval(interval) 61 | console.log('done') 62 | } 63 | }, 100, 1, 2, 3) 64 | console.log('interval', interval) 65 | 66 | console.log('Shrinking interval') 67 | let shrink = new Timer() 68 | shrink.start(0, 400) 69 | shrink.onTimeout = () => { 70 | idle.again() 71 | console.log('Shrink', shrink.repeat) 72 | shrink.repeat >>>= 1 73 | if (!shrink.repeat) shrink.close() 74 | } 75 | idle.again() 76 | -------------------------------------------------------------------------------- /libs/codec-tools.js: -------------------------------------------------------------------------------- 1 | // read / write - stream prmitives 2 | // await read() -> value 3 | // await read() -> undefined 4 | // await write(value) 5 | // await write() 6 | 7 | // encode / decode - low-level codecs 8 | // decode(input, offset) -> [value, offset] 9 | // decode(input, offset) -> undefined 10 | // encode(value) -> encoded 11 | // encode() -> encoded? 12 | 13 | // encoder / decode - higher-level codecs 14 | // encoder(write) -> newWrite (with updateEncode and updateWrite) 15 | // decoder(read) -> newRead (with updateDecode and updateRead) 16 | 17 | // makeEncoder(encode) -> encoder (with updateEncode) 18 | exports.makeEncoder = encode => { 19 | const encoder = inner => { 20 | const write = async value => write.inner(write.encode(value)) 21 | write.encode = encoder.encode 22 | write.inner = inner 23 | return write 24 | } 25 | encoder.encode = encode 26 | return encoder 27 | } 28 | 29 | // makeDecoder(decode) -> decoder (with updateDecode) 30 | exports.makeDecoder = decode => { 31 | const decoder = inner => { 32 | let buffer = null 33 | let offset = null 34 | let done = false 35 | const read = async () => { 36 | while (true) { 37 | let result = read.decode(buffer, offset) 38 | if (result) { 39 | offset = result[1] 40 | if (buffer && offset >= buffer.length) { 41 | buffer = null 42 | offset = null 43 | } 44 | return result[0] 45 | } 46 | if (done) return 47 | const next = await read.inner() 48 | if (!next) { 49 | done = true 50 | continue 51 | } 52 | if (!buffer) { 53 | buffer = next 54 | offset = 0 55 | continue 56 | } 57 | const bytesLeft = buffer.length - offset 58 | const last = buffer 59 | buffer = Buffer.alloc(bytesLeft + next.length) 60 | last.copy(buffer, 0, offset) 61 | next.copy(buffer, bytesLeft) 62 | offset = 0 63 | } 64 | } 65 | read.decode = decoder.decode 66 | read.inner = inner 67 | return read 68 | } 69 | decoder.decode = decode 70 | return decoder 71 | } 72 | -------------------------------------------------------------------------------- /tests/test-tcp.js: -------------------------------------------------------------------------------- 1 | const { 2 | /* eslint camelcase: 0 */ 3 | sizeof_uv_tcp_t, 4 | sizeof_uv_connect_t, 5 | sizeof_uv_shutdown_t, 6 | sizeof_uv_write_t, 7 | // TCP Functions 8 | nuv_tcp_init, 9 | nuv_tcp_nodelay, 10 | nuv_tcp_keepalive, 11 | nuv_tcp_simultaneous_accepts, 12 | nuv_tcp_bind, 13 | nuv_tcp_getsockname, 14 | nuv_tcp_getpeername, 15 | nuv_tcp_connect, 16 | // Stream Functions 17 | nuv_shutdown, 18 | nuv_listen, 19 | nuv_accept, 20 | nuv_read_start, 21 | nuv_read_stop, 22 | nuv_write, 23 | // Handle Functions 24 | nuv_close 25 | } = require('bindings')('nuv.node') 26 | 27 | let server = {} 28 | nuv_tcp_init(Buffer.alloc(sizeof_uv_tcp_t), server) 29 | 30 | nuv_tcp_simultaneous_accepts(server.handle, true) 31 | nuv_tcp_bind(server.handle, '0.0.0.0', 0) 32 | let addr = nuv_tcp_getsockname(server.handle) 33 | console.log('server-sock', addr) 34 | 35 | server.onConnection = err => { 36 | console.log('on_connection', {err}) 37 | let socket = {} 38 | nuv_tcp_init(Buffer.alloc(sizeof_uv_tcp_t), socket) 39 | nuv_tcp_nodelay(socket.handle, true) 40 | nuv_tcp_keepalive(socket.handle, true, 300) 41 | nuv_accept(server.handle, socket.handle) 42 | console.log('client-sock', nuv_tcp_getsockname(socket.handle)) 43 | console.log('client-peer', nuv_tcp_getpeername(socket.handle)) 44 | socket.readBuffer = Buffer.alloc(1024) 45 | socket.onRead = (nread) => { 46 | console.log('socket.onRead', {nread}) 47 | if (!nread) { 48 | nuv_read_stop(socket.handle) 49 | socket.onClose = () => { 50 | console.log('socket.onClose') 51 | } 52 | nuv_close(socket.handle) 53 | } 54 | } 55 | nuv_read_start(socket.handle) 56 | } 57 | nuv_listen(server.handle, 128) 58 | 59 | let client = {} 60 | nuv_tcp_init(Buffer.alloc(sizeof_uv_tcp_t), client) 61 | client.onConnect = err => { 62 | delete client.onConnect 63 | console.log('client.onConnect', {err}) 64 | nuv_write(Buffer.alloc(sizeof_uv_write_t), client.handle, Buffer.from('Hello World\n')) 65 | } 66 | client.onWrite = err => { 67 | console.log('client.onWrite', {err}) 68 | nuv_shutdown(Buffer.alloc(sizeof_uv_shutdown_t), client.handle) 69 | } 70 | client.onShutdown = err => { 71 | delete client.onShutdown 72 | delete client.onWrite 73 | console.log('client.onShutdown', {err}) 74 | nuv_close(client.handle) 75 | } 76 | client.onClose = () => { 77 | delete client.onClose 78 | delete client.handle 79 | console.log('client.onClose') 80 | nuv_close(server.handle) 81 | } 82 | 83 | nuv_tcp_connect(Buffer.alloc(sizeof_uv_connect_t), client.handle, '127.0.0.1', addr.port) 84 | -------------------------------------------------------------------------------- /src/nuv.js: -------------------------------------------------------------------------------- 1 | const bindings = require('bindings')('nuv.node') 2 | 3 | const { 4 | /* eslint camelcase: 0 */ 5 | sizeof_uv_tcp_t, 6 | sizeof_uv_udp_t, 7 | sizeof_uv_connect_t, 8 | sizeof_uv_shutdown_t, 9 | sizeof_uv_write_t, 10 | sizeof_uv_udp_send_t, 11 | sizeof_uv_timer_t, 12 | // TCP Functions 13 | nuv_tcp_init, 14 | nuv_tcp_nodelay, 15 | nuv_tcp_keepalive, 16 | nuv_tcp_simultaneous_accepts, 17 | nuv_tcp_bind, 18 | nuv_tcp_getsockname, 19 | nuv_tcp_getpeername, 20 | nuv_tcp_connect, 21 | // Stream Functions 22 | nuv_shutdown, 23 | nuv_listen, 24 | nuv_accept, 25 | nuv_read_start, 26 | nuv_read_stop, 27 | nuv_write, 28 | // UDP Functions 29 | nuv_udp_init, 30 | nuv_udp_bind, 31 | nuv_udp_getsockname, 32 | nuv_udp_send, 33 | nuv_udp_recv_start, 34 | nuv_udp_recv_stop, 35 | // Timer Functions 36 | nuv_timer_init, 37 | nuv_timer_start, 38 | nuv_timer_stop, 39 | nuv_timer_again, 40 | nuv_timer_set_repeat, 41 | nuv_timer_get_repeat, 42 | 43 | // Handle Functions 44 | nuv_close 45 | } = bindings 46 | 47 | class Handle { 48 | wait (name, self) { 49 | return new Promise((resolve, reject) => { 50 | this[name] = (err, result) => { 51 | delete this[name] 52 | return err ? reject(err) : resolve(self || result) 53 | } 54 | }) 55 | } 56 | 57 | close () { 58 | nuv_close(this.handle) 59 | return this.wait('onClose') 60 | } 61 | } 62 | 63 | class Tcp extends Handle { 64 | constructor (handlers) { 65 | super() 66 | for (const key in handlers) { 67 | this[key] = handlers[key] 68 | } 69 | nuv_tcp_init(Buffer.alloc(sizeof_uv_tcp_t), this) 70 | } 71 | 72 | get sockname () { 73 | return nuv_tcp_getsockname(this.handle) 74 | } 75 | 76 | get peername () { 77 | return nuv_tcp_getpeername(this.handle) 78 | } 79 | } 80 | 81 | class Server extends Tcp { 82 | bind (ip, port) { 83 | nuv_tcp_bind(this.handle, ip, port) 84 | return this 85 | } 86 | 87 | listen (backlog) { 88 | nuv_listen(this.handle, backlog) 89 | return this 90 | } 91 | 92 | accept (options) { 93 | const socket = new Socket(options) 94 | nuv_accept(this.handle, socket.handle) 95 | return socket 96 | } 97 | 98 | set simultaneousAccepts (enable) { 99 | nuv_tcp_simultaneous_accepts(this.handle, enable) 100 | } 101 | } 102 | 103 | class Socket extends Tcp { 104 | constructor () { 105 | super() 106 | if (!this.readBuffer) this.readBuffer = Buffer.alloc(0x10000) 107 | this.queue = [] 108 | this.reader = 0 109 | this.writer = 0 110 | this.paused = true 111 | } 112 | 113 | write (data, ...args) { 114 | if (!data) { 115 | nuv_shutdown(Buffer.alloc(sizeof_uv_shutdown_t), this.handle) 116 | return this.wait('onShutdown') 117 | } 118 | if (!Buffer.isBuffer(data)) data = Buffer.from(data, ...args) 119 | nuv_write(Buffer.alloc(sizeof_uv_write_t), this.handle, data) 120 | return this.wait('onWrite') 121 | } 122 | 123 | onRead (err, nread) { 124 | if (this.reader > this.writer) { 125 | const { resolve, reject } = this.queue[this.writer] 126 | delete this.queue[this.writer++] 127 | return err ? reject(err) : resolve(this.readBuffer.slice(0, nread)) 128 | } 129 | if (!this.paused) { 130 | this.paused = true 131 | nuv_read_stop(this.handle) 132 | } 133 | this.queue[this.writer++] = { err, nread } 134 | } 135 | 136 | read () { 137 | return new Promise((resolve, reject) => { 138 | if (this.writer > this.reader) { 139 | const { err, nread } = this.queue[this.reader] 140 | delete this.queue[this.reader++] 141 | return err ? reject(err) : resolve(this.readBuffer.slice(0, nread)) 142 | } 143 | if (this.paused) { 144 | this.paused = false 145 | nuv_read_start(this.handle) 146 | } 147 | this.queue[this.reader++] = { resolve, reject } 148 | }) 149 | } 150 | 151 | set keepalive (delay) { 152 | nuv_tcp_keepalive(this.handle, !!delay, delay) 153 | } 154 | 155 | set nodelay (enable) { 156 | nuv_tcp_nodelay(this.handle, enable) 157 | } 158 | 159 | [Symbol.asyncIterator] () { 160 | return { 161 | next: () => new Promise((resolve, reject) => { 162 | this.read().then( 163 | value => resolve({ value }), 164 | err => err.code === 'EOF' ? resolve({ done: true }) : reject(err) 165 | ) 166 | }) 167 | } 168 | } 169 | } 170 | 171 | class Client extends Socket { 172 | connect (ip, port) { 173 | nuv_tcp_connect(Buffer.alloc(sizeof_uv_connect_t), this.handle, ip, port) 174 | return this.wait('onConnect', this) 175 | } 176 | } 177 | 178 | class Udp extends Handle { 179 | constructor (handlers) { 180 | super() 181 | for (const key in handlers) { 182 | this[key] = handlers[key] 183 | } 184 | nuv_udp_init(Buffer.alloc(sizeof_uv_udp_t), this) 185 | if (!this.readBuffer) this.readBuffer = Buffer.alloc(0x10000) 186 | this.queue = [] 187 | this.reader = 0 188 | this.writer = 0 189 | this.paused = true 190 | } 191 | 192 | get sockname () { 193 | return nuv_udp_getsockname(this.handle) 194 | } 195 | 196 | bind (ip, port) { 197 | nuv_udp_bind(this.handle, ip, port) 198 | return this 199 | } 200 | 201 | send (ip, port, data, ...args) { 202 | if (!Buffer.isBuffer(data)) data = Buffer.from(data, ...args) 203 | nuv_udp_send(Buffer.alloc(sizeof_uv_udp_send_t), this.handle, data, ip, port) 204 | return this.wait('onSend') 205 | } 206 | 207 | onRecv (err, nread, addr) { 208 | if (this.reader > this.writer) { 209 | const { resolve, reject } = this.queue[this.writer] 210 | delete this.queue[this.writer++] 211 | return err ? reject(err) : resolve({ 212 | data: this.readBuffer.slice(0, nread), 213 | ip: addr.ip, 214 | port: addr.port 215 | }) 216 | } 217 | if (!this.paused) { 218 | this.paused = true 219 | nuv_udp_recv_stop(this.handle) 220 | } 221 | this.queue[this.writer++] = { err, nread, addr } 222 | } 223 | 224 | recv () { 225 | return new Promise((resolve, reject) => { 226 | if (this.writer > this.reader) { 227 | const { err, nread, addr } = this.queue[this.reader] 228 | delete this.queue[this.reader++] 229 | return err ? reject(err) : resolve({ 230 | data: this.readBuffer.slice(0, nread), 231 | ip: addr.ip, 232 | port: addr.port 233 | }) 234 | } 235 | if (this.paused) { 236 | this.paused = false 237 | nuv_udp_recv_start(this.handle) 238 | } 239 | this.queue[this.reader++] = { resolve, reject } 240 | }) 241 | } 242 | 243 | [Symbol.asyncIterator] () { 244 | return { 245 | next: () => new Promise((resolve, reject) => { 246 | this.recv().then( 247 | value => resolve({ value }), 248 | err => reject(err) 249 | ) 250 | }) 251 | } 252 | } 253 | } 254 | 255 | class Timer extends Handle { 256 | constructor (handlers) { 257 | super() 258 | for (const key in handlers) { 259 | this[key] = handlers[key] 260 | } 261 | nuv_timer_init(Buffer.alloc(sizeof_uv_timer_t), this) 262 | } 263 | 264 | start (timeout = 0, repeat = 0) { 265 | nuv_timer_start(this.handle, timeout, repeat) 266 | } 267 | 268 | stop () { 269 | nuv_timer_stop(this.handle) 270 | } 271 | 272 | again () { 273 | nuv_timer_again(this.handle) 274 | } 275 | 276 | get repeat () { 277 | return nuv_timer_get_repeat(this.handle) 278 | } 279 | 280 | set repeat (newRepeat) { 281 | nuv_timer_set_repeat(this.handle, newRepeat) 282 | } 283 | } 284 | 285 | exports.bindings = bindings 286 | exports.Server = Server 287 | exports.Client = Client 288 | exports.Udp = Udp 289 | exports.Timer = Timer 290 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 9, 4 | "ecmaFeatures": { 5 | "jsx": true 6 | }, 7 | "sourceType": "module" 8 | }, 9 | 10 | "env": { 11 | "es6": true, 12 | "node": true 13 | }, 14 | 15 | "plugins": [ 16 | "import", 17 | "node", 18 | "promise", 19 | "standard" 20 | ], 21 | 22 | "globals": { 23 | "document": false, 24 | "navigator": false, 25 | "window": false 26 | }, 27 | 28 | "rules": { 29 | "accessor-pairs": "error", 30 | "arrow-spacing": ["error", { "before": true, "after": true }], 31 | "block-spacing": ["error", "always"], 32 | "brace-style": ["error", "1tbs", { "allowSingleLine": true }], 33 | "camelcase": ["error", { "properties": "never" }], 34 | "comma-dangle": ["error", { 35 | "arrays": "never", 36 | "objects": "never", 37 | "imports": "never", 38 | "exports": "never", 39 | "functions": "never" 40 | }], 41 | "comma-spacing": ["error", { "before": false, "after": true }], 42 | "comma-style": ["error", "last"], 43 | "constructor-super": "error", 44 | "curly": ["error", "multi-line"], 45 | "dot-location": ["error", "property"], 46 | "eol-last": "error", 47 | "eqeqeq": ["error", "always", { "null": "ignore" }], 48 | "func-call-spacing": ["error", "never"], 49 | "generator-star-spacing": ["error", { "before": true, "after": true }], 50 | "handle-callback-err": ["error", "^(err|error)$" ], 51 | "indent": ["error", 2, { 52 | "SwitchCase": 1, 53 | "VariableDeclarator": 1, 54 | "outerIIFEBody": 1, 55 | "MemberExpression": 1, 56 | "FunctionDeclaration": { "parameters": 1, "body": 1 }, 57 | "FunctionExpression": { "parameters": 1, "body": 1 }, 58 | "CallExpression": { "arguments": 1 }, 59 | "ArrayExpression": 1, 60 | "ObjectExpression": 1, 61 | "ImportDeclaration": 1, 62 | "flatTernaryExpressions": false, 63 | "ignoreComments": false 64 | }], 65 | "key-spacing": ["error", { "beforeColon": false, "afterColon": true }], 66 | "keyword-spacing": ["error", { "before": true, "after": true }], 67 | "new-cap": ["error", { "newIsCap": true, "capIsNew": false }], 68 | "new-parens": "error", 69 | "no-array-constructor": "error", 70 | "no-caller": "error", 71 | "no-class-assign": "error", 72 | "no-compare-neg-zero": "error", 73 | "no-cond-assign": "error", 74 | "no-const-assign": "error", 75 | "no-constant-condition": ["error", { "checkLoops": false }], 76 | "no-control-regex": "error", 77 | "no-debugger": "error", 78 | "no-delete-var": "error", 79 | "no-dupe-args": "error", 80 | "no-dupe-class-members": "error", 81 | "no-dupe-keys": "error", 82 | "no-duplicate-case": "error", 83 | "no-empty-character-class": "error", 84 | "no-empty-pattern": "error", 85 | "no-eval": "error", 86 | "no-ex-assign": "error", 87 | "no-extend-native": "error", 88 | "no-extra-bind": "error", 89 | "no-extra-boolean-cast": "error", 90 | "no-extra-parens": ["error", "functions"], 91 | "no-fallthrough": "error", 92 | "no-floating-decimal": "error", 93 | "no-func-assign": "error", 94 | "no-global-assign": "error", 95 | "no-implied-eval": "error", 96 | "no-inner-declarations": ["error", "functions"], 97 | "no-invalid-regexp": "error", 98 | "no-irregular-whitespace": "error", 99 | "no-iterator": "error", 100 | "no-label-var": "error", 101 | "no-labels": ["error", { "allowLoop": false, "allowSwitch": false }], 102 | "no-lone-blocks": "error", 103 | "no-mixed-operators": ["error", { 104 | "groups": [ 105 | ["==", "!=", "===", "!==", ">", ">=", "<", "<="], 106 | ["&&", "||"], 107 | ["in", "instanceof"] 108 | ], 109 | "allowSamePrecedence": true 110 | }], 111 | "no-mixed-spaces-and-tabs": "error", 112 | "no-multi-spaces": "error", 113 | "no-multi-str": "error", 114 | "no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 0 }], 115 | "no-negated-in-lhs": "error", 116 | "no-new": "error", 117 | "no-new-func": "error", 118 | "no-new-object": "error", 119 | "no-new-require": "error", 120 | "no-new-symbol": "error", 121 | "no-new-wrappers": "error", 122 | "no-obj-calls": "error", 123 | "no-octal": "error", 124 | "no-octal-escape": "error", 125 | "no-path-concat": "error", 126 | "no-proto": "error", 127 | "no-redeclare": "error", 128 | "no-regex-spaces": "error", 129 | "no-return-assign": ["error", "except-parens"], 130 | "no-return-await": "error", 131 | "no-self-assign": "error", 132 | "no-self-compare": "error", 133 | "no-sequences": "error", 134 | "no-shadow-restricted-names": "error", 135 | "no-sparse-arrays": "error", 136 | "no-tabs": "error", 137 | "no-template-curly-in-string": "error", 138 | "no-this-before-super": "error", 139 | "no-throw-literal": "error", 140 | "no-trailing-spaces": "error", 141 | "no-undef": "error", 142 | "no-undef-init": "error", 143 | "no-unexpected-multiline": "error", 144 | "no-unmodified-loop-condition": "error", 145 | "no-unneeded-ternary": ["error", { "defaultAssignment": false }], 146 | "no-unreachable": "error", 147 | "no-unsafe-finally": "error", 148 | "no-unsafe-negation": "error", 149 | "no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true, "allowTaggedTemplates": true }], 150 | "no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": true }], 151 | "no-use-before-define": ["error", { "functions": false, "classes": false, "variables": false }], 152 | "no-useless-call": "error", 153 | "no-useless-computed-key": "error", 154 | "no-useless-constructor": "error", 155 | "no-useless-escape": "error", 156 | "no-useless-rename": "error", 157 | "no-useless-return": "error", 158 | "no-whitespace-before-property": "error", 159 | "no-with": "error", 160 | "object-property-newline": ["error", { "allowMultiplePropertiesPerLine": true }], 161 | "one-var": ["error", { "initialized": "never" }], 162 | "operator-linebreak": ["error", "after", { "overrides": { "?": "before", ":": "before" } }], 163 | "padded-blocks": ["error", { "blocks": "never", "switches": "never", "classes": "never" }], 164 | "prefer-promise-reject-errors": "error", 165 | "quotes": ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": true }], 166 | "rest-spread-spacing": ["error", "never"], 167 | "semi": ["error", "never"], 168 | "semi-spacing": ["error", { "before": false, "after": true }], 169 | "space-before-blocks": ["error", "always"], 170 | "space-before-function-paren": ["error", "always"], 171 | "space-in-parens": ["error", "never"], 172 | "space-infix-ops": "error", 173 | "space-unary-ops": ["error", { "words": true, "nonwords": false }], 174 | "spaced-comment": ["error", "always", { 175 | "line": { "markers": ["*package", "!", "/", ",", "="] }, 176 | "block": { "balanced": true, "markers": ["*package", "!", ",", ":", "::", "flow-include"], "exceptions": ["*"] } 177 | }], 178 | "symbol-description": "error", 179 | "template-curly-spacing": ["error", "never"], 180 | "template-tag-spacing": ["error", "never"], 181 | "unicode-bom": ["error", "never"], 182 | "use-isnan": "error", 183 | "valid-typeof": ["error", { "requireStringLiterals": true }], 184 | "wrap-iife": ["error", "any", { "functionPrototypeMethods": true }], 185 | "yield-star-spacing": ["error", "both"], 186 | "yoda": ["error", "never"], 187 | 188 | "import/export": "error", 189 | "import/first": "error", 190 | "import/no-duplicates": "error", 191 | "import/no-webpack-loader-syntax": "error", 192 | 193 | "node/no-deprecated-api": "error", 194 | "node/process-exit-as-throw": "error", 195 | 196 | "promise/param-names": "error", 197 | 198 | "standard/array-bracket-even-spacing": ["error", "either"], 199 | "standard/computed-property-even-spacing": ["error", "even"], 200 | "standard/no-callback-literal": "error", 201 | "standard/object-curly-even-spacing": ["error", "either"] 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /libs/codec-http.js: -------------------------------------------------------------------------------- 1 | const { indexOf, binToRaw } = require('./bintools') 2 | const { assert } = require('./assert') 3 | const { makeEncoder, makeDecoder } = require('./codec-tools') 4 | 5 | module.exports = { encoder: httpEncoder, decoder: httpDecoder } 6 | 7 | const STATUS_CODES = { 8 | '100': 'Continue', 9 | '101': 'Switching Protocols', 10 | '102': 'Processing', // RFC 2518, obsoleted by RFC 4918 11 | '200': 'OK', 12 | '201': 'Created', 13 | '202': 'Accepted', 14 | '203': 'Non-Authoritative Information', 15 | '204': 'No Content', 16 | '205': 'Reset Content', 17 | '206': 'Partial Content', 18 | '207': 'Multi-Status', // RFC 4918 19 | '300': 'Multiple Choices', 20 | '301': 'Moved Permanently', 21 | '302': 'Moved Temporarily', 22 | '303': 'See Other', 23 | '304': 'Not Modified', 24 | '305': 'Use Proxy', 25 | '307': 'Temporary Redirect', 26 | '400': 'Bad Request', 27 | '401': 'Unauthorized', 28 | '402': 'Payment Required', 29 | '403': 'Forbidden', 30 | '404': 'Not Found', 31 | '405': 'Method Not Allowed', 32 | '406': 'Not Acceptable', 33 | '407': 'Proxy Authentication Required', 34 | '408': 'Request Time-out', 35 | '409': 'Conflict', 36 | '410': 'Gone', 37 | '411': 'Length Required', 38 | '412': 'Precondition Failed', 39 | '413': 'Request Entity Too Large', 40 | '414': 'Request-URI Too Large', 41 | '415': 'Unsupported Media Type', 42 | '416': 'Requested Range Not Satisfiable', 43 | '417': 'Expectation Failed', 44 | '418': "I'm a teapot", // RFC 2324 45 | '422': 'Unprocessable Entity', // RFC 4918 46 | '423': 'Locked', // RFC 4918 47 | '424': 'Failed Dependency', // RFC 4918 48 | '425': 'Unordered Collection', // RFC 4918 49 | '426': 'Upgrade Required', // RFC 2817 50 | '500': 'Internal Server Error', 51 | '501': 'Not Implemented', 52 | '502': 'Bad Gateway', 53 | '503': 'Service Unavailable', 54 | '504': 'Gateway Time-out', 55 | '505': 'HTTP Version not supported', 56 | '506': 'Variant Also Negotiates', // RFC 2295 57 | '507': 'Insufficient Storage', // RFC 4918 58 | '509': 'Bandwidth Limit Exceeded', 59 | '510': 'Not Extended' // RFC 2774 60 | } 61 | 62 | function httpEncoder (write) { 63 | const newWrite = makeEncoder(encodeHead)(write) 64 | return newWrite 65 | 66 | function encodeHead (item) { 67 | if (!item || item.constructor !== Object) { 68 | return item 69 | } else if (typeof item !== 'object') { 70 | throw new Error( 71 | 'expected an object but got a ' + (typeof item) + ' when encoding data' 72 | ) 73 | } 74 | let head, chunkedEncoding 75 | const version = item.version || 1.1 76 | if (item.method) { 77 | const path = item.path 78 | assert(path && path.length > 0, 'expected non-empty path') 79 | head = [ item.method + ' ' + item.path + ' HTTP/' + version + '\r\n' ] 80 | } else { 81 | const reason = item.reason || STATUS_CODES[item.code] 82 | head = [ 'HTTP/' + version + ' ' + item.code + ' ' + reason + '\r\n' ] 83 | } 84 | const headers = item.headers 85 | if (Array.isArray(headers)) { 86 | for (let i = 0, l = headers.length; i < l; i += 2) { 87 | processHeader(headers[i], headers[i + 1]) 88 | } 89 | } else { 90 | for (const key in headers) { 91 | processHeader(key, headers[key]) 92 | } 93 | } 94 | function processHeader (key, value) { 95 | const lowerKey = key.toLowerCase() 96 | if (lowerKey === 'transfer-encoding') { 97 | chunkedEncoding = value.toLowerCase() === 'chunked' 98 | } 99 | value = ('' + value).replace(/[\r\n]+/, ' ') 100 | head[head.length] = key + ': ' + value + '\r\n' 101 | } 102 | 103 | head[head.length] = '\r\n' 104 | 105 | newWrite.encode = chunkedEncoding ? encodeChunked : encodeRaw 106 | return head.join('') 107 | } 108 | 109 | function encodeRaw (item) { 110 | if (typeof item !== 'string') { 111 | newWrite.encode = encodeHead 112 | return encodeHead(item) 113 | } 114 | return item 115 | } 116 | 117 | function encodeChunked (item) { 118 | if (typeof item !== 'string') { 119 | newWrite.encode = encodeHead 120 | const extra = encodeHead(item) 121 | if (extra) { 122 | return '0\r\n\r\n' + extra 123 | } else { 124 | return '0\r\n\r\n' 125 | } 126 | } 127 | if (item.length === 0) { 128 | newWrite.encode = encodeHead 129 | } 130 | return item.length.toString(16) + '\r\n' + item + '\r\n' 131 | } 132 | } 133 | 134 | function httpDecoder (read) { 135 | let bytesLeft // For counted decoder 136 | const newRead = makeDecoder(decodeHead)(read) 137 | 138 | newRead[Symbol.asyncIterator] = () => ({ 139 | next: () => new Promise((resolve, reject) => 140 | newRead().then( 141 | value => resolve(isEmpty(value) ? { done: true } : { value }), 142 | err => err.code === 'EOF' ? resolve({ done: true }) : reject(err) 143 | ) 144 | ) 145 | }) 146 | 147 | return newRead 148 | 149 | // This state is for decoding the status line and headers. 150 | function decodeHead (chunk, offset) { 151 | if (!chunk || chunk.length <= offset) return 152 | 153 | // First make sure we have all the head before continuing 154 | let index = indexOf(chunk, '\r\n\r\n', offset) 155 | if (index < 0) { 156 | if ((chunk.length - offset) < 8 * 1024) return 157 | // But protect against evil clients by refusing heads over 8K long. 158 | throw new Error('entity too large') 159 | } 160 | // Remember where the head ended and the body started 161 | let bodyStart = index + 4 162 | 163 | // Parse the status/request line 164 | let head = {} 165 | 166 | index = indexOf(chunk, '\n', offset) + 1 167 | let line = binToRaw(chunk, offset, index) 168 | let match = line.match(/^HTTP\/(\d\.\d) (\d+) ([^\r\n]+)/) 169 | let version 170 | if (match) { 171 | version = match[1] 172 | head.code = parseInt(match[2], 10) 173 | head.reason = match[3] 174 | } else { 175 | match = line.match(/^([A-Z]+) ([^ ]+) HTTP\/(\d\.\d)/) 176 | if (match) { 177 | head.method = match[1] 178 | head.path = match[2] 179 | version = match[3] 180 | } else { 181 | throw new Error('expected HTTP data') 182 | } 183 | } 184 | head.version = parseFloat(version) 185 | head.keepAlive = head.version > 1.0 186 | 187 | // We need to inspect some headers to know how to parse the body. 188 | let contentLength 189 | let chunkedEncoding 190 | 191 | const headers = head.headers = [] 192 | // Parse the header lines 193 | let start = index 194 | while ((index = indexOf(chunk, '\n', index) + 1)) { 195 | line = binToRaw(chunk, start, index) 196 | if (line === '\r\n') break 197 | start = index 198 | const match = line.match(/^([^:\r\n]+): *([^\r\n]+)/) 199 | if (!match) { 200 | throw new Error('Malformed HTTP header: ' + line) 201 | } 202 | const key = match[1] 203 | const value = match[2] 204 | const lowerKey = key.toLowerCase() 205 | 206 | // Inspect a few headers and remember the values 207 | if (lowerKey === 'content-length') { 208 | contentLength = parseInt(value) 209 | } else if (lowerKey === 'transfer-encoding') { 210 | chunkedEncoding = value.toLowerCase() === 'chunked' 211 | } else if (lowerKey === 'connection') { 212 | head.keepAlive = value.toLowerCase() === 'keep-alive' 213 | } 214 | headers.push(key, value) 215 | } 216 | 217 | if (head.keepAlive 218 | ? !(chunkedEncoding || (contentLength !== undefined && contentLength > 0)) 219 | : (head.method === 'GET' || head.method === 'HEAD') 220 | ) { 221 | newRead.decode = decodeEmpty 222 | } else if (chunkedEncoding) { 223 | newRead.decode = decodeChunked 224 | } else if (contentLength !== undefined) { 225 | bytesLeft = contentLength 226 | newRead.decode = decodeCounted 227 | } else if (!head.keepAlive) { 228 | newRead.decode = decodeRaw 229 | } 230 | return [head, bodyStart] 231 | } 232 | 233 | // This is used for inserting a single empty string into the output string for known empty bodies 234 | function decodeEmpty (chunk, offset) { 235 | newRead.decode = decodeHead 236 | return [Buffer.alloc(0), offset] 237 | } 238 | 239 | function decodeRaw (chunk, offset) { 240 | if (!chunk || chunk.length >= offset) return [Buffer.alloc(0)] 241 | if (chunk.length === 0) return 242 | return [chunk.slice(offset), chunk.length] 243 | } 244 | 245 | function decodeChunked (chunk, offset) { 246 | // Make sure we have at least the length header 247 | const index = indexOf(chunk, '\r\n', offset) 248 | if (index < 0) return 249 | 250 | // And parse it 251 | const hex = binToRaw(chunk, offset, index) 252 | const length = parseInt(hex, 16) 253 | 254 | // Wait till we have the rest of the body 255 | const start = hex.length + 2 256 | const end = start + length 257 | if ((chunk.length - offset) < end + 2) return 258 | 259 | // An empty chunk means end of stream; reset state. 260 | if (length === 0) newRead.decode = decodeHead 261 | 262 | // Make sure the chunk ends in '\r\n' 263 | assert(binToRaw(chunk, end, end + 2) === '\r\n', 'Invalid chunk tail') 264 | 265 | return [chunk.slice(start, end), end + 2] 266 | } 267 | 268 | function decodeCounted (chunk, offset) { 269 | if (bytesLeft === 0) { 270 | return decodeEmpty(chunk, offset) 271 | } 272 | const length = chunk.length - offset 273 | // Make sure we have at least one byte to process 274 | if (length <= 0) return 275 | 276 | if (length >= bytesLeft) { 277 | newRead.decode = decodeEmpty 278 | } 279 | 280 | // If the entire chunk fits, pass it all through 281 | if (length <= bytesLeft) { 282 | bytesLeft -= length 283 | return [chunk.slice(offset), chunk.length] 284 | } 285 | 286 | return [chunk.slice(offset, bytesLeft), offset + bytesLeft + 1] 287 | } 288 | } 289 | 290 | function isEmpty (value) { 291 | return (value instanceof Buffer) && !value.length 292 | } 293 | -------------------------------------------------------------------------------- /libs/bintools.js: -------------------------------------------------------------------------------- 1 | 2 | // TYPES: 3 | // bin - a node Buffer containing binary data. 4 | // str - a normal unicode string. 5 | // raw - a string where each character's charCode is a byte value. (utf-8) 6 | // hex - a string holding binary data as lowercase hexadecimal. 7 | // b64 - a string holding binary data in base64 encoding. 8 | 9 | // Convert a raw string into a Binary 10 | exports.rawToBin = rawToBin 11 | function rawToBin (raw, start, end) { 12 | raw = '' + raw 13 | start = start == null ? 0 : start | 0 14 | end = end == null ? raw.length : end | 0 15 | let len = end - start 16 | let bin = Buffer.alloc(len) 17 | for (let i = 0; i < len; i++) { 18 | bin[i] = raw.charCodeAt(i + start) 19 | } 20 | return bin 21 | } 22 | 23 | exports.binToRaw = binToRaw 24 | function binToRaw (bin, start, end) { 25 | start = start == null ? 0 : start | 0 26 | end = end == null ? bin.length : end | 0 27 | let raw = '' 28 | for (let i = start || 0; i < end; i++) { 29 | raw += String.fromCharCode(bin[i]) 30 | } 31 | return raw 32 | } 33 | 34 | exports.binToHex = binToHex 35 | function binToHex (bin, start, end) { 36 | start = start == null ? 0 : start | 0 37 | end = end == null ? bin.length : end | 0 38 | let hex = '' 39 | for (let i = start; i < end; i++) { 40 | let byte = bin[i] 41 | hex += (byte < 0x10 ? '0' : '') + byte.toString(16) 42 | } 43 | return hex 44 | } 45 | 46 | exports.hexToBin = hexToBin 47 | function hexToBin (hex, start, end) { 48 | hex = '' + hex 49 | start = start == null ? 0 : start | 0 50 | end = end == null ? hex.length : end | 0 51 | let len = (end - start) >> 1 52 | let bin = Buffer.alloc(len) 53 | let offset = 0 54 | for (let i = start; i < end; i += 2) { 55 | bin[offset++] = parseInt(hex.substr(i, 2), 16) 56 | } 57 | return bin 58 | } 59 | 60 | exports.strToRaw = strToRaw 61 | function strToRaw (str) { 62 | return unescape(encodeURIComponent(str)) 63 | } 64 | 65 | exports.rawToStr = rawToStr 66 | function rawToStr (raw) { 67 | return decodeURIComponent(escape(raw)) 68 | } 69 | 70 | function getCodes () { 71 | return 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' 72 | } 73 | let map 74 | function getMap () { 75 | if (map) return map 76 | map = [] 77 | let codes = getCodes() 78 | for (let i = 0, l = codes.length; i < l; i++) { 79 | map[codes.charCodeAt(i)] = i 80 | } 81 | return map 82 | } 83 | 84 | // Loop over input 3 bytes at a time 85 | // a,b,c are 3 x 8-bit numbers 86 | // they are encoded into groups of 4 x 6-bit numbers 87 | // aaaaaa aabbbb bbbbcc cccccc 88 | // if there is no c, then pad the 4th with = 89 | // if there is also no b then pad the 3rd with = 90 | exports.binToB64 = binToB64 91 | function binToB64 (bin) { 92 | let b64 = '' 93 | let codes = getCodes() 94 | for (let i = 0, l = bin.length; i < l; i += 3) { 95 | let a = bin[i] 96 | let b = i + 1 < l ? bin[i + 1] : -1 97 | let c = i + 2 < l ? bin[i + 2] : -1 98 | b64 += 99 | // Higher 6 bits of a 100 | codes[a >> 2] + 101 | // Lower 2 bits of a + high 4 bits of b 102 | codes[((a & 3) << 4) | (b >= 0 ? b >> 4 : 0)] + 103 | // Low 4 bits of b + High 2 bits of c 104 | (b >= 0 ? codes[((b & 15) << 2) | (c >= 0 ? c >> 6 : 0)] : '=') + 105 | // Lower 6 bits of c 106 | (c >= 0 ? codes[c & 63] : '=') 107 | } 108 | return b64 109 | } 110 | 111 | // loop over input 4 characters at a time 112 | // The characters are mapped to 4 x 6-bit integers a,b,c,d 113 | // They need to be reassembled into 3 x 8-bit bytes 114 | // aaaaaabb bbbbcccc ccdddddd 115 | // if d is padding then there is no 3rd byte 116 | // if c is padding then there is no 2nd byte 117 | exports.b64ToBin = b64ToBin 118 | function b64ToBin (b64) { 119 | let map = getMap() 120 | let bytes = [] 121 | let j = 0 122 | for (let i = 0, l = b64.length; i < l; i += 4) { 123 | let a = map[b64.charCodeAt(i)] 124 | let b = map[b64.charCodeAt(i + 1)] 125 | let c = map[b64.charCodeAt(i + 2)] 126 | let d = map[b64.charCodeAt(i + 3)] 127 | 128 | // higher 6 bits are the first char 129 | // lower 2 bits are upper 2 bits of second char 130 | bytes[j] = (a << 2) | (b >> 4) 131 | 132 | // if the third char is not padding, we have a second byte 133 | if (c < 64) { 134 | // high 4 bits come from lower 4 bits in b 135 | // low 4 bits come from high 4 bits in c 136 | bytes[j + 1] = ((b & 0xf) << 4) | (c >> 2) 137 | 138 | // if the fourth char is not padding, we have a third byte 139 | if (d < 64) { 140 | // Upper 2 bits come from Lower 2 bits of c 141 | // Lower 6 bits come from d 142 | bytes[j + 2] = ((c & 3) << 6) | d 143 | } 144 | } 145 | j = j + 3 146 | } 147 | return Buffer.alloc(bytes) 148 | } 149 | 150 | exports.strToBin = strToBin 151 | function strToBin (str) { 152 | return rawToBin(strToRaw(str)) 153 | } 154 | 155 | exports.binToStr = binToStr 156 | function binToStr (bin, start, end) { 157 | return rawToStr(binToRaw(bin, start, end)) 158 | } 159 | 160 | exports.rawToHex = rawToHex 161 | function rawToHex (raw, start, end) { 162 | return binToHex(rawToBin(raw, start, end)) 163 | } 164 | 165 | exports.hexToRaw = hexToRaw 166 | function hexToRaw (hex) { 167 | return binToRaw(hexToBin(hex)) 168 | } 169 | 170 | exports.strToHex = strToHex 171 | function strToHex (str) { 172 | return binToHex(strToBin(str)) 173 | } 174 | 175 | exports.hexToStr = hexToStr 176 | function hexToStr (hex) { 177 | return binToStr(hexToBin(hex)) 178 | } 179 | 180 | exports.b64ToStr = b64ToStr 181 | function b64ToStr (b64) { 182 | return binToStr(b64ToBin(b64)) 183 | } 184 | 185 | exports.strToB64 = strToB64 186 | function strToB64 (str) { 187 | return binToB64(strToBin(str)) 188 | } 189 | 190 | exports.b64ToHex = b64ToHex 191 | function b64ToHex (b64) { 192 | return binToHex(b64ToBin(b64)) 193 | } 194 | 195 | exports.hexToB64 = hexToB64 196 | function hexToB64 (hex) { 197 | return binToB64(hexToBin(hex)) 198 | } 199 | 200 | exports.b64ToRaw = b64ToRaw 201 | function b64ToRaw (b64) { 202 | return binToRaw(b64ToBin(b64)) 203 | } 204 | 205 | exports.rawToB64 = rawToB64 206 | function rawToB64 (raw, start, end) { 207 | return binToB64(rawToBin(raw, start, end)) 208 | } 209 | 210 | // This takes nested lists of numbers, strings and array buffers and returns 211 | // a single buffer. Numbers represent single bytes, strings are raw 8-bit 212 | // strings, and buffers represent themselves. 213 | // EX: 214 | // 1 -> <01> 215 | // "Hi" -> <48 69> 216 | // [1, "Hi"] -> <01 48 69> 217 | // [[1],2,[3]] -> <01 02 03> 218 | exports.flatten = flatten 219 | function flatten (parts) { 220 | if (typeof parts === 'number') return Buffer.alloc([parts]) 221 | if (parts instanceof Buffer) return parts 222 | let buffer = Buffer.alloc(count(parts)) 223 | copy(buffer, 0, parts) 224 | return buffer 225 | } 226 | 227 | function count (value) { 228 | if (value == null) return 0 229 | if (typeof value === 'number') return 1 230 | if (typeof value === 'string') return value.length 231 | if (value instanceof Buffer) return value.length 232 | if (!Array.isArray(value)) { 233 | console.log('VALUE', value) 234 | throw new TypeError('Bad type for flatten: ' + typeof value) 235 | } 236 | let sum = 0 237 | for (let piece of value) { 238 | sum += count(piece) 239 | } 240 | return sum 241 | } 242 | 243 | function copy (buffer, offset, value) { 244 | if (value == null) return offset 245 | if (typeof value === 'number') { 246 | buffer[offset++] = value 247 | return offset 248 | } 249 | if (typeof value === 'string') { 250 | for (let i = 0, l = value.length; i < l; i++) { 251 | buffer[offset++] = value.charCodeAt(i) 252 | } 253 | return offset 254 | } 255 | if (value instanceof ArrayBuffer) { 256 | value = Buffer.alloc(value) 257 | } 258 | for (let piece of value) { 259 | offset = copy(buffer, offset, piece) 260 | } 261 | return offset 262 | } 263 | 264 | // indexOf for arrays/buffers. Raw is a string in raw encoding. 265 | // returns -1 when not found. 266 | // start and end are indexes into buffer. Default is 0 and length. 267 | exports.indexOf = indexOf 268 | function indexOf (bin, raw, start, end) { 269 | /* eslint no-labels: 0 */ 270 | start = start == null ? 0 : start | 0 271 | end = end == null ? bin.length : end | 0 272 | outer: for (let i = start || 0; i < end; i++) { 273 | for (let j = 0, l = raw.length; j < l; j++) { 274 | if (i + j >= end || bin[i + j] !== raw.charCodeAt(j)) { 275 | continue outer 276 | } 277 | } 278 | return i 279 | } 280 | return -1 281 | } 282 | 283 | exports.uint8 = uint8 284 | function uint8 (num) { 285 | return (num >>> 0) & 0xff 286 | } 287 | 288 | exports.uint16 = uint16 289 | function uint16 (num) { 290 | num = (num >>> 0) & 0xffff 291 | return [ 292 | num >> 8, 293 | num & 0xff 294 | ] 295 | } 296 | exports.uint32 = uint32 297 | function uint32 (num) { 298 | num >>>= 0 299 | return [ 300 | num >> 24, 301 | (num >> 16) & 0xff, 302 | (num >> 8) & 0xff, 303 | num & 0xff 304 | ] 305 | } 306 | 307 | exports.uint64 = uint64 308 | function uint64 (value) { 309 | if (value < 0) value += 0x10000000000000000 310 | return [ 311 | uint32(value / 0x100000000), 312 | uint32(value % 0x100000000) 313 | ] 314 | } 315 | 316 | // If the first 1 bit of the byte is 0,that character is 1 byte width and this is the byte. 317 | // If the first 2 bit of the byte is 10,that byte is not the first byte of a character 318 | // If the first 3 bit is 110,that character is 2 byte width and this is the first byte 319 | // If the first 4 bit is 1110,that character is 3 byte width and this is the first byte 320 | // If the first 5 bit is 11110,that character is 4 byte width and this is the first byte 321 | // If the first 6 bit is 111110,that character is 5 byte width and this is the first byte 322 | exports.isUTF8 = isUTF8 323 | function isUTF8 (bin) { 324 | let i = 0 325 | let l = bin.length 326 | while (i < l) { 327 | if (bin[i] < 0x80) i++ 328 | else if (bin[i] < 0xc0) return false 329 | else if (bin[i] < 0xe0) i += 2 330 | else if (bin[i] < 0xf0) i += 3 331 | else if (bin[i] < 0xf8) i += 4 332 | else if (bin[i] < 0xfc) i += 5 333 | } 334 | return i === l 335 | } 336 | 337 | exports.parseOct = parseOct 338 | function parseOct (bin, start, end) { 339 | let val = 0 340 | let sign = 1 341 | if (bin[start] === 0x2d) { 342 | start++ 343 | sign = -1 344 | } 345 | while (start < end) { 346 | val = (val << 3) + bin[start++] - 0x30 347 | } 348 | return sign * val 349 | } 350 | 351 | exports.parseDec = parseDec 352 | function parseDec (bin, start, end) { 353 | let val = 0 354 | let sign = 1 355 | if (bin[start] === 0x2d) { 356 | start++ 357 | sign = -1 358 | } 359 | while (start < end) { 360 | val = val * 10 + bin[start++] - 0x30 361 | } 362 | return sign * val 363 | } 364 | -------------------------------------------------------------------------------- /src/nuv.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct 9 | { 10 | napi_env env; // The JS environment. 11 | napi_ref this; // Event handlers, read buffer, and `this` for callbacks. 12 | } nuv_context_t; 13 | 14 | #define NUV_CALLBACK_START(handle, name) \ 15 | nuv_context_t *context = handle->data; \ 16 | napi_env env = context->env; \ 17 | napi_handle_scope scope; \ 18 | napi_open_handle_scope(env, &scope); \ 19 | napi_value this; \ 20 | napi_get_reference_value(env, context->this, &this); \ 21 | napi_value handler; \ 22 | napi_get_named_property(env, this, name, &handler); 23 | 24 | #define NUV_CALLBACK_ARGS(count) \ 25 | int argc = count; \ 26 | napi_value argv[count]; 27 | 28 | #define NUV_CALLBACK_CALL() \ 29 | napi_value result; \ 30 | napi_make_callback(env, NULL, this, handler, argc, argv, &result); 31 | 32 | #define NUV_CALLBACK_END() \ 33 | napi_close_handle_scope(env, scope); 34 | 35 | #define NAPI_BOOL(name, val) \ 36 | bool name; \ 37 | napi_get_value_bool(env, val, &name); 38 | 39 | #define NAPI_INT64(name, val) \ 40 | int64_t name; \ 41 | napi_get_value_int64(env, val, &name); 42 | 43 | static napi_status nuv_create_status(napi_env env, int status, napi_value *target) 44 | { 45 | if (status) 46 | { 47 | napi_value code; 48 | napi_value message; 49 | napi_create_string_utf8(env, uv_err_name(status), NAPI_AUTO_LENGTH, &code); 50 | napi_create_string_utf8(env, uv_strerror(status), NAPI_AUTO_LENGTH, &message); 51 | return napi_create_error(env, code, message, target); 52 | } 53 | return napi_get_undefined(env, target); 54 | } 55 | 56 | static napi_value parse_sockaddr(napi_env env, struct sockaddr_storage *address) 57 | { 58 | char ip[INET6_ADDRSTRLEN]; 59 | int port = 0; 60 | napi_value obj; 61 | napi_create_object(env, &obj); 62 | if (address->ss_family == AF_INET) 63 | { 64 | struct sockaddr_in *addrin = (struct sockaddr_in *)address; 65 | uv_inet_ntop(AF_INET, &(addrin->sin_addr), ip, INET6_ADDRSTRLEN); 66 | port = ntohs(addrin->sin_port); 67 | } 68 | else if (address->ss_family == AF_INET6) 69 | { 70 | struct sockaddr_in6 *addrin6 = (struct sockaddr_in6 *)address; 71 | uv_inet_ntop(AF_INET6, &(addrin6->sin6_addr), ip, INET6_ADDRSTRLEN); 72 | port = ntohs(addrin6->sin6_port); 73 | } 74 | napi_value port_value; 75 | napi_create_uint32(env, port, &port_value); 76 | napi_set_named_property(env, obj, "port", port_value); 77 | napi_value ip_value; 78 | napi_create_string_utf8(env, ip, NAPI_AUTO_LENGTH, &ip_value); 79 | napi_set_named_property(env, obj, "ip", ip_value); 80 | return obj; 81 | } 82 | 83 | static napi_status nuv_create_addr(napi_env env, struct sockaddr_storage *address, napi_value *res) 84 | { 85 | *res = parse_sockaddr(env, address); 86 | return napi_ok; 87 | } 88 | 89 | static void on_uv_close(uv_handle_t *handle) 90 | { 91 | // printf("on_uv_close handle=%p\n", handle); 92 | NUV_CALLBACK_START(handle, "onClose") 93 | NUV_CALLBACK_ARGS(0) 94 | NUV_CALLBACK_CALL() 95 | NUV_CALLBACK_END() 96 | 97 | // Clean up memory and release references now that the handle is closed. 98 | napi_delete_reference(env, context->this); 99 | free(handle->data); 100 | handle->data = NULL; 101 | } 102 | 103 | static void on_uv_timeout(uv_timer_t *handle) 104 | { 105 | NUV_CALLBACK_START(handle, "onTimeout") 106 | NUV_CALLBACK_ARGS(0) 107 | NUV_CALLBACK_CALL() 108 | NUV_CALLBACK_END() 109 | } 110 | 111 | static void on_uv_connection(uv_stream_t *handle, int status) 112 | { 113 | // printf("on_uv_connection handle=%p status=%d\n", handle, status); 114 | if (status) 115 | { 116 | NUV_CALLBACK_START(handle, "onError") 117 | NUV_CALLBACK_ARGS(1) 118 | nuv_create_status(env, status, &argv[0]); 119 | NUV_CALLBACK_CALL() 120 | NUV_CALLBACK_END() 121 | } 122 | else 123 | { 124 | NUV_CALLBACK_START(handle, "onConnection") 125 | NUV_CALLBACK_ARGS(0) 126 | NUV_CALLBACK_CALL() 127 | NUV_CALLBACK_END() 128 | } 129 | } 130 | 131 | static void on_uv_alloc(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) 132 | { 133 | // printf("on_uv_alloc handle=%p suggested_size=%zu buf=%p\n", handle, suggested_size, buf); 134 | NUV_CALLBACK_START(handle, "readBuffer") 135 | NAPI_BUFFER_CAST(char *, data, handler) 136 | buf->base = data; 137 | buf->len = data_len; 138 | NUV_CALLBACK_END() 139 | } 140 | 141 | static void on_uv_read(uv_stream_t *handle, ssize_t nread, const uv_buf_t *buf) 142 | { 143 | // printf("on_uv_read handle=%p nread=%zd buf=%p\n", handle, nread, buf); 144 | if (!nread) 145 | return; 146 | NUV_CALLBACK_START(handle, "onRead") 147 | NUV_CALLBACK_ARGS(2) 148 | if (nread < 0) 149 | nuv_create_status(env, nread, &argv[0]); 150 | else 151 | napi_get_undefined(env, &argv[0]); 152 | if (nread > 0) 153 | napi_create_uint32(env, nread, &argv[1]); 154 | else 155 | napi_get_undefined(env, &argv[1]); 156 | NUV_CALLBACK_CALL() 157 | NUV_CALLBACK_END() 158 | } 159 | 160 | static void on_uv_connect(uv_connect_t *req, int status) 161 | { 162 | // printf("on_uv_connect req=%p status=%d\n", req, status); 163 | uv_stream_t *handle = req->handle; 164 | NUV_CALLBACK_START(handle, "onConnect") 165 | NUV_CALLBACK_ARGS(1) 166 | nuv_create_status(env, status, &argv[0]); 167 | NUV_CALLBACK_CALL() 168 | napi_delete_reference(env, req->data); 169 | req->data = NULL; 170 | NUV_CALLBACK_END() 171 | } 172 | 173 | static void on_uv_shutdown(uv_shutdown_t *req, int status) 174 | { 175 | // printf("on_uv_shutdown req=%p status=%d\n", req, status); 176 | uv_stream_t *handle = req->handle; 177 | NUV_CALLBACK_START(handle, "onShutdown") 178 | NUV_CALLBACK_ARGS(1) 179 | nuv_create_status(env, status, &argv[0]); 180 | NUV_CALLBACK_CALL() 181 | napi_delete_reference(env, req->data); 182 | req->data = NULL; 183 | NUV_CALLBACK_END() 184 | } 185 | 186 | static void on_uv_write(uv_write_t *req, int status) 187 | { 188 | // printf("on_uv_write req=%p status=%d\n", req, status); 189 | uv_stream_t *handle = req->handle; 190 | NUV_CALLBACK_START(handle, "onWrite") 191 | NUV_CALLBACK_ARGS(1) 192 | nuv_create_status(env, status, &argv[0]); 193 | NUV_CALLBACK_CALL() 194 | napi_delete_reference(env, req->data); 195 | req->data = NULL; 196 | NUV_CALLBACK_END() 197 | } 198 | 199 | static void on_uv_send(uv_udp_send_t *req, int status) 200 | { 201 | uv_udp_t *handle = req->handle; 202 | NUV_CALLBACK_START(handle, "onSend") 203 | NUV_CALLBACK_ARGS(1) 204 | nuv_create_status(env, status, &argv[0]); 205 | NUV_CALLBACK_CALL() 206 | napi_delete_reference(env, req->data); 207 | req->data = NULL; 208 | NUV_CALLBACK_END() 209 | } 210 | 211 | static void on_uv_recv(uv_udp_t *handle, ssize_t nread, const uv_buf_t *buf, const struct sockaddr *addr, unsigned flags) 212 | { 213 | // printf("on_uv_recv handle=%p nread=%zd buf=%p addr=%p flags=%u\n", handle, nread, buf, addr, flags); 214 | if (!nread) 215 | return; 216 | NUV_CALLBACK_START(handle, "onRecv") 217 | NUV_CALLBACK_ARGS(3) 218 | if (nread < 0) 219 | nuv_create_status(env, nread, &argv[0]); 220 | else 221 | napi_get_undefined(env, &argv[0]); 222 | if (nread > 0) 223 | { 224 | napi_create_uint32(env, nread, &argv[1]); 225 | nuv_create_addr(env, (struct sockaddr_storage *)addr, &argv[2]); 226 | } 227 | else 228 | { 229 | napi_get_undefined(env, &argv[1]); 230 | napi_get_undefined(env, &argv[2]); 231 | } 232 | NUV_CALLBACK_CALL() 233 | NUV_CALLBACK_END() 234 | } 235 | 236 | // nuv_tcp_init(handle, this) 237 | NAPI_METHOD(nuv_tcp_init) 238 | { 239 | NAPI_ARGV(2) 240 | NAPI_BUFFER_CAST(uv_tcp_t *, handle, argv[0]) 241 | int NAPI_UV_THROWS(err, uv_tcp_init(uv_default_loop(), handle)); 242 | nuv_context_t *context = malloc(sizeof(*context)); 243 | handle->data = context; 244 | context->env = env; 245 | napi_create_reference(env, argv[1], 1, &(context->this)); 246 | napi_set_named_property(env, argv[1], "handle", argv[0]); 247 | return NULL; 248 | } 249 | 250 | // nuv_tcp_nodelay(handle, enable) 251 | NAPI_METHOD(nuv_tcp_nodelay) 252 | { 253 | NAPI_ARGV(2) 254 | NAPI_BUFFER_CAST(uv_tcp_t *, handle, argv[0]) 255 | NAPI_BOOL(enable, argv[1]) 256 | int NAPI_UV_THROWS(err, uv_tcp_nodelay(handle, enable)); 257 | return NULL; 258 | } 259 | 260 | // nuv_tcp_keepalive(handle, enable, delay) 261 | NAPI_METHOD(nuv_tcp_keepalive) 262 | { 263 | NAPI_ARGV(3) 264 | NAPI_BUFFER_CAST(uv_tcp_t *, handle, argv[0]) 265 | NAPI_BOOL(enable, argv[1]) 266 | NAPI_UINT32(delay, argv[2]) 267 | int NAPI_UV_THROWS(err, uv_tcp_keepalive(handle, enable, delay)); 268 | return NULL; 269 | } 270 | 271 | // nuv_tcp_simultaneous_accepts(handle, enable) 272 | NAPI_METHOD(nuv_tcp_simultaneous_accepts) 273 | { 274 | NAPI_ARGV(2) 275 | NAPI_BUFFER_CAST(uv_tcp_t *, handle, argv[0]) 276 | NAPI_BOOL(enable, argv[1]) 277 | int NAPI_UV_THROWS(err, uv_tcp_simultaneous_accepts(handle, enable)); 278 | return NULL; 279 | } 280 | 281 | // nuv_tcp_bind(handle, ip, port) 282 | NAPI_METHOD(nuv_tcp_bind) 283 | { 284 | NAPI_ARGV(3) 285 | NAPI_BUFFER_CAST(uv_tcp_t *, handle, argv[0]) 286 | NAPI_UTF8(ip, 64, argv[1]) 287 | NAPI_UINT32(port, argv[2]) 288 | 289 | struct sockaddr_in addr; 290 | int err; 291 | NAPI_UV_THROWS(err, uv_ip4_addr(ip, port, &addr)); 292 | NAPI_UV_THROWS(err, uv_tcp_bind(handle, (const struct sockaddr *)&addr, 0)); 293 | return NULL; 294 | } 295 | 296 | // nuv_tcp_getsockname(handle) -> { ip, port } 297 | NAPI_METHOD(nuv_tcp_getsockname) 298 | { 299 | NAPI_ARGV(1) 300 | NAPI_BUFFER_CAST(uv_tcp_t *, handle, argv[0]) 301 | struct sockaddr_storage name; 302 | int namelen = sizeof(name); 303 | int NAPI_UV_THROWS(err, uv_tcp_getsockname(handle, (struct sockaddr *)&name, &namelen)); 304 | return parse_sockaddr(env, &name); 305 | } 306 | 307 | // nuv_tcp_getpeername(handle) -> { ip, port } 308 | NAPI_METHOD(nuv_tcp_getpeername) 309 | { 310 | NAPI_ARGV(1) 311 | NAPI_BUFFER_CAST(uv_tcp_t *, handle, argv[0]) 312 | struct sockaddr_storage name; 313 | int namelen = sizeof(name); 314 | int NAPI_UV_THROWS(err, uv_tcp_getpeername(handle, (struct sockaddr *)&name, &namelen)); 315 | // int err = uv_tcp_getpeername(handle, (struct sockaddr*)&name, &namelen); 316 | // printf("err=%d\n", err); 317 | return parse_sockaddr(env, &name); 318 | } 319 | 320 | NAPI_METHOD(nuv_tcp_connect) 321 | { 322 | NAPI_ARGV(4) 323 | NAPI_BUFFER_CAST(uv_connect_t *, req, argv[0]) 324 | NAPI_BUFFER_CAST(uv_tcp_t *, handle, argv[1]) 325 | NAPI_UTF8(ip, 64, argv[2]) 326 | NAPI_UINT32(port, argv[3]) 327 | struct sockaddr_in addr; 328 | int err; 329 | NAPI_UV_THROWS(err, uv_ip4_addr(ip, port, &addr)); 330 | NAPI_UV_THROWS(err, uv_tcp_connect(req, handle, (const struct sockaddr *)&addr, on_uv_connect)); 331 | napi_create_reference(env, argv[0], 1, req->data); 332 | return NULL; 333 | } 334 | 335 | NAPI_METHOD(nuv_shutdown) 336 | { 337 | NAPI_ARGV(2) 338 | NAPI_BUFFER_CAST(uv_shutdown_t *, req, argv[0]) 339 | NAPI_BUFFER_CAST(uv_stream_t *, handle, argv[1]) 340 | int NAPI_UV_THROWS(err, uv_shutdown(req, handle, on_uv_shutdown)); 341 | napi_create_reference(env, argv[0], 1, req->data); 342 | return NULL; 343 | } 344 | 345 | NAPI_METHOD(nuv_listen) 346 | { 347 | NAPI_ARGV(2) 348 | NAPI_BUFFER_CAST(uv_stream_t *, handle, argv[0]) 349 | NAPI_UINT32(backlog, argv[1]) 350 | int NAPI_UV_THROWS(err, uv_listen(handle, backlog, on_uv_connection)); 351 | return NULL; 352 | } 353 | 354 | NAPI_METHOD(nuv_accept) 355 | { 356 | NAPI_ARGV(2) 357 | NAPI_BUFFER_CAST(uv_stream_t *, handle, argv[0]) 358 | NAPI_BUFFER_CAST(uv_stream_t *, client, argv[1]) 359 | int NAPI_UV_THROWS(err, uv_accept(handle, client)); 360 | return NULL; 361 | } 362 | 363 | NAPI_METHOD(nuv_read_start) 364 | { 365 | NAPI_ARGV(1) 366 | NAPI_BUFFER_CAST(uv_stream_t *, handle, argv[0]) 367 | int NAPI_UV_THROWS(err, uv_read_start(handle, on_uv_alloc, on_uv_read)); 368 | return NULL; 369 | } 370 | 371 | NAPI_METHOD(nuv_read_stop) 372 | { 373 | NAPI_ARGV(1) 374 | NAPI_BUFFER_CAST(uv_stream_t *, handle, argv[0]) 375 | int NAPI_UV_THROWS(err, uv_read_stop(handle)); 376 | return NULL; 377 | } 378 | 379 | NAPI_METHOD(nuv_write) 380 | { 381 | NAPI_ARGV(3) 382 | NAPI_BUFFER_CAST(uv_write_t *, req, argv[0]) 383 | NAPI_BUFFER_CAST(uv_stream_t *, handle, argv[1]) 384 | NAPI_BUFFER_CAST(char *, data, argv[2]) 385 | uv_buf_t buf; 386 | buf.base = data; 387 | buf.len = data_len; 388 | int NAPI_UV_THROWS(err, uv_write(req, handle, &buf, 1, on_uv_write)); 389 | napi_create_reference(env, argv[0], 1, req->data); 390 | return NULL; 391 | } 392 | 393 | NAPI_METHOD(nuv_udp_init) 394 | { 395 | NAPI_ARGV(2) 396 | NAPI_BUFFER_CAST(uv_udp_t *, handle, argv[0]) 397 | int NAPI_UV_THROWS(err, uv_udp_init(uv_default_loop(), handle)); 398 | nuv_context_t *context = malloc(sizeof(*context)); 399 | handle->data = context; 400 | context->env = env; 401 | napi_create_reference(env, argv[1], 1, &(context->this)); 402 | napi_set_named_property(env, argv[1], "handle", argv[0]); 403 | return NULL; 404 | } 405 | 406 | NAPI_METHOD(nuv_udp_bind) 407 | { 408 | NAPI_ARGV(3) 409 | NAPI_BUFFER_CAST(uv_udp_t *, handle, argv[0]) 410 | NAPI_UTF8(ip, 64, argv[1]) 411 | NAPI_UINT32(port, argv[2]) 412 | struct sockaddr_in addr; 413 | int err; 414 | NAPI_UV_THROWS(err, uv_ip4_addr(ip, port, &addr)); 415 | NAPI_UV_THROWS(err, uv_udp_bind(handle, (const struct sockaddr *)&addr, 0)); 416 | return NULL; 417 | } 418 | 419 | NAPI_METHOD(nuv_udp_getsockname) 420 | { 421 | NAPI_ARGV(1) 422 | NAPI_BUFFER_CAST(uv_udp_t *, handle, argv[0]) 423 | struct sockaddr_storage name; 424 | int namelen = sizeof(name); 425 | int NAPI_UV_THROWS(err, uv_udp_getsockname(handle, (struct sockaddr *)&name, &namelen)); 426 | return parse_sockaddr(env, &name); 427 | } 428 | 429 | NAPI_METHOD(nuv_udp_send) 430 | { 431 | NAPI_ARGV(5) 432 | NAPI_BUFFER_CAST(uv_udp_send_t *, req, argv[0]) 433 | NAPI_BUFFER_CAST(uv_udp_t *, handle, argv[1]) 434 | NAPI_BUFFER_CAST(char *, data, argv[2]) 435 | NAPI_UTF8(ip, 64, argv[3]) 436 | NAPI_UINT32(port, argv[4]) 437 | struct sockaddr_in addr; 438 | int err; 439 | NAPI_UV_THROWS(err, uv_ip4_addr(ip, port, &addr)); 440 | uv_buf_t buf; 441 | buf.base = data; 442 | buf.len = data_len; 443 | NAPI_UV_THROWS(err, uv_udp_send(req, handle, &buf, 1, (const struct sockaddr *)&addr, on_uv_send)); 444 | napi_create_reference(env, argv[0], 1, req->data); 445 | return NULL; 446 | } 447 | 448 | NAPI_METHOD(nuv_udp_recv_start) 449 | { 450 | NAPI_ARGV(1) 451 | NAPI_BUFFER_CAST(uv_udp_t *, handle, argv[0]) 452 | int NAPI_UV_THROWS(err, uv_udp_recv_start(handle, on_uv_alloc, on_uv_recv)); 453 | return NULL; 454 | } 455 | 456 | NAPI_METHOD(nuv_udp_recv_stop) 457 | { 458 | NAPI_ARGV(1) 459 | NAPI_BUFFER_CAST(uv_udp_t *, handle, argv[0]) 460 | int NAPI_UV_THROWS(err, uv_udp_recv_stop(handle)); 461 | return NULL; 462 | } 463 | 464 | NAPI_METHOD(nuv_timer_init) 465 | { 466 | NAPI_ARGV(2) 467 | NAPI_BUFFER_CAST(uv_timer_t *, handle, argv[0]) 468 | int NAPI_UV_THROWS(err, uv_timer_init(uv_default_loop(), handle)); 469 | nuv_context_t *context = malloc(sizeof(*context)); 470 | handle->data = context; 471 | context->env = env; 472 | napi_create_reference(env, argv[1], 1, &(context->this)); 473 | napi_set_named_property(env, argv[1], "handle", argv[0]); 474 | return NULL; 475 | } 476 | 477 | NAPI_METHOD(nuv_timer_start) 478 | { 479 | NAPI_ARGV(3) 480 | NAPI_BUFFER_CAST(uv_timer_t *, handle, argv[0]) 481 | NAPI_INT64(timeout, argv[1]) 482 | NAPI_INT64(repeat, argv[2]) 483 | int NAPI_UV_THROWS(err, uv_timer_start(handle, on_uv_timeout, timeout, repeat)); 484 | return NULL; 485 | } 486 | 487 | NAPI_METHOD(nuv_timer_stop) 488 | { 489 | NAPI_ARGV(1) 490 | NAPI_BUFFER_CAST(uv_timer_t *, handle, argv[0]) 491 | int NAPI_UV_THROWS(err, uv_timer_stop(handle)); 492 | return NULL; 493 | } 494 | 495 | NAPI_METHOD(nuv_timer_again) 496 | { 497 | NAPI_ARGV(1) 498 | NAPI_BUFFER_CAST(uv_timer_t *, handle, argv[0]) 499 | int NAPI_UV_THROWS(err, uv_timer_again(handle)); 500 | return NULL; 501 | } 502 | 503 | NAPI_METHOD(nuv_timer_set_repeat) 504 | { 505 | NAPI_ARGV(2) 506 | NAPI_BUFFER_CAST(uv_timer_t *, handle, argv[0]) 507 | NAPI_INT64(repeat, argv[1]) 508 | uv_timer_set_repeat(handle, repeat); 509 | return NULL; 510 | } 511 | 512 | NAPI_METHOD(nuv_timer_get_repeat) 513 | { 514 | NAPI_ARGV(1) 515 | NAPI_BUFFER_CAST(uv_timer_t *, handle, argv[0]) 516 | napi_value repeat; 517 | napi_create_int64(env, uv_timer_get_repeat(handle), &repeat); 518 | return repeat; 519 | } 520 | 521 | NAPI_METHOD(nuv_close) 522 | { 523 | NAPI_ARGV(1) 524 | NAPI_ARGV_BUFFER_CAST(uv_tcp_t *, handle, 0) 525 | uv_close((uv_handle_t *)handle, on_uv_close); 526 | return NULL; 527 | } 528 | 529 | NAPI_INIT() 530 | { 531 | NAPI_EXPORT_SIZEOF(uv_tcp_t) 532 | NAPI_EXPORT_SIZEOF(uv_connect_t) 533 | NAPI_EXPORT_SIZEOF(uv_shutdown_t) 534 | NAPI_EXPORT_SIZEOF(uv_write_t) 535 | NAPI_EXPORT_SIZEOF(uv_udp_t) 536 | NAPI_EXPORT_SIZEOF(uv_udp_send_t) 537 | NAPI_EXPORT_SIZEOF(uv_timer_t) 538 | // TCP Functions 539 | NAPI_EXPORT_FUNCTION(nuv_tcp_init) 540 | NAPI_EXPORT_FUNCTION(nuv_tcp_nodelay) 541 | NAPI_EXPORT_FUNCTION(nuv_tcp_keepalive) 542 | NAPI_EXPORT_FUNCTION(nuv_tcp_simultaneous_accepts) 543 | NAPI_EXPORT_FUNCTION(nuv_tcp_bind) 544 | NAPI_EXPORT_FUNCTION(nuv_tcp_getsockname) 545 | NAPI_EXPORT_FUNCTION(nuv_tcp_getpeername) 546 | NAPI_EXPORT_FUNCTION(nuv_tcp_connect) 547 | // Stream Functions 548 | NAPI_EXPORT_FUNCTION(nuv_shutdown) 549 | NAPI_EXPORT_FUNCTION(nuv_listen) 550 | NAPI_EXPORT_FUNCTION(nuv_accept) 551 | NAPI_EXPORT_FUNCTION(nuv_read_start) 552 | NAPI_EXPORT_FUNCTION(nuv_read_stop) 553 | NAPI_EXPORT_FUNCTION(nuv_write) 554 | // UDP Functions 555 | NAPI_EXPORT_FUNCTION(nuv_udp_init) 556 | NAPI_EXPORT_FUNCTION(nuv_udp_bind) 557 | NAPI_EXPORT_FUNCTION(nuv_udp_getsockname) 558 | NAPI_EXPORT_FUNCTION(nuv_udp_send) 559 | NAPI_EXPORT_FUNCTION(nuv_udp_recv_start) 560 | NAPI_EXPORT_FUNCTION(nuv_udp_recv_stop) 561 | // Timer Functions 562 | NAPI_EXPORT_FUNCTION(nuv_timer_init) 563 | NAPI_EXPORT_FUNCTION(nuv_timer_start) 564 | NAPI_EXPORT_FUNCTION(nuv_timer_stop) 565 | NAPI_EXPORT_FUNCTION(nuv_timer_again) 566 | NAPI_EXPORT_FUNCTION(nuv_timer_set_repeat) 567 | NAPI_EXPORT_FUNCTION(nuv_timer_get_repeat) 568 | // Handle Functions 569 | NAPI_EXPORT_FUNCTION(nuv_close) 570 | } 571 | --------------------------------------------------------------------------------