| (function() { |
| var Auth, EventEmitter, Forge, Parser, Promise, Protocol, RollingCounter, ServiceMap, Socket, crypto, debug, |
| __hasProp = {}.hasOwnProperty, |
| __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; |
| |
| crypto = require('crypto'); |
| |
| EventEmitter = require('events').EventEmitter; |
| |
| Promise = require('bluebird'); |
| |
| Forge = require('node-forge'); |
| |
| debug = require('debug')('adb:tcpusb:socket'); |
| |
| Parser = require('../parser'); |
| |
| Protocol = require('../protocol'); |
| |
| Auth = require('../auth'); |
| |
| ServiceMap = require('./servicemap'); |
| |
| RollingCounter = require('./rollingcounter'); |
| |
| Socket = (function(_super) { |
| var AUTH_RSAPUBLICKEY, AUTH_SIGNATURE, AUTH_TOKEN, A_AUTH, A_CLSE, A_CNXN, A_OKAY, A_OPEN, A_SYNC, A_WRTE, TOKEN_LENGTH, UINT32_MAX; |
| |
| __extends(Socket, _super); |
| |
| A_SYNC = 0x434e5953; |
| |
| A_CNXN = 0x4e584e43; |
| |
| A_OPEN = 0x4e45504f; |
| |
| A_OKAY = 0x59414b4f; |
| |
| A_CLSE = 0x45534c43; |
| |
| A_WRTE = 0x45545257; |
| |
| A_AUTH = 0x48545541; |
| |
| UINT32_MAX = 0xFFFFFFFF; |
| |
| AUTH_TOKEN = 1; |
| |
| AUTH_SIGNATURE = 2; |
| |
| AUTH_RSAPUBLICKEY = 3; |
| |
| TOKEN_LENGTH = 20; |
| |
| function Socket(client, serial, socket, options) { |
| var _base; |
| this.client = client; |
| this.serial = serial; |
| this.socket = socket; |
| this.options = options != null ? options : {}; |
| (_base = this.options).auth || (_base.auth = Promise.resolve(true)); |
| this.ended = false; |
| this.parser = new Parser(this.socket); |
| this.version = 1; |
| this.maxPayload = 4096; |
| this.authorized = false; |
| this.syncToken = new RollingCounter(UINT32_MAX); |
| this.remoteId = new RollingCounter(UINT32_MAX); |
| this.services = new ServiceMap; |
| this.remoteAddress = this.socket.remoteAddress; |
| this.token = null; |
| this.signature = null; |
| this._inputLoop(); |
| } |
| |
| Socket.prototype.end = function() { |
| if (!this.ended) { |
| this.socket.end(); |
| this.services.end(); |
| this.emit('end'); |
| this.ended = true; |
| } |
| return this; |
| }; |
| |
| Socket.prototype._inputLoop = function() { |
| return this._readMessage().then((function(_this) { |
| return function(message) { |
| return _this._route(message); |
| }; |
| })(this)).then((function(_this) { |
| return function() { |
| return setImmediate(_this._inputLoop.bind(_this)); |
| }; |
| })(this))["catch"](Parser.PrematureEOFError, (function(_this) { |
| return function() { |
| return _this.end(); |
| }; |
| })(this))["catch"]((function(_this) { |
| return function(err) { |
| debug("Error: " + err.message); |
| _this.emit('error', err); |
| return _this.end(); |
| }; |
| })(this)); |
| }; |
| |
| Socket.prototype._readMessage = function() { |
| return this.parser.readBytes(24).then(function(header) { |
| return { |
| command: header.readUInt32LE(0), |
| arg0: header.readUInt32LE(4), |
| arg1: header.readUInt32LE(8), |
| length: header.readUInt32LE(12), |
| check: header.readUInt32LE(16), |
| magic: header.readUInt32LE(20) |
| }; |
| }).then((function(_this) { |
| return function(message) { |
| return _this.parser.readBytes(message.length).then(function(data) { |
| message.data = data; |
| return message; |
| }); |
| }; |
| })(this)).then((function(_this) { |
| return function(message) { |
| return _this._validateMessage(message); |
| }; |
| })(this)); |
| }; |
| |
| Socket.prototype._route = function(message) { |
| if (this.ended) { |
| return; |
| } |
| this.emit('userActivity', message); |
| switch (message.command) { |
| case A_SYNC: |
| return this._handleSyncMessage(message); |
| case A_CNXN: |
| return this._handleConnectionMessage(message); |
| case A_OPEN: |
| return this._handleOpenMessage(message); |
| case A_OKAY: |
| return this._handleOkayMessage(message); |
| case A_CLSE: |
| return this._handleCloseMessage(message); |
| case A_WRTE: |
| return this._handleWriteMessage(message); |
| case A_AUTH: |
| return this._handleAuthMessage(message); |
| default: |
| return this.emit('error', new Error("Unknown command " + message.command)); |
| } |
| }; |
| |
| Socket.prototype._handleSyncMessage = function(message) { |
| return this._writeHeader(A_SYNC, 1, this.syncToken.next(), 0); |
| }; |
| |
| Socket.prototype._handleConnectionMessage = function(message) { |
| debug('A_CNXN', message); |
| return this._createToken().then((function(_this) { |
| return function(token) { |
| _this.token = token; |
| return _this._writeMessage(A_AUTH, AUTH_TOKEN, 0, _this.token); |
| }; |
| })(this)); |
| }; |
| |
| Socket.prototype._handleAuthMessage = function(message) { |
| debug('A_AUTH', message); |
| switch (message.arg0) { |
| case AUTH_SIGNATURE: |
| if (!this.signature) { |
| this.signature = message.data; |
| } |
| return this._writeMessage(A_AUTH, AUTH_TOKEN, 0, this.token); |
| case AUTH_RSAPUBLICKEY: |
| return Auth.parsePublicKey(message.data.slice(0, -1)).then((function(_this) { |
| return function(key) { |
| var digest, sig; |
| digest = _this.token.toString('binary'); |
| sig = _this.signature.toString('binary'); |
| if (key.verify(digest, sig)) { |
| return _this.options.auth(key).then(function() { |
| return _this._deviceId().then(function(id) { |
| _this.authorized = true; |
| return _this._writeMessage(A_CNXN, _this.version, _this.maxPayload, "device::" + id); |
| }); |
| })["catch"](function(err) { |
| return _this.end(); |
| }); |
| } else { |
| return _this.end(); |
| } |
| }; |
| })(this)); |
| default: |
| return this.end(); |
| } |
| }; |
| |
| Socket.prototype._handleOpenMessage = function(message) { |
| var command, localId, remoteId, service; |
| if (!this.authorized) { |
| return; |
| } |
| debug('A_OPEN', message); |
| localId = message.arg0; |
| remoteId = this.remoteId.next(); |
| service = message.data.slice(0, -1); |
| command = service.toString().split(':', 1)[0]; |
| return this.client.transport(this.serial).then((function(_this) { |
| return function(transport) { |
| var parser, pump; |
| if (_this.ended) { |
| return; |
| } |
| debug("Calling " + service); |
| _this.services.put(remoteId, transport); |
| transport.write(Protocol.encodeData(service)); |
| parser = transport.parser; |
| pump = function() { |
| return new Promise(function(resolve, reject) { |
| var maybeRead, out; |
| out = parser.raw(); |
| maybeRead = function() { |
| var chunk, _results; |
| _results = []; |
| while (chunk = _this._readChunk(out)) { |
| _results.push(_this._writeMessage(A_WRTE, remoteId, localId, chunk)); |
| } |
| return _results; |
| }; |
| out.on('readable', maybeRead); |
| return out.on('end', resolve); |
| }); |
| }; |
| parser.readAscii(4).then(function(reply) { |
| switch (reply) { |
| case Protocol.OKAY: |
| _this._writeHeader(A_OKAY, remoteId, localId); |
| return pump(); |
| case Protocol.FAIL: |
| return parser.readError(); |
| default: |
| return parser.unexpected(reply, 'OKAY or FAIL'); |
| } |
| })["catch"](Parser.PrematureEOFError, function() { |
| return true; |
| })["finally"](function() { |
| return _this._close(remoteId, localId); |
| })["catch"](Parser.FailError, function(err) { |
| debug("Unable to open transport: " + err); |
| return _this.end(); |
| }); |
| }; |
| })(this)); |
| }; |
| |
| Socket.prototype._handleOkayMessage = function(message) { |
| var localId, remoteId; |
| if (!this.authorized) { |
| return; |
| } |
| debug('A_OKAY', message); |
| localId = message.arg0; |
| return remoteId = message.arg1; |
| }; |
| |
| Socket.prototype._handleCloseMessage = function(message) { |
| var localId, remoteId; |
| if (!this.authorized) { |
| return; |
| } |
| debug('A_CLSE', message); |
| localId = message.arg0; |
| remoteId = message.arg1; |
| return this._close(remoteId, localId); |
| }; |
| |
| Socket.prototype._handleWriteMessage = function(message) { |
| var localId, remote, remoteId; |
| if (!this.authorized) { |
| return; |
| } |
| debug('A_WRTE', message); |
| localId = message.arg0; |
| remoteId = message.arg1; |
| if (remote = this.services.get(remoteId)) { |
| remote.write(message.data); |
| this._writeHeader(A_OKAY, remoteId, localId); |
| } else { |
| debug("A_WRTE to unknown socket pair " + localId + "/" + remoteId); |
| } |
| return true; |
| }; |
| |
| Socket.prototype._close = function(remoteId, localId) { |
| var remote; |
| if (remote = this.services.remove(remoteId)) { |
| remote.end(); |
| return this._writeHeader(A_CLSE, remoteId, localId); |
| } |
| }; |
| |
| Socket.prototype._writeHeader = function(command, arg0, arg1, length, checksum) { |
| var header; |
| if (this.ended) { |
| return; |
| } |
| header = new Buffer(24); |
| header.writeUInt32LE(command, 0); |
| header.writeUInt32LE(arg0 || 0, 4); |
| header.writeUInt32LE(arg1 || 0, 8); |
| header.writeUInt32LE(length || 0, 12); |
| header.writeUInt32LE(checksum || 0, 16); |
| header.writeUInt32LE(this._magic(command), 20); |
| return this.socket.write(header); |
| }; |
| |
| Socket.prototype._writeMessage = function(command, arg0, arg1, data) { |
| if (this.ended) { |
| return; |
| } |
| if (!Buffer.isBuffer(data)) { |
| data = new Buffer(data); |
| } |
| this._writeHeader(command, arg0, arg1, data.length, this._checksum(data)); |
| return this.socket.write(data); |
| }; |
| |
| Socket.prototype._validateMessage = function(message) { |
| if (message.magic !== this._magic(message.command)) { |
| throw new Error("Command failed magic check"); |
| } |
| if (message.check !== this._checksum(message.data)) { |
| throw new Error("Message checksum doesn't match received message"); |
| } |
| return message; |
| }; |
| |
| Socket.prototype._readChunk = function(stream) { |
| return stream.read(this.maxPayload) || stream.read(); |
| }; |
| |
| Socket.prototype._checksum = function(data) { |
| var char, sum, _i, _len; |
| if (!Buffer.isBuffer(data)) { |
| throw new Error("Unable to calculate checksum of non-Buffer"); |
| } |
| sum = 0; |
| for (_i = 0, _len = data.length; _i < _len; _i++) { |
| char = data[_i]; |
| sum += char; |
| } |
| return sum; |
| }; |
| |
| Socket.prototype._magic = function(command) { |
| return (command ^ 0xffffffff) >>> 0; |
| }; |
| |
| Socket.prototype._createToken = function() { |
| return Promise.promisify(crypto.randomBytes)(TOKEN_LENGTH); |
| }; |
| |
| Socket.prototype._deviceId = function() { |
| return this.client.getProperties(this.serial).then(function(properties) { |
| var prop; |
| return ((function() { |
| var _i, _len, _ref, _results; |
| _ref = ['ro.product.name', 'ro.product.model', 'ro.product.device']; |
| _results = []; |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { |
| prop = _ref[_i]; |
| _results.push("" + prop + "=" + properties[prop] + ";"); |
| } |
| return _results; |
| })()).join(''); |
| }); |
| }; |
| |
| return Socket; |
| |
| })(EventEmitter); |
| |
| module.exports = Socket; |
| |
| }).call(this); |