From 0ea4912a3aecccb98e71207e5f6dd0b2f38b38f4 Mon Sep 17 00:00:00 2001 From: Andy Prock Date: Tue, 22 Dec 2015 15:53:08 -0800 Subject: [PATCH] intial server implementation --- TcpSocket.js | 102 +++++- TcpSockets.js | 73 +++-- examples/rctsockets/index.js | 104 +++---- .../ios/rctsockets.xcodeproj/project.pbxproj | 7 +- .../xcschemes/rctsockets.xcscheme | 13 +- examples/rctsockets/ios/rctsockets/Info.plist | 17 +- .../rctsockets/ios/rctsocketsTests/Info.plist | 2 +- .../ios/rctsocketsTests/rctsocketsTests.m | 2 +- examples/rctsockets/package.json | 6 +- examples/rctsockets/telnet-client.js | 292 ------------------ ios/TcpSocketClient.h | 13 +- ios/TcpSocketClient.m | 57 +++- ios/TcpSockets.h | 2 - ios/TcpSockets.m | 101 +++--- 14 files changed, 329 insertions(+), 462 deletions(-) delete mode 100644 examples/rctsockets/telnet-client.js diff --git a/TcpSocket.js b/TcpSocket.js index 47d46b8..9b6dd88 100644 --- a/TcpSocket.js +++ b/TcpSocket.js @@ -23,20 +23,36 @@ var Sockets = NativeModules.TcpSockets; var base64 = require('base64-js'); var Base64Str = require('./base64-str'); var noop = function () {}; -var instances = 0; +var usedIds = []; var STATE = { DISCONNECTED: 0, CONNECTING: 1, CONNECTED: 2 }; -module.exports = TcpSocket; +exports.Socket = TcpSocket; function TcpSocket(options) { EventEmitter.call(this); - this._id = instances++; - this._state = STATE.DISCONNECTED; + options = options || {}; + + var nativeSocket = false; + if (!options._id) { + // javascript generated sockets range from 1-1000 + this._id = Math.floor((Math.random() * 1000) + 1); + while (usedIds.indexOf(this._id) !== -1) { + this._id = Math.floor((Math.random() * 1000) + 1); + } + } else { + // native generated sockets range from 5000-6000 + // e.g. incoming server connections + this._id = options._id; + nativeSocket = true; + } + usedIds.push(this._id); + + this._state = nativeSocket ? STATE.CONNECTED : STATE.DISCONNECTED; this._connecting = false; this._hadError = false; this._host = null; @@ -63,7 +79,9 @@ function TcpSocket(options) { this.on = this.addListener.bind(this); } - Sockets.createSocket(this._id); // later + if (nativeSocket === false) { + Sockets.createSocket(this._id); + } } inherits(TcpSocket, EventEmitter); @@ -82,7 +100,7 @@ TcpSocket.prototype.connect = function(options, callback) { } if (typeof callback === 'function') { - this.once('connected', callback); + this.once('connect', callback); } var host = options.host || 'localhost'; @@ -282,3 +300,75 @@ function normalizeError (err) { return err; } } + +exports.Server = TcpServer; + +function TcpServer(options, connectionListener) { + if (!(this instanceof TcpServer)) { + return new TcpServer(options, connectionListener); + } + + EventEmitter.call(this); + + var self = this; + + this._socket = new exports.Socket(options); + this._socket.on('connect', function() { + self.emit('listening'); + }); + this._socket.on('error', function(error) { + self.emit('error', error); + self._socket.destroy(); + }); + this._socket.on('close', function() { + self.emit('close'); + }); + this._socket.on('connection', function(socketId) { + var socket = new exports.Socket({_id : socketId }); + self.emit('connection', socket); + }); + + if (typeof options === 'function') { + connectionListener = options; + options = {}; + self.on('connection', connectionListener); + } else { + options = options || {}; + + if (typeof connectionListener === 'function') { + self.on('connection', connectionListener); + } + } + + // this._connections = 0; + + // this.allowHalfOpen = options.allowHalfOpen || false; + // this.pauseOnConnect = !!options.pauseOnConnect; +} + +inherits(TcpServer, EventEmitter); + +TcpServer.prototype.listen = function(options, callback) { + var port = Number(options.port); + var hostname = options.hostname || 'localhost'; + + if (callback) { + this.on('listening', callback); + } + + Sockets.listen(this._socket._id, hostname, port); + + return this; +}; + +TcpServer.prototype.getConnections = function(callback) { + /* nop */ +}; + +TcpServer.prototype.close = function(callback) { + if (callback) { + this.on('close', callback); + } + + this._socket.end(); +}; diff --git a/TcpSockets.js b/TcpSockets.js index c04d882..0b259fa 100644 --- a/TcpSockets.js +++ b/TcpSockets.js @@ -6,7 +6,13 @@ var ipRegex = require('ip-regex'); -exports.Socket = require('./TcpSocket'); +var Socket = require('./TcpSocket').Socket; +var Server = require('./TcpSocket').Server; + +exports.createServer = function(options, connectionListener) { + return new Server(options, connectionListener); +}; + // Target API: // @@ -20,38 +26,38 @@ exports.Socket = require('./TcpSocket'); // connect(port, [host], [cb]) // connect(path, [cb]); // -exports.connect = exports.createConnection = function() { - var args = normalizeConnectArgs(arguments); - exports.Socket._debug('createConnection', args); - var s = new exports.Socket(args[0]); - return exports.Socket.prototype.connect.apply(s, args); -}; +// exports.connect = exports.createConnection = function() { +// var args = normalizeConnectArgs(arguments); +// Socket._debug('createConnection', args); +// var s = new Socket(args[0]); +// return Socket.prototype.connect.apply(s, args); +// }; +// +// // Returns an array [options] or [options, cb] +// // It is the same as the argument of Socket.prototype.connect(). +// function normalizeConnectArgs(args) { +// var options = {}; +// +// if (args[0] !== null && typeof args[0] === 'object') { +// // connect(options, [cb]) +// options = args[0]; +// }/* else if (isPipeName(args[0])) { +// // connect(path, [cb]); +// options.path = args[0]; +// }*/ else { +// // connect(port, [host], [cb]) +// options.port = args[0]; +// if (typeof args[1] === 'string') { +// options.host = args[1]; +// } +// } +// +// var cb = args[args.length - 1]; +// return typeof cb === 'function' ? [options, cb] : [options]; +// } -// Returns an array [options] or [options, cb] -// It is the same as the argument of Socket.prototype.connect(). -function normalizeConnectArgs(args) { - var options = {}; - - if (args[0] !== null && typeof args[0] === 'object') { - // connect(options, [cb]) - options = args[0]; - }/* else if (isPipeName(args[0])) { - // connect(path, [cb]); - options.path = args[0]; - }*/ else { - // connect(port, [host], [cb]) - options.port = args[0]; - if (typeof args[1] === 'string') { - options.host = args[1]; - } - } - - var cb = args[args.length - 1]; - return typeof cb === 'function' ? [options, cb] : [options]; -} - -exports.createConnection = function(options: { port: number,host: ?string, localAddress: ?string, localPort: ?number, family: ?number }, callback : ?any) : exports.Socket { - var tcpSocket = new exports.Socket(); +exports.connect = exports.createConnection = function(options: { port: number, host: ?string, localAddress: ?string, localPort: ?number, family: ?number }, callback : ?any) : Socket { + var tcpSocket = new Socket(); tcpSocket.connect(options, callback); return tcpSocket; }; @@ -73,3 +79,6 @@ exports.isIPv4 = function(input: string) : boolean { exports.isIPv6 = function(input: string) : boolean { return exports.isIP(input) === 6; }; + +exports.Socket = Socket; +exports.Server = Server; diff --git a/examples/rctsockets/index.js b/examples/rctsockets/index.js index 8119c87..4f92b6c 100644 --- a/examples/rctsockets/index.js +++ b/examples/rctsockets/index.js @@ -13,58 +13,54 @@ global.Buffer = global.Buffer || require('buffer').Buffer; var net = require('net'); function randomPort() { - return Math.random() * 60536 | 0 + 5000 // 60536-65536 + return Math.random() * 60536 | 0 + 5000; // 60536-65536 } -var a = net.createConnection({ port: randomPort() }, function(err) { - if (err) throw err +var aPort = randomPort(); - console.log('connected'); +var a = net.createServer({}, function(socket) { + console.log('server connected'); + + // socket.on('data', function (data) { + // var str = String.fromCharCode.apply(null, new Uint8Array(data)); + // console.log('a received', str); + // a.close(); + // b.end(); + // }); + + socket.on('data', function (data) { + console.log('Server Received: ' + data); + socket.write('Echo server\r\n'); + }); + + socket.on('error', function(error) { + console.log('error ' + error); + }); +}).listen({ port: aPort }); + +// a.on('listening', function() { +// console.log('listening'); +// }); +// +// a.on('error', function(error) { +// console.log('error ' + error); +// }); + +var b = net.createConnection({ port: aPort }, function(err) { + if (err) { + throw err; + } + + console.log('client connected'); + b.write('Hello, server! Love, Client.'); }); -a.on('error', function(err) { - console.log(err); +b.on('data', function(data) { + console.log('Client Received: ' + data); + b.end(); // kill client after server's response + a.close(); }); -var b = net.createConnection({ port: randomPort() }, function(err) { - if (err) throw err - - console.log('connected'); -}); - -b.on('error', function(err) { - console.log(err); -}); - - -// a.on('message', function(data, rinfo) { -// var str = String.fromCharCode.apply(null, new Uint8Array(data)); -// console.log('a received', str, rinfo) -// a.close() -// b.close() -// }) -// -// b.on('message', function(data, rinfo) { -// var str = String.fromCharCode.apply(null, new Uint8Array(data)); -// console.log('b received', str, rinfo) -// -// // echo back -// b.send(data, 0, data.length, aPort, '127.0.0.1', function(err) { -// if (err) throw err -// -// console.log('sent') -// }) -// }) -// -// b.once('listening', function() { -// var msg = toByteArray('hello') -// a.send(msg, 0, msg.length, bPort, '127.0.0.1', function(err) { -// if (err) throw err -// -// console.log('sent') -// }) -// }) - var rctsockets = React.createClass({ render: function() { return ( @@ -96,14 +92,14 @@ var styles = StyleSheet.create({ }, }); -// // only works for 8-bit chars -// function toByteArray(obj) { -// var uint = new Uint8Array(obj.length); -// for (var i = 0, l = obj.length; i < l; i++){ -// uint[i] = obj.charCodeAt(i); -// } -// -// return new Uint8Array(uint); -// } +// only works for 8-bit chars +function toByteArray(obj) { + var uint = new Uint8Array(obj.length); + for (var i = 0, l = obj.length; i < l; i++){ + uint[i] = obj.charCodeAt(i); + } + + return new Uint8Array(uint); +} AppRegistry.registerComponent('rctsockets', () => rctsockets); diff --git a/examples/rctsockets/ios/rctsockets.xcodeproj/project.pbxproj b/examples/rctsockets/ios/rctsockets.xcodeproj/project.pbxproj index 082e76c..23c8517 100644 --- a/examples/rctsockets/ios/rctsockets.xcodeproj/project.pbxproj +++ b/examples/rctsockets/ios/rctsockets.xcodeproj/project.pbxproj @@ -369,7 +369,7 @@ 83CBB9F71A601CBA00E9B192 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0610; + LastUpgradeCheck = 0720; ORGANIZATIONNAME = Facebook; TargetAttributes = { 00E356ED1AD99517003FC87E = { @@ -615,6 +615,7 @@ INFOPLIST_FILE = rctsocketsTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/rctsockets.app/rctsockets"; }; @@ -632,6 +633,7 @@ INFOPLIST_FILE = rctsocketsTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/rctsockets.app/rctsockets"; }; @@ -650,6 +652,7 @@ INFOPLIST_FILE = rctsockets/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = "-ObjC"; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = rctsockets; }; name = Debug; @@ -666,6 +669,7 @@ INFOPLIST_FILE = rctsockets/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = "-ObjC"; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = rctsockets; }; name = Release; @@ -690,6 +694,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; diff --git a/examples/rctsockets/ios/rctsockets.xcodeproj/xcshareddata/xcschemes/rctsockets.xcscheme b/examples/rctsockets/ios/rctsockets.xcodeproj/xcshareddata/xcschemes/rctsockets.xcscheme index e375dd4..fb41608 100644 --- a/examples/rctsockets/ios/rctsockets.xcodeproj/xcshareddata/xcschemes/rctsockets.xcscheme +++ b/examples/rctsockets/ios/rctsockets.xcodeproj/xcshareddata/xcschemes/rctsockets.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -62,15 +62,18 @@ ReferencedContainer = "container:rctsockets.xcodeproj"> + + @@ -86,10 +89,10 @@ diff --git a/examples/rctsockets/ios/rctsockets/Info.plist b/examples/rctsockets/ios/rctsockets/Info.plist index cddf076..682480a 100644 --- a/examples/rctsockets/ios/rctsockets/Info.plist +++ b/examples/rctsockets/ios/rctsockets/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -22,6 +22,13 @@ 1 LSRequiresIPhoneOS + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSLocationWhenInUseUsageDescription + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities @@ -36,13 +43,5 @@ UIViewControllerBasedStatusBarAppearance - NSLocationWhenInUseUsageDescription - - NSAppTransportSecurity - - - NSAllowsArbitraryLoads - - diff --git a/examples/rctsockets/ios/rctsocketsTests/Info.plist b/examples/rctsockets/ios/rctsocketsTests/Info.plist index 886825c..ba72822 100644 --- a/examples/rctsockets/ios/rctsocketsTests/Info.plist +++ b/examples/rctsockets/ios/rctsocketsTests/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/examples/rctsockets/ios/rctsocketsTests/rctsocketsTests.m b/examples/rctsockets/ios/rctsocketsTests/rctsocketsTests.m index 0436bcc..2168760 100644 --- a/examples/rctsockets/ios/rctsocketsTests/rctsocketsTests.m +++ b/examples/rctsockets/ios/rctsocketsTests/rctsocketsTests.m @@ -42,7 +42,7 @@ BOOL foundElement = NO; __block NSString *redboxError = nil; - RCTSetLogFunction(^(RCTLogLevel level, NSString *fileName, NSNumber *lineNumber, NSString *message) { + RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { if (level >= RCTLogLevelError) { redboxError = message; } diff --git a/examples/rctsockets/package.json b/examples/rctsockets/package.json index 1575d7c..19d26be 100644 --- a/examples/rctsockets/package.json +++ b/examples/rctsockets/package.json @@ -11,8 +11,12 @@ "dependencies": { "buffer": "^3.5.3", "events": "^1.1.0", - "react-native": "^0.15.0", + "react-native": "^0.17.0", "react-native-tcp": "../../", "util": "^0.10.3" + }, + "devDependencies": { + "babel-eslint": "^4.1.6", + "eslint-plugin-react": "^3.11.3" } } diff --git a/examples/rctsockets/telnet-client.js b/examples/rctsockets/telnet-client.js deleted file mode 100644 index c539a03..0000000 --- a/examples/rctsockets/telnet-client.js +++ /dev/null @@ -1,292 +0,0 @@ -// https://www.npmjs.com/package/another-telnet-client -(function () { - "use strict"; - - var util = require('util'); - var events = require('events'); - var net = require('net'); - - - // define a constructor (object) and inherit EventEmitter functions - function Telnet() { - events.EventEmitter.call(this); - if (false === (this instanceof Telnet)) { - return new Telnet(); - } - } - - util.inherits(Telnet, events.EventEmitter); - - Telnet.prototype.connect = function (opts) { - var self = this; - var host = (typeof opts.host !== 'undefined' ? opts.host : '127.0.0.1'); - var port = (typeof opts.port !== 'undefined' ? opts.port : 23); - this.timeout = (typeof opts.timeout !== 'undefined' ? opts.timeout : 500); - this.shellPrompt = (typeof opts.shellPrompt !== 'undefined' ? opts.shellPrompt : /(?:\/ )?#\s/); - this.loginPrompt = (typeof opts.loginPrompt !== 'undefined' ? opts.loginPrompt : /login[: ]*$/i); - this.passwordPrompt = (typeof opts.passwordPrompt !== 'undefined' ? opts.passwordPrompt : /Password: /i); - this.username = (typeof opts.username !== 'undefined' ? opts.username : 'root'); - this.password = (typeof opts.password !== 'undefined' ? opts.password : 'guest'); - this.enable = (typeof opts.enable !== 'undefined' ? opts.enable : false); - this.enablePrompt = (typeof opts.enablePrompt !== 'undefined' ? opts.enablePrompt : /Password: /i); - this.enablePassword = (typeof opts.enablePassword !== 'undefined' ? opts.enablePassword : 'enablepass'); - this.irs = (typeof opts.irs !== 'undefined' ? opts.irs : '\r\n'); - this.ors = (typeof opts.ors !== 'undefined' ? opts.ors : '\n'); - this.echoLines = (typeof opts.echoLines !== 'undefined' ? opts.echoLines : 1); - this.pageSeparator = (typeof opts.pageSeparator !== 'undefined' ? opts.pageSeparator : '---- More'); - this.ignoreOutput = (typeof opts.ignoreOutput !== 'undefined' ? opts.ignoreOutput : false); - this.ignoreOutputTimeout = (typeof opts.ignoreOutputTimeout !== 'undefined' ? opts.ignoreOutputTimeout : 1000); - this.response = ''; - this.telnetState = null; - - this.telnetSocket = net.createConnection({ - port: port, - host: host - }, function () { - self.telnetState = 'start'; - self.stringData = ''; - self.emit('connect'); - }); - - this.telnetSocket.setTimeout(this.timeout, function () { - if (self.telnetSocket._connecting === true) { - // info: cannot connect; emit error and destroy - self.emit('error', 'Cannot connect'); - - self.telnetSocket.destroy(); - } - else { - self.emit('timeout'); - } - }); - - this.telnetSocket.on('data', function (data) { - parseData(data, self); - }); - - this.telnetSocket.on('error', function (error) { - self.emit('error', error); - }); - - this.telnetSocket.on('end', function () { - self.emit('end'); - }); - - this.telnetSocket.on('close', function () { - self.emit('close'); - }); - }; - - Telnet.prototype.exec = function (cmd, opts, callback) { - var self = this; - cmd += this.ors; - - if (opts && opts instanceof Function) { - callback = opts; - } - else if (opts && opts instanceof Object) { - self.shellPrompt = opts.shellPrompt || self.shellPrompt; - self.loginPrompt = opts.loginPrompt || self.loginPrompt; - self.timeout = opts.timeout || self.timeout; - self.irs = opts.irs || self.irs; - self.ors = opts.ors || self.ors; - self.echoLines = opts.echoLines || self.echoLines; - self.ignoreOutput = opts.ignoreOutput || self.ignoreOutput; - self.ignoreOutputTimeout = opts.ignoreOutputTimeout || self.ignoreOutputTimeout; - } - - if (this.telnetSocket.writable) { - this.telnetSocket.write(cmd, function () { - self.telnetState = 'response'; - self.emit('writedone'); - - if (self.ignoreOutput === true) { - setTimeout(function () { - self.ignoreOutput = false; - callback(null); - }, self.ignoreOutputTimeout); - } else { - self.once('responseready', function () { - if (callback && self.cmdOutput !== undefined) { - callback(self.cmdOutput.join('\n')); - } - else if (callback && self.cmdOutput === undefined) { - callback(null); - } - - // reset stored response - self.stringData = ''; - }); - } - - }); - } else { - callback(new Error("Socket not writable")); - } - }; - - Telnet.prototype.end = function () { - this.telnetSocket.end(); - }; - - Telnet.prototype.destroy = function () { - this.telnetSocket.destroy(); - }; - - function parseData(chunk, telnetObj) { - var promptIndex = ''; - var tempStringData = ''; - - if (chunk[0] === 255 && chunk[1] !== 255) { - telnetObj.stringData = ''; - var negReturn = negotiate(telnetObj, chunk); - - if (negReturn === undefined) { - return; - } - else { - chunk = negReturn; - } - } - - if (telnetObj.ignoreOutput === true) { - telnetObj.stringData = ''; - return; - } - - if (telnetObj.telnetState === 'start') { - telnetObj.telnetState = 'getprompt'; - } - - if (telnetObj.telnetState === 'getprompt') { - tempStringData = chunk.toString(); - promptIndex = tempStringData.search(telnetObj.shellPrompt); - - if (promptIndex !== -1) { - telnetObj.shellPrompt = tempStringData.substring(promptIndex); - telnetObj.telnetState = 'sendcmd'; - telnetObj.stringData = ''; - telnetObj.emit('ready', telnetObj.shellPrompt); - } - else if (tempStringData.search(telnetObj.loginPrompt) !== -1) { - telnetObj.telnetState = 'login'; - login(telnetObj, 'username'); - } - else if (tempStringData.search(telnetObj.passwordPrompt) !== -1) { - telnetObj.telnetState = 'login'; - login(telnetObj, 'password'); - } - } - else if (telnetObj.telnetState === 'enable') { - tempStringData = chunk.toString(); - - if (tempStringData.search(telnetObj.enablePrompt) !== -1) { - telnetObj.telnetState = 'login'; - login(telnetObj, 'enablePassword'); - } - } - else if (telnetObj.telnetState === 'getenprompt') { - tempStringData = chunk.toString(); - - if (tempStringData.search(telnetObj.shellPrompt) !== -1) { - telnetObj.telnetState = 'login'; - login(telnetObj, 'enable'); - } - } - else if (telnetObj.telnetState === 'response') { - tempStringData = chunk.toString(); - telnetObj.stringData += tempStringData; - promptIndex = tempStringData.search(telnetObj.shellPrompt); - - if (promptIndex === -1 && tempStringData.length !== 0) { - if (tempStringData.search(telnetObj.pageSeparator) !== -1) { - telnetObj.telnetSocket.write(Buffer('20', 'hex')); - } - - return; - } - - telnetObj.cmdOutput = telnetObj.stringData.split(telnetObj.irs); - - for (var i = 0; i < telnetObj.cmdOutput.length; i++) { - if (telnetObj.cmdOutput[i].search(telnetObj.pageSeparator) !== -1) { - telnetObj.cmdOutput.splice(i, 1); - } - } - - if (telnetObj.echoLines === 1) { - telnetObj.cmdOutput.shift(); - } - else if (telnetObj.echoLines > 1) { - telnetObj.cmdOutput.splice(0, telnetObj.echoLines); - } - - // remove prompt - telnetObj.cmdOutput.pop(); - - telnetObj.emit('responseready'); - } - } - - function login(telnetObj, handle) { - if (handle === 'username') { - if (telnetObj.telnetSocket.writable) { - telnetObj.telnetSocket.write(telnetObj.username + telnetObj.ors, function () { - telnetObj.telnetState = 'getprompt'; - }); - } - } - else if (handle === 'password') { - if (telnetObj.telnetSocket.writable) { - telnetObj.telnetSocket.write(telnetObj.password + telnetObj.ors, function () { - if (telnetObj.enable) { - telnetObj.telnetState = 'getenprompt'; - } else { - telnetObj.telnetState = 'getprompt'; - } - }); - } - } - else if (handle === 'enable') { - if (telnetObj.telnetSocket.writable) { - telnetObj.telnetSocket.write("en" + telnetObj.ors, function () { - telnetObj.telnetState = 'enable'; - }); - } - } - else if (handle === 'enablePassword') { - if (telnetObj.telnetSocket.writable) { - telnetObj.telnetSocket.write(telnetObj.enablePassword + telnetObj.ors, function () { - telnetObj.telnetState = 'getprompt'; - }); - } - } - } - - function negotiate(telnetObj, chunk) { - // info: http://tools.ietf.org/html/rfc1143#section-7 - // refuse to start performing and ack the start of performance - // DO -> WONT; WILL -> DO - var packetLength = chunk.length, negData = chunk, cmdData, negResp; - - for (var i = 0; i < packetLength; i += 3) { - if (chunk[i] !== 255) { - negData = chunk.slice(0, i); - cmdData = chunk.slice(i); - break; - } - } - - negResp = negData.toString('hex').replace(/fd/g, 'fc').replace(/fb/g, 'fd'); - - if (telnetObj.telnetSocket.writable) { - telnetObj.telnetSocket.write(Buffer(negResp, 'hex')); - } - - if (cmdData !== undefined) { - return cmdData; - } - } - - module.exports = Telnet; -}()); diff --git a/ios/TcpSocketClient.h b/ios/TcpSocketClient.h index c813c5f..006de4b 100644 --- a/ios/TcpSocketClient.h +++ b/ios/TcpSocketClient.h @@ -29,16 +29,19 @@ typedef enum RCTTCPError RCTTCPError; @protocol SocketClientDelegate -- (void)onConnect:(TcpSocketClient*) client; -- (void)onData:(TcpSocketClient*) client data:(NSData *)data; -- (void)onClose:(TcpSocketClient*) client withError:(NSError *)err; -- (void)onError:(TcpSocketClient*) client withError:(NSError *)err; +- (void)onConnect:(TcpSocketClient*)client; +- (void)onConnection:(TcpSocketClient*)client toClient:(NSNumber *)clientID; +- (void)onData:(NSNumber *)clientID data:(NSData *)data; +- (void)onClose:(TcpSocketClient*)client withError:(NSError *)err; +- (void)onError:(TcpSocketClient*)client withError:(NSError *)err; +- (NSNumber*)generateRandomId; @end @interface TcpSocketClient : NSObject @property (nonatomic, retain) NSNumber * id; +@property (nonatomic, weak) id clientDelegate; ///--------------------------------------------------------------------------------------- /// @name Class Methods @@ -65,6 +68,8 @@ typedef enum RCTTCPError RCTTCPError; */ - (BOOL)connect:(NSString *)host port:(int)port withOptions:(NSDictionary *)options error:(NSError **)error; +- (BOOL)listen:(NSString *)host port:(int)port error:(NSError **)error; + /** * write data * diff --git a/ios/TcpSocketClient.m b/ios/TcpSocketClient.m index e2c4704..a44e3c1 100644 --- a/ios/TcpSocketClient.m +++ b/ios/TcpSocketClient.m @@ -18,13 +18,13 @@ NSString *const RCTTCPErrorDomain = @"RCTTCPErrorDomain"; { @private GCDAsyncSocket *_tcpSocket; - id _clientDelegate; NSMutableDictionary *_pendingSends; NSLock *_lock; long _sendTag; } -- (id)initWithClientId:(NSNumber *)clientID andConfig:(id) aDelegate; +- (id)initWithClientId:(NSNumber *)clientID andConfig:(id)aDelegate; +- (id)initWithClientId:(NSNumber *)clientID andConfig:(id)aDelegate andSocket:(GCDAsyncSocket*)tcpSocket; @end @@ -32,10 +32,15 @@ NSString *const RCTTCPErrorDomain = @"RCTTCPErrorDomain"; + (id)socketClientWithId:(nonnull NSNumber *)clientID andConfig:(id)delegate { - return [[[self class] alloc] initWithClientId:clientID andConfig:delegate]; + return [[[self class] alloc] initWithClientId:clientID andConfig:delegate andSocket:nil]; } -- (id)initWithClientId:(NSNumber *)clientID andConfig:(id) aDelegate +- (id)initWithClientId:(NSNumber *)clientID andConfig:(id)aDelegate +{ + return [self initWithClientId:clientID andConfig:aDelegate andSocket:nil]; +} + +- (id)initWithClientId:(NSNumber *)clientID andConfig:(id)aDelegate andSocket:(GCDAsyncSocket*)tcpSocket; { self = [super init]; if (self) { @@ -43,6 +48,7 @@ NSString *const RCTTCPErrorDomain = @"RCTTCPErrorDomain"; _clientDelegate = aDelegate; _pendingSends = [NSMutableDictionary dictionary]; _lock = [[NSLock alloc] init]; + _tcpSocket = tcpSocket; } return self; @@ -82,6 +88,27 @@ NSString *const RCTTCPErrorDomain = @"RCTTCPErrorDomain"; return result; } +- (BOOL)listen:(NSString *)host port:(int)port error:(NSError **)error +{ + if (_tcpSocket) { + if (error) { + *error = [self badInvocationError:@"this client's socket is already connected"]; + } + + return false; + } + + _tcpSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:[self methodQueue]]; + + BOOL result = [_tcpSocket acceptOnInterface:host port:port error:error]; + if (result == YES) { + [_clientDelegate onConnect: self]; + [_tcpSocket readDataWithTimeout:-1 tag:_id.longValue]; + } + + return result; +} + - (void)setPendingSend:(RCTResponseSenderBlock)callback forKey:(NSNumber *)key { [_lock lock]; @@ -130,12 +157,12 @@ NSString *const RCTTCPErrorDomain = @"RCTTCPErrorDomain"; { [_tcpSocket writeData:data withTimeout:-1 tag:_sendTag]; if (callback) { - [self setPendingSend:callback forKey:[NSNumber numberWithLong:_sendTag]]; + [self setPendingSend:callback forKey:@(_sendTag)]; } _sendTag++; - [_tcpSocket readDataWithTimeout:-1 tag:-1]; + [_tcpSocket readDataWithTimeout:-1 tag:_id.longValue]; } - (void)end @@ -150,9 +177,19 @@ NSString *const RCTTCPErrorDomain = @"RCTTCPErrorDomain"; - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { if (!_clientDelegate) return; - [_clientDelegate onData:self data:data]; + [_clientDelegate onData:@(tag) data:data]; - [sock readDataWithTimeout:-1 tag:-1]; + [sock readDataWithTimeout:-1 tag:tag]; +} + +- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket +{ + TcpSocketClient *inComing = [[TcpSocketClient alloc] initWithClientId:[_clientDelegate generateRandomId] + andConfig:_clientDelegate + andSocket:newSocket]; + [_clientDelegate onConnection: inComing + toClient: _id]; + [newSocket readDataWithTimeout:-1 tag:inComing.id.longValue]; } - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port @@ -160,11 +197,13 @@ NSString *const RCTTCPErrorDomain = @"RCTTCPErrorDomain"; if (!_clientDelegate) return; [_clientDelegate onConnect:self]; - [sock readDataWithTimeout:-1 tag:-1]; + [sock readDataWithTimeout:-1 tag:_id.longValue]; } - (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock { + // TODO : investigate for half-closed sockets + /* no-op */ } - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err diff --git a/ios/TcpSockets.h b/ios/TcpSockets.h index 446e5e9..9ac89c5 100644 --- a/ios/TcpSockets.h +++ b/ios/TcpSockets.h @@ -16,6 +16,4 @@ @interface TcpSockets : NSObject -+(NSMutableDictionary *)clients; - @end diff --git a/ios/TcpSockets.m b/ios/TcpSockets.m index 911dabf..2dd9a48 100644 --- a/ios/TcpSockets.m +++ b/ios/TcpSockets.m @@ -15,47 +15,38 @@ #import "TcpSocketClient.h" @implementation TcpSockets +{ + NSMutableDictionary *_clients; +} RCT_EXPORT_MODULE() @synthesize bridge = _bridge; -+ (void) initialize { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(closeAllSockets) - name:RCTReloadNotification - object:nil]; -} - -+(NSMutableDictionary *) clients +-(void)dealloc { - static NSMutableDictionary* c = nil; - - static dispatch_once_t oncePredicate; - - dispatch_once(&oncePredicate, ^{ - c = [[NSMutableDictionary alloc] init]; - }); - - return c; + for (NSNumber *cId in _clients.allKeys) { + [self destroyClient:cId callback:nil]; + } } RCT_EXPORT_METHOD(createSocket:(nonnull NSNumber*)cId) { - NSMutableDictionary *_clients = [TcpSockets clients]; if (!cId) { RCTLogError(@"%@.createSocket called with nil id parameter.", [self class]); return; } - TcpSocketClient *client = [_clients objectForKey:cId]; - if (client) { + if (!_clients) { + _clients = [NSMutableDictionary new]; + } + + if (_clients[cId]) { RCTLogError(@"%@.createSocket called twice with the same id.", [self class]); return; } - client = [TcpSocketClient socketClientWithId:cId andConfig:self]; - [_clients setObject:client forKey:cId]; + _clients[cId] = [TcpSocketClient socketClientWithId:cId andConfig:self]; } RCT_EXPORT_METHOD(connect:(nonnull NSNumber*)cId @@ -63,7 +54,7 @@ RCT_EXPORT_METHOD(connect:(nonnull NSNumber*)cId port:(int)port withOptions:(NSDictionary *)options) { - TcpSocketClient* client = [TcpSockets findClient:cId callback:nil]; + TcpSocketClient* client = [self findClient:cId callback:nil]; if (!client) return; NSError *error = nil; @@ -78,7 +69,7 @@ RCT_EXPORT_METHOD(write:(nonnull NSNumber*)cId string:(NSString *)base64String encoded:(BOOL)encoded callback:(RCTResponseSenderBlock)callback) { - TcpSocketClient* client = [TcpSockets findClient:cId callback:callback]; + TcpSocketClient* client = [self findClient:cId callback:callback]; if (!client) return; // iOS7+ @@ -89,30 +80,43 @@ RCT_EXPORT_METHOD(write:(nonnull NSNumber*)cId RCT_EXPORT_METHOD(end:(nonnull NSNumber*)cId callback:(RCTResponseSenderBlock)callback) { - [TcpSockets endClient:cId callback:callback]; + [self endClient:cId callback:callback]; } RCT_EXPORT_METHOD(destroy:(nonnull NSNumber*)cId callback:(RCTResponseSenderBlock)callback) { - [TcpSockets destroyClient:cId callback:callback]; + [self destroyClient:cId callback:callback]; } -- (void) onConnect:(TcpSocketClient*) client +RCT_EXPORT_METHOD(listen:(nonnull NSNumber*)cId + host:(NSString *)host + port:(int)port) +{ + TcpSocketClient* client = [self findClient:cId callback:nil]; + if (!client) return; + + NSError *error = nil; + if (![client listen:host port:port error:&error]) + { + [self onError:client withError:error]; + return; + } +} + +- (void)onConnect:(TcpSocketClient*) client { [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"tcp-%@-event", client.id] body:@{ @"event": @"connect" }]; } -- (void) onData:(TcpSocketClient*) client data:(NSData *)data +- (void)onData:(NSNumber *)clientID data:(NSData *)data { - NSMutableDictionary *_clients = [TcpSockets clients]; - NSNumber *clientID = [[_clients allKeysForObject:client] objectAtIndex:0]; NSString *base64String = [data base64EncodedStringWithOptions:0]; [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"tcp-%@-event", clientID] body:@{ @"event": @"data", @"data": base64String }]; } -- (void) onClose:(TcpSocketClient*) client withError:(NSError *)err +- (void)onClose:(TcpSocketClient*) client withError:(NSError *)err { if (err) { [self onError:client withError:err]; @@ -121,7 +125,7 @@ RCT_EXPORT_METHOD(destroy:(nonnull NSNumber*)cId [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"tcp-%@-event", client.id] body:@{ @"event": @"close", @"data": err == nil ? @NO : @YES }]; - NSMutableDictionary *_clients = [TcpSockets clients]; + client.clientDelegate = nil; [_clients removeObjectForKey:client.id]; } @@ -132,10 +136,9 @@ RCT_EXPORT_METHOD(destroy:(nonnull NSNumber*)cId } -+(TcpSocketClient*)findClient:(nonnull NSNumber*)cId callback:(RCTResponseSenderBlock)callback +-(TcpSocketClient*)findClient:(nonnull NSNumber*)cId callback:(RCTResponseSenderBlock)callback { - NSMutableDictionary *_clients = [TcpSockets clients]; - TcpSocketClient *client = [_clients objectForKey:cId]; + TcpSocketClient *client = _clients[cId]; if (!client) { if (!callback) { RCTLogError(@"%@.missing callback parameter.", [self class]); @@ -149,10 +152,10 @@ RCT_EXPORT_METHOD(destroy:(nonnull NSNumber*)cId return client; } -+(void) endClient:(nonnull NSNumber*)cId +-(void)endClient:(nonnull NSNumber*)cId callback:(RCTResponseSenderBlock)callback { - TcpSocketClient* client = [TcpSockets findClient:cId callback:callback]; + TcpSocketClient* client = [self findClient:cId callback:callback]; if (!client) return; [client end]; @@ -160,22 +163,30 @@ RCT_EXPORT_METHOD(destroy:(nonnull NSNumber*)cId if (callback) callback(@[]); } -+(void) destroyClient:(nonnull NSNumber*)cId +-(void)destroyClient:(nonnull NSNumber*)cId callback:(RCTResponseSenderBlock)callback { - NSMutableDictionary *_clients = [TcpSockets clients]; - TcpSocketClient* client = [TcpSockets findClient:cId callback:nil]; + TcpSocketClient* client = [self findClient:cId callback:nil]; if (!client) return; [client destroy]; [_clients removeObjectForKey:cId]; } -+(void) closeAllSockets { - NSMutableDictionary *_clients = [TcpSockets clients]; - for (NSNumber* cId in _clients) { - [TcpSockets endClient:cId callback:nil]; - } +-(void)onConnection:(TcpSocketClient *)client toClient:(NSNumber *)clientID { + _clients[client.id] = client; + + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"tcp-%@-event", clientID] + body:@{ @"event": @"connection", @"data": client.id }]; +} + +-(NSNumber*)generateRandomId { + int r = 0; + do { + r = (arc4random() % 1000) + 5001; + } while(_clients[@(r)]); + + return @(r); } @end