adbkit
Advanced tools
Comparing version 2.2.2 to 2.3.0
@@ -41,2 +41,5 @@ (function() { | ||
_this.connections.push(socket); | ||
socket.on('error', function(err) { | ||
return _this.emit('error', err); | ||
}); | ||
socket.once('end', function() { | ||
@@ -43,0 +46,0 @@ return _this.connections = _this.connections.filter(function(val) { |
(function() { | ||
var Auth, EventEmitter, Forge, Parser, Promise, Protocol, RollingCounter, ServiceMap, Socket, crypto, debug, | ||
var Auth, EventEmitter, Forge, Packet, PacketReader, Parser, Promise, Protocol, RollingCounter, Service, ServiceMap, Socket, crypto, debug, | ||
extend = 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; }, | ||
@@ -22,2 +22,8 @@ hasProp = {}.hasOwnProperty; | ||
Packet = require('./packet'); | ||
PacketReader = require('./packetreader'); | ||
Service = require('./service'); | ||
ServiceMap = require('./servicemap'); | ||
@@ -28,22 +34,38 @@ | ||
Socket = (function(superClass) { | ||
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; | ||
var AUTH_RSAPUBLICKEY, AUTH_SIGNATURE, AUTH_TOKEN, TOKEN_LENGTH, UINT16_MAX, UINT32_MAX; | ||
extend(Socket, superClass); | ||
A_SYNC = 0x434e5953; | ||
Socket.AuthError = (function(superClass1) { | ||
extend(AuthError, superClass1); | ||
A_CNXN = 0x4e584e43; | ||
function AuthError(message) { | ||
this.message = message; | ||
Error.call(this); | ||
this.name = 'AuthError'; | ||
Error.captureStackTrace(this, Socket.AuthError); | ||
} | ||
A_OPEN = 0x4e45504f; | ||
return AuthError; | ||
A_OKAY = 0x59414b4f; | ||
})(Error); | ||
A_CLSE = 0x45534c43; | ||
Socket.UnauthorizedError = (function(superClass1) { | ||
extend(UnauthorizedError, superClass1); | ||
A_WRTE = 0x45545257; | ||
function UnauthorizedError() { | ||
Error.call(this); | ||
this.name = 'UnauthorizedError'; | ||
this.message = "Unauthorized access"; | ||
Error.captureStackTrace(this, Socket.UnauthorizedError); | ||
} | ||
A_AUTH = 0x48545541; | ||
return UnauthorizedError; | ||
})(Error); | ||
UINT32_MAX = 0xFFFFFFFF; | ||
UINT16_MAX = 0xFFFF; | ||
AUTH_TOKEN = 1; | ||
@@ -65,3 +87,4 @@ | ||
this.ended = false; | ||
this.parser = new Parser(this.socket); | ||
this.reader = new PacketReader(this.socket); | ||
this.reader.on('packet', this._handle.bind(this)); | ||
this.version = 1; | ||
@@ -76,27 +99,50 @@ this.maxPayload = 4096; | ||
this.signature = null; | ||
this._inputLoop(); | ||
} | ||
Socket.prototype.end = function() { | ||
if (!this.ended) { | ||
this.socket.end(); | ||
this.services.end(); | ||
this.emit('end'); | ||
this.ended = true; | ||
if (this.ended) { | ||
return this; | ||
} | ||
this.socket.end(); | ||
this.services.end(); | ||
this.ended = true; | ||
this.emit('end'); | ||
return this; | ||
}; | ||
Socket.prototype._inputLoop = function() { | ||
return this._readMessage().then((function(_this) { | ||
return function(message) { | ||
return _this._route(message); | ||
Socket.prototype._error = function(err) { | ||
this.emit('error', err); | ||
return this.end(); | ||
}; | ||
Socket.prototype._handle = function(packet) { | ||
if (this.ended) { | ||
return; | ||
} | ||
this.emit('userActivity', packet); | ||
return Promise["try"]((function(_this) { | ||
return function() { | ||
switch (packet.command) { | ||
case Packet.A_SYNC: | ||
return _this._handleSyncPacket(packet); | ||
case Packet.A_CNXN: | ||
return _this._handleConnectionPacket(packet); | ||
case Packet.A_OPEN: | ||
return _this._handleOpenPacket(packet); | ||
case Packet.A_OKAY: | ||
case Packet.A_WRTE: | ||
case Packet.A_CLSE: | ||
return _this._forwardServicePacket(packet); | ||
case Packet.A_AUTH: | ||
return _this._handleAuthPacket(packet); | ||
default: | ||
throw new Error("Unknown command " + packet.command); | ||
} | ||
}; | ||
})(this)).then((function(_this) { | ||
})(this))["catch"](Socket.AuthError, (function(_this) { | ||
return function() { | ||
return setImmediate(_this._inputLoop.bind(_this)); | ||
return _this.end(); | ||
}; | ||
})(this))["catch"](Parser.PrematureEOFError, (function(_this) { | ||
})(this))["catch"](Socket.UnauthorizedError, (function(_this) { | ||
return function() { | ||
debug("adb disconnect"); | ||
return _this.end(); | ||
@@ -106,5 +152,3 @@ }; | ||
return function(err) { | ||
debug("Error: " + err.message); | ||
_this.emit('error', err); | ||
return _this.end(); | ||
return _this._error(err); | ||
}; | ||
@@ -114,61 +158,18 @@ })(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._handleSyncPacket = function(packet) { | ||
debug('I:A_SYNC'); | ||
debug('O:A_SYNC'); | ||
return this.write(Packet.assemble(Packet.A_SYNC, 1, this.syncToken.next(), null)); | ||
}; | ||
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); | ||
Socket.prototype._handleConnectionPacket = function(packet) { | ||
var version; | ||
debug('I:A_CNXN', packet); | ||
version = Packet.swap32(packet.arg0); | ||
this.maxPayload = Math.min(UINT16_MAX, packet.arg1); | ||
return this._createToken().then((function(_this) { | ||
return function(token) { | ||
_this.token = token; | ||
return _this._writeMessage(A_AUTH, AUTH_TOKEN, 0, _this.token); | ||
debug('O:A_AUTH'); | ||
return _this.write(Packet.assemble(Packet.A_AUTH, AUTH_TOKEN, 0, _this.token)); | ||
}; | ||
@@ -178,12 +179,19 @@ })(this)); | ||
Socket.prototype._handleAuthMessage = function(message) { | ||
debug('A_AUTH', message); | ||
switch (message.arg0) { | ||
Socket.prototype._handleAuthPacket = function(packet) { | ||
debug('I:A_AUTH', packet); | ||
switch (packet.arg0) { | ||
case AUTH_SIGNATURE: | ||
if (!this.signature) { | ||
this.signature = message.data; | ||
this.signature = packet.data; | ||
} | ||
return this._writeMessage(A_AUTH, AUTH_TOKEN, 0, this.token); | ||
debug('O:A_AUTH'); | ||
return this.write(Packet.assemble(Packet.A_AUTH, AUTH_TOKEN, 0, this.token)); | ||
case AUTH_RSAPUBLICKEY: | ||
return Auth.parsePublicKey(this._skipNull(message.data)).then((function(_this) { | ||
if (!this.signature) { | ||
throw new AuthError("Public key sent before signature"); | ||
} | ||
if (!(packet.data && packet.data.length >= 2)) { | ||
throw new AuthError("Empty RSA public key"); | ||
} | ||
return Auth.parsePublicKey(this._skipNull(packet.data)).then((function(_this) { | ||
return function(key) { | ||
@@ -193,81 +201,57 @@ var digest, sig; | ||
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(); | ||
if (!key.verify(digest, sig)) { | ||
throw new Socket.AuthError("Signature mismatch"); | ||
} | ||
return key; | ||
}; | ||
})(this)).then((function(_this) { | ||
return function(key) { | ||
return _this.options.auth(key)["catch"](function(err) { | ||
throw new Socket.AuthError("Rejected by user-defined handler"); | ||
}); | ||
}; | ||
})(this)).then((function(_this) { | ||
return function() { | ||
return _this._deviceId(); | ||
}; | ||
})(this)).then((function(_this) { | ||
return function(id) { | ||
_this.authorized = true; | ||
debug('O:A_CNXN'); | ||
return _this.write(Packet.assemble(Packet.A_CNXN, Packet.swap32(_this.version), _this.maxPayload, id)); | ||
}; | ||
})(this)); | ||
default: | ||
return this.end(); | ||
throw new Error("Unknown authentication method " + packet.arg0); | ||
} | ||
}; | ||
Socket.prototype._handleOpenMessage = function(message) { | ||
var localId, remoteId, service; | ||
Socket.prototype._handleOpenPacket = function(packet) { | ||
var localId, name, remoteId, service; | ||
if (!this.authorized) { | ||
return; | ||
throw new Socket.UnauthorizedError(); | ||
} | ||
debug('A_OPEN', message); | ||
localId = message.arg0; | ||
remoteId = this.remoteId.next(); | ||
service = this._skipNull(message.data); | ||
debug("Calling " + service); | ||
this._writeHeader(A_OKAY, remoteId, localId); | ||
this.client.transport(this.serial).then((function(_this) { | ||
return function(transport) { | ||
var parser; | ||
if (_this.ended) { | ||
return; | ||
} | ||
_this.services.insert(remoteId, transport); | ||
remoteId = packet.arg0; | ||
localId = this.remoteId.next(); | ||
if (!(packet.data && packet.data.length >= 2)) { | ||
throw new Error("Empty service name"); | ||
} | ||
name = this._skipNull(packet.data); | ||
debug("Calling " + name); | ||
service = new Service(this.client, this.serial, localId, remoteId, this); | ||
return new Promise((function(_this) { | ||
return function(resolve, reject) { | ||
service.on('error', reject); | ||
service.on('end', resolve); | ||
_this.services.insert(localId, service); | ||
debug("Handling " + _this.services.count + " services simultaneously"); | ||
transport.write(Protocol.encodeData(service)); | ||
parser = transport.parser; | ||
transport.on('error', function(err) {}); | ||
return parser.readAscii(4).then(function(reply) { | ||
switch (reply) { | ||
case Protocol.OKAY: | ||
break; | ||
case Protocol.FAIL: | ||
return parser.readError(); | ||
default: | ||
return parser.unexpected(reply, 'OKAY or FAIL'); | ||
} | ||
}).then(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); | ||
out.on('end', resolve); | ||
out.on('error', reject); | ||
return maybeRead(); | ||
}); | ||
})["catch"](Parser.PrematureEOFError, function() { | ||
return true; | ||
})["finally"](function() { | ||
return _this._close(remoteId, localId); | ||
})["catch"](Parser.FailError, function(err) { | ||
debug("Unable to open service: " + err); | ||
return _this.end(); | ||
}); | ||
return service.handle(packet); | ||
}; | ||
})(this))["catch"]((function(_this) { | ||
return function(err) { | ||
return _this._close(remoteId, localId); | ||
})(this))["catch"](function(err) { | ||
return true; | ||
})["finally"]((function(_this) { | ||
return function() { | ||
_this.services.remove(localId); | ||
debug("Handling " + _this.services.count + " services simultaneously"); | ||
return service.end(); | ||
}; | ||
@@ -277,106 +261,23 @@ })(this)); | ||
Socket.prototype._handleOkayMessage = function(message) { | ||
var localId, remoteId; | ||
Socket.prototype._forwardServicePacket = function(packet) { | ||
var localId, remoteId, service; | ||
if (!this.authorized) { | ||
return; | ||
throw new Socket.UnauthorizedError(); | ||
} | ||
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); | ||
remoteId = packet.arg0; | ||
localId = packet.arg1; | ||
if (service = this.services.get(localId)) { | ||
return service.handle(packet); | ||
} else { | ||
debug("A_WRTE to unknown socket pair " + localId + "/" + remoteId); | ||
return debug("Received a packet to a service that may have been closed already"); | ||
} | ||
return true; | ||
}; | ||
Socket.prototype._close = function(remoteId, localId) { | ||
var remote; | ||
if (remote = this.services.remove(remoteId)) { | ||
debug("Handling " + this.services.count + " services simultaneously"); | ||
remote.end(); | ||
return this._writeHeader(A_CLSE, remoteId, localId); | ||
} | ||
}; | ||
Socket.prototype._writeHeader = function(command, arg0, arg1, length, checksum) { | ||
var header; | ||
Socket.prototype.write = function(chunk) { | ||
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); | ||
return this.socket.write(chunk); | ||
}; | ||
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, i, len, sum; | ||
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() { | ||
@@ -393,4 +294,4 @@ return Promise.promisify(crypto.randomBytes)(TOKEN_LENGTH); | ||
return this.client.getProperties(this.serial).then(function(properties) { | ||
var prop; | ||
return ((function() { | ||
var id, prop; | ||
id = ((function() { | ||
var i, len, ref, results; | ||
@@ -405,2 +306,3 @@ ref = ['ro.product.name', 'ro.product.model', 'ro.product.device']; | ||
})()).join(''); | ||
return new Buffer("device::" + id + "\0"); | ||
}); | ||
@@ -407,0 +309,0 @@ }; |
(function() { | ||
var Adb, Auth, Promise, forge, fs, pkg, program; | ||
var Adb, Auth, PacketReader, Promise, forge, fs, pkg, program; | ||
@@ -18,2 +18,4 @@ fs = require('fs'); | ||
PacketReader = require('./adb/tcpusb/packetreader'); | ||
Promise.longStackTraces(); | ||
@@ -52,2 +54,4 @@ | ||
return console.info('Connect with `adb connect localhost:%d`', options.port); | ||
}).on('error', function(err) { | ||
return console.error("An error occured: " + err.message); | ||
}); | ||
@@ -57,4 +61,12 @@ return server.listen(options.port); | ||
program.command('parse-tcp-packets <file>').description('Parses ADB TCP packets from the given file.').action(function(file, options) { | ||
var reader; | ||
reader = new PacketReader(fs.createReadStream(file)); | ||
return reader.on('packet', function(packet) { | ||
return console.log(packet.toString()); | ||
}); | ||
}); | ||
program.parse(process.argv); | ||
}).call(this); |
{ | ||
"name": "adbkit", | ||
"version": "2.2.2", | ||
"version": "2.3.0", | ||
"description": "A pure Node.js client for the Android Debug Bridge.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
224935
69
4414