/*
* Copyright 2017-2019 Tom Swindell
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
const os = require('os')
const net = require('net')
const dgram = require('dgram')
const MessageChannel = require('./channel')
/**
* Manages connecting to remote device via IP networking, handshaking and
* MessageChannel creation.
*
* @class
* @public
*
* @param {string} [remoteAddr='192.168.53.1'] IP address of target network device
* @param {uint16} [remotePort=44444] ARSDK TCP discovery port
* @param {Object} [opts]
* @param {boolean} [opts.debug=false] Enable debugging output
*
* @example
*
* const connector = new NetworkConnector('192.168.53.1', 44444, { debug: true })
* connector.connect()
* .then(ch => { ... })
* .catch(error => { ... })
*/
function NetworkConnector (remoteAddr, remotePort, __opts) {
if (arguments.length === 1 && typeof(remoteAddr) === 'object') {
__opts = remoteAddr
remoteAddr = undefined
}
remoteAddr = remoteAddr || '192.168.53.1'
remotePort = remotePort || 44444
__opts = Object.assign({ debug: false }, __opts || {})
const host = `${remoteAddr}:${remotePort}`
const uid = `arnet://${host}`
this.uid = () => uid
/**
* Initiate connection to remote network end-point. Resolves on
* successful connection.
*
* @method NetworkConnector#connect
* @public
*
* @param {string} [controller_type='js-arsdk']
* @param {string} [controller_name=HOSTNAME]
*
* @returns {MessageChannel}
*/
const connect = async (controllerType, controllerName) => {
controllerType = controllerType || 'js-arsdk'
controllerName = controllerName || os.hostname()
if (__opts.debug) console.info(`Attempting to connect to: ${host}`)
const adv_sock = new net.Socket()
await new Promise((resolve, reject) => {
adv_sock.on('error', (error) => reject(error))
adv_sock.connect(remotePort, remoteAddr, resolve)
})
if (__opts.debug) console.info(`Connection established to: ${host}`)
if (__opts.debug) console.info('Creating UDP control channel port...')
const d2c_sock = dgram.createSocket('udp4')
await new Promise(resolve => d2c_sock.bind(resolve))
const d2c_addr = d2c_sock.address()
if (__opts.debug) console.info(`UDP control channel listening on: ${d2c_addr.address}:${d2c_addr.port}`)
if (__opts.debug) console.info('Performing handshake with:', host)
adv_sock.write(JSON.stringify({
controllerType,
controllerName,
d2c_port: d2c_addr.port,
}))
const data = await new Promise(resolve => adv_sock.once('data', resolve))
try {
const source = (data[data.length-1] === 0x00 ? data.slice(0, data.length-1) : data).toString('utf8')
const params = JSON.parse(source)
if (params.status !== 0) {
console.error("Handshake failure...", params)
throw 'HANDSHAKE_FAILURE'
}
if (__opts.debug) console.info('Handshake successful, creating device control channel.')
const channel = new MessageChannel(remoteAddr, params, d2c_sock, __opts)
adv_sock.end()
return Promise.resolve(channel)
} catch (e) {
d2c_sock.close()
adv_sock.end()
return Promise.reject(e)
}
}
// Export Public Interface
this.connect = connect.bind(this)
}
module.exports = NetworkConnector