├── .editorconfig
├── .gitignore
├── .npmignore
├── CONTRIBUTING.md
├── Gruntfile.coffee
├── LICENSE
├── README.md
├── bench
└── sync
│ └── pull.coffee
├── bin
└── adbkit
├── index.coffee
├── package.json
├── src
├── adb.coffee
├── adb
│ ├── auth.coffee
│ ├── client.coffee
│ ├── command.coffee
│ ├── command
│ │ ├── host-serial
│ │ │ ├── forward.coffee
│ │ │ ├── getdevicepath.coffee
│ │ │ ├── getserialno.coffee
│ │ │ ├── getstate.coffee
│ │ │ ├── listforwards.coffee
│ │ │ └── waitfordevice.coffee
│ │ ├── host-transport
│ │ │ ├── clear.coffee
│ │ │ ├── framebuffer.coffee
│ │ │ ├── getfeatures.coffee
│ │ │ ├── getpackages.coffee
│ │ │ ├── getproperties.coffee
│ │ │ ├── install.coffee
│ │ │ ├── isinstalled.coffee
│ │ │ ├── listreverses.coffee
│ │ │ ├── local.coffee
│ │ │ ├── log.coffee
│ │ │ ├── logcat.coffee
│ │ │ ├── monkey.coffee
│ │ │ ├── reboot.coffee
│ │ │ ├── remount.coffee
│ │ │ ├── reverse.coffee
│ │ │ ├── root.coffee
│ │ │ ├── screencap.coffee
│ │ │ ├── shell.coffee
│ │ │ ├── startactivity.coffee
│ │ │ ├── startservice.coffee
│ │ │ ├── sync.coffee
│ │ │ ├── tcp.coffee
│ │ │ ├── tcpip.coffee
│ │ │ ├── trackjdwp.coffee
│ │ │ ├── uninstall.coffee
│ │ │ ├── usb.coffee
│ │ │ └── waitbootcomplete.coffee
│ │ └── host
│ │ │ ├── connect.coffee
│ │ │ ├── devices.coffee
│ │ │ ├── deviceswithpaths.coffee
│ │ │ ├── disconnect.coffee
│ │ │ ├── kill.coffee
│ │ │ ├── trackdevices.coffee
│ │ │ ├── transport.coffee
│ │ │ └── version.coffee
│ ├── connection.coffee
│ ├── dump.coffee
│ ├── framebuffer
│ │ └── rgbtransform.coffee
│ ├── keycode.coffee
│ ├── linetransform.coffee
│ ├── parser.coffee
│ ├── proc
│ │ └── stat.coffee
│ ├── protocol.coffee
│ ├── sync.coffee
│ ├── sync
│ │ ├── entry.coffee
│ │ ├── pulltransfer.coffee
│ │ ├── pushtransfer.coffee
│ │ └── stats.coffee
│ ├── tcpusb
│ │ ├── packet.coffee
│ │ ├── packetreader.coffee
│ │ ├── rollingcounter.coffee
│ │ ├── server.coffee
│ │ ├── service.coffee
│ │ ├── servicemap.coffee
│ │ └── socket.coffee
│ ├── tracker.coffee
│ └── util.coffee
└── cli.coffee
├── tasks
└── keycode.coffee
└── test
├── adb.coffee
├── adb
├── command
│ ├── host-serial
│ │ └── waitfordevice.coffee
│ ├── host-transport
│ │ ├── clear.coffee
│ │ ├── framebuffer.coffee
│ │ ├── getfeatures.coffee
│ │ ├── getpackages.coffee
│ │ ├── getproperties.coffee
│ │ ├── install.coffee
│ │ ├── isinstalled.coffee
│ │ ├── local.coffee
│ │ ├── log.coffee
│ │ ├── logcat.coffee
│ │ ├── monkey.coffee
│ │ ├── reboot.coffee
│ │ ├── remount.coffee
│ │ ├── root.coffee
│ │ ├── screencap.coffee
│ │ ├── shell.coffee
│ │ ├── startactivity.coffee
│ │ ├── startservice.coffee
│ │ ├── sync.coffee
│ │ ├── tcp.coffee
│ │ ├── tcpip.coffee
│ │ ├── uninstall.coffee
│ │ ├── usb.coffee
│ │ └── waitbootcomplete.coffee
│ └── host
│ │ ├── connect.coffee
│ │ ├── disconnect.coffee
│ │ └── version.coffee
├── framebuffer
│ └── rgbtransform.coffee
├── linetransform.coffee
├── parser.coffee
├── protocol.coffee
├── sync.coffee
├── tracker.coffee
└── util.coffee
└── mock
├── connection.coffee
└── duplex.coffee
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org/
2 |
3 | root = true
4 |
5 | [*]
6 | indent_style = tab
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.js]
13 | indent_style = space
14 | indent_size = 2
15 |
16 | [*.json]
17 | indent_style = space
18 | indent_size = 2
19 |
20 | [*.coffee]
21 | indent_style = space
22 | indent_size = 2
23 |
24 | [*.md]
25 | indent_style = space
26 | indent_size = 2
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /lib/
3 | /temp/
4 | /index.js
5 | /*.tgz
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /.editorconfig
2 | /.npmignore
3 | /*.tgz
4 | /npm-debug.log
5 | /Gruntfile.coffee
6 | /index.coffee
7 | /CONTRIBUTING.md
8 | /src/
9 | /test/
10 | /bench/
11 | /tasks/
12 | /temp/
13 | /git_hooks/
14 | *.!sync
15 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We are happy to accept any contributions that make sense and respect the rules listed below.
4 |
5 | ## How to contribute
6 |
7 | 1. Fork the repo.
8 | 2. Create a feature branch for your contribution out of the `master` branch. Only one contribution per branch is accepted.
9 | 3. Implement your contribution while respecting our rules (see below).
10 | 4. If possible, add tests for your contribution to make sure it actually works.
11 | 5. Don't forget to run `npm test` just right before submitting, it also checks for code styling issues.
12 | 6. Submit a pull request against our `master` branch!
13 |
14 | ## Rules
15 |
16 | * **Do** use feature branches.
17 | * **Do** conform to existing coding style so that your contribution fits in.
18 | * **Do** use [EditorConfig] to enforce our [whitespace rules](.editorconfig). If your editor is not supported, enforce the settings manually.
19 | * **Do** run `npm test` for CoffeeLint, JSONLint and unit test coverage.
20 | * **Do not** touch the `version` field in [package.json](package.json).
21 | * **Do not** commit any generated files, unless already in the repo. If absolutely necessary, explain why.
22 | * **Do not** create any top level files or directories. If absolutely necessary, explain why and update [.npmignore](.npmignore).
23 |
24 | ## License
25 |
26 | By contributing your code, you agree to license your contribution under our [LICENSE](LICENSE).
27 |
28 | [editorconfig]:
29 |
--------------------------------------------------------------------------------
/Gruntfile.coffee:
--------------------------------------------------------------------------------
1 | module.exports = (grunt) ->
2 |
3 | grunt.initConfig
4 | pkg: require './package'
5 | coffee:
6 | src:
7 | options:
8 | bare: true
9 | noHeader: true
10 | expand: true
11 | cwd: 'src'
12 | src: '**/*.coffee'
13 | dest: 'lib'
14 | ext: '.js'
15 | index:
16 | src: 'index.coffee'
17 | dest: 'index.js'
18 | clean:
19 | lib:
20 | src: 'lib'
21 | index:
22 | src: 'index.js'
23 | coffeelint:
24 | options:
25 | indentation:
26 | level: 'ignore'
27 | no_backticks:
28 | level: 'ignore'
29 | src:
30 | src: '<%= coffee.src.cwd %>/<%= coffee.src.src %>'
31 | index:
32 | src: '<%= coffee.index.src %>'
33 | test:
34 | src: 'test/**/*.coffee'
35 | tasks:
36 | src: 'tasks/**/*.coffee'
37 | gruntfile:
38 | src: 'Gruntfile.coffee'
39 | jsonlint:
40 | packagejson:
41 | src: 'package.json'
42 | watch:
43 | src:
44 | files: '<%= coffee.src.cwd %>/<%= coffee.src.src %>'
45 | tasks: ['coffeelint:src', 'test']
46 | index:
47 | files: '<%= coffee.index.src %>'
48 | tasks: ['coffeelint:index', 'test']
49 | test:
50 | files: '<%= coffeelint.test.src %>',
51 | tasks: ['coffeelint:test', 'test']
52 | gruntfile:
53 | files: '<%= coffeelint.gruntfile.src %>'
54 | tasks: ['coffeelint:gruntfile']
55 | packagejson:
56 | files: '<%= jsonlint.packagejson.src %>'
57 | tasks: ['jsonlint:packagejson']
58 | exec:
59 | mocha:
60 | options: [
61 | '--compilers coffee:coffee-script/register'
62 | '--reporter spec'
63 | '--colors'
64 | '--recursive'
65 | ],
66 | cmd: './node_modules/.bin/mocha <%= exec.mocha.options.join(" ") %>'
67 | keycode:
68 | generate:
69 | dest: 'src/adb/keycode.coffee'
70 |
71 | grunt.loadNpmTasks 'grunt-contrib-clean'
72 | grunt.loadNpmTasks 'grunt-contrib-coffee'
73 | grunt.loadNpmTasks 'grunt-coffeelint'
74 | grunt.loadNpmTasks 'grunt-jsonlint'
75 | grunt.loadNpmTasks 'grunt-contrib-watch'
76 | grunt.loadNpmTasks 'grunt-notify'
77 | grunt.loadNpmTasks 'grunt-exec'
78 | grunt.loadTasks './tasks'
79 |
80 | grunt.registerTask 'test', ['jsonlint', 'coffeelint', 'exec:mocha']
81 | grunt.registerTask 'build', ['coffee']
82 | grunt.registerTask 'default', ['test']
83 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright © 2013 CyberAgent, Inc.
2 | Copyright © 2016 The OpenSTF Project
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
--------------------------------------------------------------------------------
/bench/sync/pull.coffee:
--------------------------------------------------------------------------------
1 | Bench = require 'bench'
2 | {spawn} = require 'child_process'
3 |
4 | Adb = require '../..'
5 |
6 | deviceId = process.env.DEVICE_ID
7 |
8 | module.exports =
9 | compareCount: 3
10 | compare:
11 | "pull /dev/graphics/fb0 using ADB CLI": (done) ->
12 | proc = spawn 'adb',
13 | ['-s', deviceId, 'pull', '/dev/graphics/fb0', '/dev/null']
14 | proc.stdout.on 'end', done
15 | "pull /dev/graphics/fb0 using client.pull()": (done) ->
16 | client = Adb.createClient()
17 | client.pull deviceId, '/dev/graphics/fb0', (err, stream) ->
18 | stream.resume()
19 | stream.on 'end', done
20 |
21 | Bench.runMain()
22 |
--------------------------------------------------------------------------------
/bin/adbkit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | require('../lib/cli')
4 |
--------------------------------------------------------------------------------
/index.coffee:
--------------------------------------------------------------------------------
1 | Path = require 'path'
2 |
3 | module.exports = switch Path.extname __filename
4 | when '.coffee' then require './src/adb'
5 | else require './lib/adb'
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adbkit",
3 | "version": "2.11.1",
4 | "description": "A pure Node.js client for the Android Debug Bridge.",
5 | "keywords": [
6 | "adb",
7 | "adbkit",
8 | "android",
9 | "logcat",
10 | "monkey"
11 | ],
12 | "bin": {
13 | "adbkit": "./bin/adbkit"
14 | },
15 | "bugs": {
16 | "url": "https://github.com/openstf/adbkit/issues"
17 | },
18 | "license": "Apache-2.0",
19 | "author": {
20 | "name": "The OpenSTF Project",
21 | "email": "contact@openstf.io",
22 | "url": "https://openstf.io"
23 | },
24 | "main": "./index",
25 | "repository": {
26 | "type": "git",
27 | "url": "https://github.com/openstf/adbkit.git"
28 | },
29 | "scripts": {
30 | "postpublish": "grunt clean",
31 | "prepublish": "grunt test coffee",
32 | "test": "grunt test"
33 | },
34 | "dependencies": {
35 | "adbkit-logcat": "^1.1.0",
36 | "adbkit-monkey": "~1.0.1",
37 | "bluebird": "~2.9.24",
38 | "commander": "^2.3.0",
39 | "debug": "~2.6.3",
40 | "node-forge": "^0.7.1",
41 | "split": "~0.3.3"
42 | },
43 | "devDependencies": {
44 | "bench": "~0.3.5",
45 | "chai": "~2.2.0",
46 | "coffee-script": "~1.9.1",
47 | "grunt": "~0.4.5",
48 | "grunt-cli": "~0.1.13",
49 | "grunt-coffeelint": "0.0.13",
50 | "grunt-contrib-clean": "~0.6.0",
51 | "grunt-contrib-coffee": "~0.13.0",
52 | "grunt-contrib-watch": "~0.6.1",
53 | "grunt-exec": "~0.4.3",
54 | "grunt-jsonlint": "~1.0.4",
55 | "grunt-notify": "~0.4.1",
56 | "mocha": "~2.2.1",
57 | "sinon": "~1.14.1",
58 | "sinon-chai": "~2.7.0",
59 | "coffeelint": "~1.9.3"
60 | },
61 | "engines": {
62 | "node": ">= 0.10.4"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/adb.coffee:
--------------------------------------------------------------------------------
1 | Client = require './adb/client'
2 | Keycode = require './adb/keycode'
3 | util = require './adb/util'
4 |
5 | class Adb
6 | @createClient: (options = {}) ->
7 | options.host ||= process.env.ADB_HOST
8 | options.port ||= process.env.ADB_PORT
9 | new Client options
10 |
11 | Adb.Keycode = Keycode
12 |
13 | Adb.util = util
14 |
15 | module.exports = Adb
16 |
--------------------------------------------------------------------------------
/src/adb/auth.coffee:
--------------------------------------------------------------------------------
1 | Promise = require 'bluebird'
2 | forge = require 'node-forge'
3 | BigInteger = forge.jsbn.BigInteger
4 |
5 | ###
6 | The stucture of an ADB RSAPublicKey is as follows:
7 |
8 | #define RSANUMBYTES 256 // 2048 bit key length
9 | #define RSANUMWORDS (RSANUMBYTES / sizeof(uint32_t))
10 |
11 | typedef struct RSAPublicKey {
12 | int len; // Length of n[] in number of uint32_t
13 | uint32_t n0inv; // -1 / n[0] mod 2^32
14 | uint32_t n[RSANUMWORDS]; // modulus as little endian array
15 | uint32_t rr[RSANUMWORDS]; // R^2 as little endian array
16 | int exponent; // 3 or 65537
17 | } RSAPublicKey;
18 |
19 | ###
20 | class Auth
21 | # coffeelint: disable=max_line_length
22 | RE = /^((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?)\0?( .*|)\s*$/
23 | # coffeelint: enable=max_line_length
24 |
25 | readPublicKeyFromStruct = (struct, comment) ->
26 | throw new Error "Invalid public key" unless struct.length
27 |
28 | # Keep track of what we've read already
29 | offset = 0
30 |
31 | # Get len
32 | len = struct.readUInt32LE(offset) * 4
33 | offset += 4
34 |
35 | unless struct.length is 4 + 4 + len + len + 4
36 | throw new Error "Invalid public key"
37 |
38 | # Skip n0inv, we don't need it
39 | offset += 4
40 |
41 | # Get n
42 | n = new Buffer len
43 | struct.copy(n, 0, offset, offset + len)
44 | [].reverse.call(n)
45 | offset += len
46 |
47 | # Skip rr, we don't need it
48 | offset += len
49 |
50 | # Get e
51 | e = struct.readUInt32LE(offset)
52 |
53 | unless e is 3 or e is 65537
54 | throw new Error "Invalid exponent #{e}, only 3 and 65537 are supported"
55 |
56 | # Restore the public key
57 | key = forge.pki.setRsaPublicKey(
58 | new BigInteger(n.toString('hex'), 16)
59 | new BigInteger(e.toString(), 10)
60 | )
61 |
62 | # It will be difficult to retrieve the fingerprint later as it's based
63 | # on the complete struct data, so let's just extend the key with it.
64 | md = forge.md.md5.create()
65 | md.update struct.toString('binary')
66 | key.fingerprint = md.digest().toHex().match(/../g).join(':')
67 |
68 | # Expose comment for the same reason
69 | key.comment = comment
70 |
71 | return key
72 |
73 | @parsePublicKey = (buffer) ->
74 | new Promise (resolve, reject) ->
75 | if match = RE.exec(buffer)
76 | struct = new Buffer(match[1], 'base64')
77 | comment = match[2].trim()
78 | resolve readPublicKeyFromStruct struct, comment
79 | else
80 | reject new Error "Unrecognizable public key format"
81 |
82 | module.exports = Auth
83 |
--------------------------------------------------------------------------------
/src/adb/command.coffee:
--------------------------------------------------------------------------------
1 | debug = require('debug')('adb:command')
2 |
3 | Parser = require './parser'
4 | Protocol = require './protocol'
5 |
6 | class Command
7 | RE_SQUOT = /'/g
8 | RE_ESCAPE = /([$`\\!"])/g
9 |
10 | constructor: (@connection) ->
11 | @parser = @connection.parser
12 | @protocol = Protocol
13 |
14 | execute: ->
15 | throw new Exception 'Missing implementation'
16 |
17 | _send: (data) ->
18 | encoded = Protocol.encodeData data
19 | debug "Send '#{encoded}'"
20 | @connection.write encoded
21 | return this
22 |
23 | # Note that this is just for convenience, not security.
24 | _escape: (arg) ->
25 | switch typeof arg
26 | when 'number'
27 | arg
28 | else
29 | "'" + arg.toString().replace(RE_SQUOT, "'\"'\"'") + "'"
30 |
31 | # Note that this is just for convenience, not security. Also, for some
32 | # incomprehensible reason, some Lenovo devices (e.g. Lenovo A806) behave
33 | # differently when arguments are given inside single quotes. See
34 | # https://github.com/openstf/stf/issues/471 for more information. So that's
35 | # why we now use double quotes here.
36 | _escapeCompat: (arg) ->
37 | switch typeof arg
38 | when 'number'
39 | arg
40 | else
41 | '"' + arg.toString().replace(RE_ESCAPE, '\\$1') + '"'
42 |
43 | module.exports = Command
44 |
--------------------------------------------------------------------------------
/src/adb/command/host-serial/forward.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class ForwardCommand extends Command
5 | execute: (serial, local, remote) ->
6 | this._send "host-serial:#{serial}:forward:#{local};#{remote}"
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | @parser.readAscii 4
12 | .then (reply) =>
13 | switch reply
14 | when Protocol.OKAY
15 | true
16 | when Protocol.FAIL
17 | @parser.readError()
18 | else
19 | @parser.unexpected reply, 'OKAY or FAIL'
20 | when Protocol.FAIL
21 | @parser.readError()
22 | else
23 | @parser.unexpected reply, 'OKAY or FAIL'
24 |
25 | module.exports = ForwardCommand
26 |
--------------------------------------------------------------------------------
/src/adb/command/host-serial/getdevicepath.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class GetDevicePathCommand extends Command
5 | execute: (serial) ->
6 | this._send "host-serial:#{serial}:get-devpath"
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | @parser.readValue()
12 | .then (value) ->
13 | value.toString()
14 | when Protocol.FAIL
15 | @parser.readError()
16 | else
17 | @parser.unexpected reply, 'OKAY or FAIL'
18 |
19 | module.exports = GetDevicePathCommand
20 |
--------------------------------------------------------------------------------
/src/adb/command/host-serial/getserialno.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class GetSerialNoCommand extends Command
5 | execute: (serial) ->
6 | this._send "host-serial:#{serial}:get-serialno"
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | @parser.readValue()
12 | .then (value) ->
13 | value.toString()
14 | when Protocol.FAIL
15 | @parser.readError()
16 | else
17 | @parser.unexpected reply, 'OKAY or FAIL'
18 |
19 | module.exports = GetSerialNoCommand
20 |
--------------------------------------------------------------------------------
/src/adb/command/host-serial/getstate.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class GetStateCommand extends Command
5 | execute: (serial) ->
6 | this._send "host-serial:#{serial}:get-state"
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | @parser.readValue()
12 | .then (value) ->
13 | value.toString()
14 | when Protocol.FAIL
15 | @parser.readError()
16 | else
17 | @parser.unexpected reply, 'OKAY or FAIL'
18 |
19 | module.exports = GetStateCommand
20 |
--------------------------------------------------------------------------------
/src/adb/command/host-serial/listforwards.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class ListForwardsCommand extends Command
5 | execute: (serial) ->
6 | this._send "host-serial:#{serial}:list-forward"
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | @parser.readValue()
12 | .then (value) =>
13 | this._parseForwards value
14 | when Protocol.FAIL
15 | @parser.readError()
16 | else
17 | @parser.unexpected reply, 'OKAY or FAIL'
18 |
19 | _parseForwards: (value) ->
20 | forwards = []
21 | for forward in value.toString().split '\n'
22 | if forward
23 | [serial, local, remote] = forward.split /\s+/
24 | forwards.push serial: serial, local: local, remote: remote
25 | return forwards
26 |
27 | module.exports = ListForwardsCommand
28 |
--------------------------------------------------------------------------------
/src/adb/command/host-serial/waitfordevice.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class WaitForDeviceCommand extends Command
5 | execute: (serial) ->
6 | this._send "host-serial:#{serial}:wait-for-any"
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | @parser.readAscii 4
12 | .then (reply) =>
13 | switch reply
14 | when Protocol.OKAY
15 | serial
16 | when Protocol.FAIL
17 | @parser.readError()
18 | else
19 | @parser.unexpected reply, 'OKAY or FAIL'
20 | when Protocol.FAIL
21 | @parser.readError()
22 | else
23 | @parser.unexpected reply, 'OKAY or FAIL'
24 |
25 | module.exports = WaitForDeviceCommand
26 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/clear.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class ClearCommand extends Command
5 | execute: (pkg) ->
6 | this._send "shell:pm clear #{pkg}"
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | @parser.searchLine /^(Success|Failed)$/
12 | .finally =>
13 | @parser.end()
14 | .then (result) ->
15 | switch result[0]
16 | when 'Success'
17 | true
18 | when 'Failed'
19 | # Unfortunately, the command may stall at this point and we
20 | # have to kill the connection.
21 | throw new Error "Package '#{pkg}' could not be cleared"
22 | when Protocol.FAIL
23 | @parser.readError()
24 | else
25 | @parser.unexpected reply, 'OKAY or FAIL'
26 |
27 | module.exports = ClearCommand
28 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/framebuffer.coffee:
--------------------------------------------------------------------------------
1 | Assert = require 'assert'
2 | {spawn} = require 'child_process'
3 | debug = require('debug')('adb:command:framebuffer')
4 |
5 | Command = require '../../command'
6 | Protocol = require '../../protocol'
7 | RgbTransform = require '../../framebuffer/rgbtransform'
8 |
9 | class FrameBufferCommand extends Command
10 | execute: (format) ->
11 | this._send 'framebuffer:'
12 | @parser.readAscii 4
13 | .then (reply) =>
14 | switch reply
15 | when Protocol.OKAY
16 | @parser.readBytes 52
17 | .then (header) =>
18 | meta = this._parseHeader header
19 | switch format
20 | when 'raw'
21 | stream = @parser.raw()
22 | stream.meta = meta
23 | stream
24 | else
25 | stream = this._convert meta, format
26 | stream.meta = meta
27 | stream
28 | when Protocol.FAIL
29 | @parser.readError()
30 | else
31 | @parser.unexpected reply, 'OKAY or FAIL'
32 |
33 | _convert: (meta, format, raw) ->
34 | debug "Converting raw framebuffer stream into #{format.toUpperCase()}"
35 | switch meta.format
36 | when 'rgb', 'rgba'
37 | # Known to be supported by GraphicsMagick
38 | else
39 | debug "Silently transforming '#{meta.format}' into 'rgb' for `gm`"
40 | transform = new RgbTransform meta
41 | meta.format = 'rgb'
42 | raw = @parser.raw().pipe transform
43 | proc = spawn 'gm', [
44 | 'convert'
45 | '-size'
46 | "#{meta.width}x#{meta.height}"
47 | "#{meta.format}:-"
48 | "#{format}:-"
49 | ]
50 | raw.pipe proc.stdin
51 | return proc.stdout
52 |
53 | _parseHeader: (header) ->
54 | meta = {}
55 | offset = 0
56 | meta.version = header.readUInt32LE offset
57 | if meta.version is 16
58 | throw new Error 'Old-style raw images are not supported'
59 | offset += 4
60 | meta.bpp = header.readUInt32LE offset
61 | offset += 4
62 | meta.size = header.readUInt32LE offset
63 | offset += 4
64 | meta.width = header.readUInt32LE offset
65 | offset += 4
66 | meta.height = header.readUInt32LE offset
67 | offset += 4
68 | meta.red_offset = header.readUInt32LE offset
69 | offset += 4
70 | meta.red_length = header.readUInt32LE offset
71 | offset += 4
72 | meta.blue_offset = header.readUInt32LE offset
73 | offset += 4
74 | meta.blue_length = header.readUInt32LE offset
75 | offset += 4
76 | meta.green_offset = header.readUInt32LE offset
77 | offset += 4
78 | meta.green_length = header.readUInt32LE offset
79 | offset += 4
80 | meta.alpha_offset = header.readUInt32LE offset
81 | offset += 4
82 | meta.alpha_length = header.readUInt32LE offset
83 | meta.format = if meta.blue_offset is 0 then 'bgr' else 'rgb'
84 | meta.format += 'a' if meta.bpp is 32 or meta.alpha_length
85 | return meta
86 |
87 | module.exports = FrameBufferCommand
88 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/getfeatures.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class GetFeaturesCommand extends Command
5 | RE_FEATURE = /^feature:(.*?)(?:=(.*?))?\r?$/gm
6 |
7 | execute: ->
8 | this._send 'shell:pm list features 2>/dev/null'
9 | @parser.readAscii 4
10 | .then (reply) =>
11 | switch reply
12 | when Protocol.OKAY
13 | @parser.readAll()
14 | .then (data) =>
15 | this._parseFeatures data.toString()
16 | when Protocol.FAIL
17 | @parser.readError()
18 | else
19 | @parser.unexpected reply, 'OKAY or FAIL'
20 |
21 | _parseFeatures: (value) ->
22 | features = {}
23 | while match = RE_FEATURE.exec value
24 | features[match[1]] = match[2] or true
25 | return features
26 |
27 | module.exports = GetFeaturesCommand
28 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/getpackages.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class GetPackagesCommand extends Command
5 | RE_PACKAGE = /^package:(.*?)\r?$/gm
6 |
7 | execute: ->
8 | this._send 'shell:pm list packages 2>/dev/null'
9 | @parser.readAscii 4
10 | .then (reply) =>
11 | switch reply
12 | when Protocol.OKAY
13 | @parser.readAll()
14 | .then (data) =>
15 | this._parsePackages data.toString()
16 | when Protocol.FAIL
17 | @parser.readError()
18 | else
19 | @parser.unexpected reply, 'OKAY or FAIL'
20 |
21 | _parsePackages: (value) ->
22 | features = []
23 | while match = RE_PACKAGE.exec value
24 | features.push match[1]
25 | return features
26 |
27 | module.exports = GetPackagesCommand
28 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/getproperties.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class GetPropertiesCommand extends Command
5 | RE_KEYVAL = /^\[([\s\S]*?)\]: \[([\s\S]*?)\]\r?$/gm
6 |
7 | execute: ->
8 | this._send 'shell:getprop'
9 | @parser.readAscii 4
10 | .then (reply) =>
11 | switch reply
12 | when Protocol.OKAY
13 | @parser.readAll()
14 | .then (data) =>
15 | this._parseProperties data.toString()
16 | when Protocol.FAIL
17 | @parser.readError()
18 | else
19 | @parser.unexpected reply, 'OKAY or FAIL'
20 |
21 | _parseProperties: (value) ->
22 | properties = {}
23 | while match = RE_KEYVAL.exec value
24 | properties[match[1]] = match[2]
25 | return properties
26 |
27 | module.exports = GetPropertiesCommand
28 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/install.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class InstallCommand extends Command
5 | execute: (apk) ->
6 | this._send "shell:pm install -r #{this._escapeCompat(apk)}"
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | @parser.searchLine /^(Success|Failure \[(.*?)\])$/
12 | .then (match) ->
13 | if match[1] is 'Success'
14 | true
15 | else
16 | code = match[2]
17 | err = new Error "#{apk} could not be installed [#{code}]"
18 | err.code = code
19 | throw err
20 | .finally =>
21 | # Consume all remaining content to "naturally" close the
22 | # connection.
23 | @parser.readAll()
24 | when Protocol.FAIL
25 | @parser.readError()
26 | else
27 | @parser.unexpected reply, 'OKAY or FAIL'
28 |
29 | module.exports = InstallCommand
30 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/isinstalled.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 | Parser = require '../../parser'
4 |
5 | class IsInstalledCommand extends Command
6 | execute: (pkg) ->
7 | this._send "shell:pm path #{pkg} 2>/dev/null"
8 | @parser.readAscii 4
9 | .then (reply) =>
10 | switch reply
11 | when Protocol.OKAY
12 | @parser.readAscii 8
13 | .then (reply) =>
14 | switch reply
15 | when 'package:'
16 | true
17 | else
18 | @parser.unexpected reply, "'package:'"
19 | .catch Parser.PrematureEOFError, (err) ->
20 | false
21 | when Protocol.FAIL
22 | @parser.readError()
23 | else
24 | @parser.unexpected reply, 'OKAY or FAIL'
25 |
26 | module.exports = IsInstalledCommand
27 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/listreverses.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class ListReversesCommand extends Command
5 | execute: ->
6 | this._send "reverse:list-forward"
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | @parser.readValue()
12 | .then (value) =>
13 | this._parseReverses value
14 | when Protocol.FAIL
15 | @parser.readError()
16 | else
17 | @parser.unexpected reply, 'OKAY or FAIL'
18 |
19 | _parseReverses: (value) ->
20 | reverses = []
21 | for reverse in value.toString().split '\n'
22 | if reverse
23 | [serial, remote, local] = reverse.split /\s+/
24 | reverses.push remote: remote, local: local
25 | return reverses
26 |
27 | module.exports = ListReversesCommand
28 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/local.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class LocalCommand extends Command
5 | execute: (path) ->
6 | this._send if /:/.test(path) then path else "localfilesystem:#{path}"
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | @parser.raw()
12 | when Protocol.FAIL
13 | @parser.readError()
14 | else
15 | @parser.unexpected reply, 'OKAY or FAIL'
16 |
17 | module.exports = LocalCommand
18 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/log.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class LogCommand extends Command
5 | execute: (name) ->
6 | this._send "log:#{name}"
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | @parser.raw()
12 | when Protocol.FAIL
13 | @parser.readError()
14 | else
15 | @parser.unexpected reply, 'OKAY or FAIL'
16 |
17 | module.exports = LogCommand
18 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/logcat.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 | LineTransform = require '../../linetransform'
4 |
5 | class LogcatCommand extends Command
6 | execute: (options = {}) ->
7 | # For some reason, LG G Flex requires a filter spec with the -B option.
8 | # It doesn't actually use it, though. Regardless of the spec we always get
9 | # all events on all devices.
10 | cmd = 'logcat -B *:I 2>/dev/null'
11 | cmd = "logcat -c 2>/dev/null && #{cmd}" if options.clear
12 | this._send "shell:echo && #{cmd}"
13 | @parser.readAscii 4
14 | .then (reply) =>
15 | switch reply
16 | when Protocol.OKAY
17 | @parser.raw().pipe new LineTransform autoDetect: true
18 | when Protocol.FAIL
19 | @parser.readError()
20 | else
21 | @parser.unexpected reply, 'OKAY or FAIL'
22 |
23 | module.exports = LogcatCommand
24 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/monkey.coffee:
--------------------------------------------------------------------------------
1 | Promise = require 'bluebird'
2 |
3 | Command = require '../../command'
4 | Protocol = require '../../protocol'
5 |
6 | class MonkeyCommand extends Command
7 | execute: (port) ->
8 | # Some devices have broken /sdcard (i.e. /mnt/sdcard), which monkey will
9 | # attempt to use to write log files to. We can cheat and set the location
10 | # with an environment variable, because most logs use
11 | # Environment.getLegacyExternalStorageDirectory() like they should. There
12 | # are some hardcoded logs, though. Anyway, this should enable most things.
13 | # Check https://github.com/android/platform_frameworks_base/blob/master/
14 | # core/java/android/os/Environment.java for the variables.
15 | this._send "shell:EXTERNAL_STORAGE=/data/local/tmp monkey --port #{port} -v"
16 |
17 | @parser.readAscii 4
18 | .then (reply) =>
19 | switch reply
20 | when Protocol.OKAY
21 | # The monkey command is a bit weird in that it doesn't look like
22 | # it starts in daemon mode, but it actually does. So even though
23 | # the command leaves the terminal "hanging", Ctrl-C (or just
24 | # ending the connection) will not end the daemon. HOWEVER, on
25 | # some devices, such as SO-02C by Sony, it is required to leave
26 | # the command hanging around. In any case, if the command exits
27 | # by itself, it means that something went wrong.
28 | @parser.searchLine /^:Monkey:/
29 | # On some devices (such as F-08D by Fujitsu), the monkey
30 | # command gives no output no matter how many verbose flags you
31 | # give it. So we use a fallback timeout.
32 | .timeout 1000
33 | .then =>
34 | @parser.raw()
35 | .catch Promise.TimeoutError, (err) =>
36 | @parser.raw()
37 | when Protocol.FAIL
38 | @parser.readError()
39 | else
40 | @parser.unexpected reply, 'OKAY or FAIL'
41 |
42 | module.exports = MonkeyCommand
43 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/reboot.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class RebootCommand extends Command
5 | execute: ->
6 | this._send 'reboot:'
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | @parser.readAll()
12 | .return true
13 | when Protocol.FAIL
14 | @parser.readError()
15 | else
16 | @parser.unexpected reply, 'OKAY or FAIL'
17 |
18 | module.exports = RebootCommand
19 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/remount.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class RemountCommand extends Command
5 | execute: ->
6 | this._send 'remount:'
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | true
12 | when Protocol.FAIL
13 | @parser.readError()
14 | else
15 | @parser.unexpected reply, 'OKAY or FAIL'
16 |
17 | module.exports = RemountCommand
18 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/reverse.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class ReverseCommand extends Command
5 | execute: (remote, local) ->
6 | this._send "reverse:forward:#{remote};#{local}"
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | @parser.readAscii 4
12 | .then (reply) =>
13 | switch reply
14 | when Protocol.OKAY
15 | true
16 | when Protocol.FAIL
17 | @parser.readError()
18 | else
19 | @parser.unexpected reply, 'OKAY or FAIL'
20 | when Protocol.FAIL
21 | @parser.readError()
22 | else
23 | @parser.unexpected reply, 'OKAY or FAIL'
24 |
25 | module.exports = ReverseCommand
26 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/root.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class RootCommand extends Command
5 | RE_OK = /restarting adbd as root/
6 |
7 | execute: ->
8 | this._send 'root:'
9 | @parser.readAscii 4
10 | .then (reply) =>
11 | switch reply
12 | when Protocol.OKAY
13 | @parser.readAll()
14 | .then (value) ->
15 | if RE_OK.test(value)
16 | true
17 | else
18 | throw new Error value.toString().trim()
19 | when Protocol.FAIL
20 | @parser.readError()
21 | else
22 | @parser.unexpected reply, 'OKAY or FAIL'
23 |
24 | module.exports = RootCommand
25 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/screencap.coffee:
--------------------------------------------------------------------------------
1 | Promise = require 'bluebird'
2 |
3 | Command = require '../../command'
4 | Protocol = require '../../protocol'
5 | Parser = require '../../parser'
6 | LineTransform = require '../../linetransform'
7 |
8 | class ScreencapCommand extends Command
9 | execute: ->
10 | this._send 'shell:echo && screencap -p 2>/dev/null'
11 | @parser.readAscii 4
12 | .then (reply) =>
13 | switch reply
14 | when Protocol.OKAY
15 | transform = new LineTransform
16 | @parser.readBytes 1
17 | .then (chunk) =>
18 | transform = new LineTransform autoDetect: true
19 | transform.write chunk
20 | @parser.raw().pipe transform
21 | .catch Parser.PrematureEOFError, ->
22 | throw new Error 'No support for the screencap command'
23 | when Protocol.FAIL
24 | @parser.readError()
25 | else
26 | @parser.unexpected reply, 'OKAY or FAIL'
27 |
28 | module.exports = ScreencapCommand
29 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/shell.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class ShellCommand extends Command
5 | execute: (command) ->
6 | if Array.isArray command
7 | command = command.map(this._escape).join ' '
8 | this._send "shell:#{command}"
9 | @parser.readAscii 4
10 | .then (reply) =>
11 | switch reply
12 | when Protocol.OKAY
13 | @parser.raw()
14 | when Protocol.FAIL
15 | @parser.readError()
16 | else
17 | @parser.unexpected reply, 'OKAY or FAIL'
18 |
19 | module.exports = ShellCommand
20 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/startactivity.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 | Parser = require '../../parser'
4 |
5 | class StartActivityCommand extends Command
6 | RE_ERROR = /^Error: (.*)$/
7 | EXTRA_TYPES =
8 | string: 's'
9 | null: 'sn'
10 | bool: 'z'
11 | int: 'i'
12 | long: 'l'
13 | float: 'l'
14 | uri: 'u'
15 | component: 'cn'
16 |
17 | execute: (options) ->
18 | args = this._intentArgs options
19 | if options.debug
20 | args.push '-D'
21 | if options.wait
22 | args.push '-W'
23 | if options.user or options.user is 0
24 | args.push '--user', this._escape options.user
25 | this._run 'start', args
26 |
27 | _run: (command, args) ->
28 | this._send "shell:am #{command} #{args.join ' '}"
29 | @parser.readAscii 4
30 | .then (reply) =>
31 | switch reply
32 | when Protocol.OKAY
33 | @parser.searchLine RE_ERROR
34 | .finally =>
35 | @parser.end()
36 | .then (match) ->
37 | throw new Error match[1]
38 | .catch Parser.PrematureEOFError, (err) ->
39 | true
40 | when Protocol.FAIL
41 | @parser.readError()
42 | else
43 | @parser.unexpected reply, 'OKAY or FAIL'
44 |
45 | _intentArgs: (options) ->
46 | args = []
47 | if options.extras
48 | args.push.apply args, this._formatExtras options.extras
49 | if options.action
50 | args.push '-a', this._escape options.action
51 | if options.data
52 | args.push '-d', this._escape options.data
53 | if options.mimeType
54 | args.push '-t', this._escape options.mimeType
55 | if options.category
56 | if Array.isArray options.category
57 | options.category.forEach (category) =>
58 | args.push '-c', this._escape category
59 | else
60 | args.push '-c', this._escape options.category
61 | if options.component
62 | args.push '-n', this._escape options.component
63 | if options.flags
64 | args.push '-f', this._escape options.flags
65 | return args
66 |
67 | _formatExtras: (extras) ->
68 | return [] unless extras
69 | if Array.isArray extras
70 | extras.reduce (all, extra) =>
71 | all.concat this._formatLongExtra extra
72 | , []
73 | else
74 | Object.keys(extras).reduce (all, key) =>
75 | all.concat this._formatShortExtra key, extras[key]
76 | , []
77 |
78 | _formatShortExtra: (key, value) ->
79 | sugared =
80 | key: key
81 | if value is null
82 | sugared.type = 'null'
83 | else if Array.isArray value
84 | throw new Error "Refusing to format array value '#{key}' using short
85 | syntax; empty array would cause unpredictable results due to unknown
86 | type. Please use long syntax instead."
87 | else
88 | switch typeof value
89 | when 'string'
90 | sugared.type = 'string'
91 | sugared.value = value
92 | when 'boolean'
93 | sugared.type = 'bool'
94 | sugared.value = value
95 | when 'number'
96 | sugared.type = 'int'
97 | sugared.value = value
98 | when 'object'
99 | sugared = value
100 | sugared.key = key
101 | return this._formatLongExtra sugared
102 |
103 | _formatLongExtra: (extra) ->
104 | args = []
105 | extra.type = 'string' unless extra.type
106 | type = EXTRA_TYPES[extra.type]
107 | unless type
108 | throw new Error "Unsupported type '#{extra.type}' for extra
109 | '#{extra.key}'"
110 | if extra.type is 'null'
111 | args.push "--e#{type}"
112 | args.push this._escape extra.key
113 | else if Array.isArray extra.value
114 | args.push "--e#{type}a"
115 | args.push this._escape extra.key
116 | args.push this._escape extra.value.join ','
117 | else
118 | args.push "--e#{type}"
119 | args.push this._escape extra.key
120 | args.push this._escape extra.value
121 | return args
122 |
123 | module.exports = StartActivityCommand
124 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/startservice.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 | Parser = require '../../parser'
4 |
5 | StartActivityCommand = require './startactivity'
6 |
7 | class StartServiceCommand extends StartActivityCommand
8 | execute: (options) ->
9 | args = this._intentArgs options
10 | if options.user or options.user is 0
11 | args.push '--user', this._escape options.user
12 | this._run 'startservice', args
13 |
14 | module.exports = StartServiceCommand
15 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/sync.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 | Sync = require '../../sync'
4 |
5 | class SyncCommand extends Command
6 | execute: ->
7 | this._send 'sync:'
8 | @parser.readAscii 4
9 | .then (reply) =>
10 | switch reply
11 | when Protocol.OKAY
12 | new Sync @connection
13 | when Protocol.FAIL
14 | @parser.readError()
15 | else
16 | @parser.unexpected reply, 'OKAY or FAIL'
17 |
18 | module.exports = SyncCommand
19 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/tcp.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class TcpCommand extends Command
5 | execute: (port, host) ->
6 | this._send "tcp:#{port}" + if host then ":#{host}" else ''
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | @parser.raw()
12 | when Protocol.FAIL
13 | @parser.readError()
14 | else
15 | @parser.unexpected reply, 'OKAY or FAIL'
16 |
17 | module.exports = TcpCommand
18 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/tcpip.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class TcpIpCommand extends Command
5 | RE_OK = /restarting in/
6 |
7 | execute: (port) ->
8 | this._send "tcpip:#{port}"
9 | @parser.readAscii 4
10 | .then (reply) =>
11 | switch reply
12 | when Protocol.OKAY
13 | @parser.readAll()
14 | .then (value) ->
15 | if RE_OK.test(value)
16 | port
17 | else
18 | throw new Error value.toString().trim()
19 | when Protocol.FAIL
20 | @parser.readError()
21 | else
22 | @parser.unexpected reply, 'OKAY or FAIL'
23 |
24 | module.exports = TcpIpCommand
25 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/trackjdwp.coffee:
--------------------------------------------------------------------------------
1 | {EventEmitter} = require 'events'
2 |
3 | Promise = require 'bluebird'
4 |
5 | Command = require '../../command'
6 | Protocol = require '../../protocol'
7 | Parser = require '../../parser'
8 |
9 | class TrackJdwpCommand extends Command
10 | execute: ->
11 | this._send 'track-jdwp'
12 | @parser.readAscii 4
13 | .then (reply) =>
14 | switch reply
15 | when Protocol.OKAY
16 | new Tracker this
17 | when Protocol.FAIL
18 | @parser.readError()
19 | else
20 | @parser.unexpected reply, 'OKAY or FAIL'
21 |
22 | class Tracker extends EventEmitter
23 | constructor: (@command) ->
24 | @pids = []
25 | @pidMap = Object.create null
26 | @reader = this.read()
27 | .catch Parser.PrematureEOFError, (err) =>
28 | this.emit 'end'
29 | .catch Promise.CancellationError, (err) =>
30 | @command.connection.end()
31 | this.emit 'end'
32 | .catch (err) =>
33 | @command.connection.end()
34 | this.emit 'error', err
35 | this.emit 'end'
36 |
37 | read: ->
38 | @command.parser.readValue()
39 | .cancellable()
40 | .then (list) =>
41 | pids = list.toString().split '\n'
42 | pids.push maybeEmpty if maybeEmpty = pids.pop()
43 | this.update pids
44 |
45 | update: (newList) ->
46 | changeSet =
47 | removed: []
48 | added: []
49 | newMap = Object.create null
50 | for pid in newList
51 | unless @pidMap[pid]
52 | changeSet.added.push pid
53 | this.emit 'add', pid
54 | newMap[pid] = pid
55 | for pid in @pids
56 | unless newMap[pid]
57 | changeSet.removed.push pid
58 | this.emit 'remove', pid
59 | @pids = newList
60 | @pidMap = newMap
61 | this.emit 'changeSet', changeSet, newList
62 | return this
63 |
64 | end: ->
65 | @reader.cancel()
66 | return this
67 |
68 | module.exports = TrackJdwpCommand
69 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/uninstall.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class UninstallCommand extends Command
5 | execute: (pkg) ->
6 | this._send "shell:pm uninstall #{pkg}"
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | @parser.searchLine /^(Success|Failure.*|.*Unknown package:.*)$/
12 | .then (match) ->
13 | if match[1] is 'Success'
14 | true
15 | else
16 | # Either way, the package was uninstalled or doesn't exist,
17 | # which is good enough for us.
18 | true
19 | .finally =>
20 | # Consume all remaining content to "naturally" close the
21 | # connection.
22 | @parser.readAll()
23 | when Protocol.FAIL
24 | @parser.readError()
25 | else
26 | @parser.unexpected reply, "OKAY or FAIL"
27 |
28 | module.exports = UninstallCommand
29 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/usb.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class UsbCommand extends Command
5 | RE_OK = /restarting in/
6 |
7 | execute: ->
8 | this._send 'usb:'
9 | @parser.readAscii 4
10 | .then (reply) =>
11 | switch reply
12 | when Protocol.OKAY
13 | @parser.readAll()
14 | .then (value) ->
15 | if RE_OK.test(value)
16 | true
17 | else
18 | throw new Error value.toString().trim()
19 | when Protocol.FAIL
20 | @parser.readError()
21 | else
22 | @parser.unexpected reply, 'OKAY or FAIL'
23 |
24 | module.exports = UsbCommand
25 |
--------------------------------------------------------------------------------
/src/adb/command/host-transport/waitbootcomplete.coffee:
--------------------------------------------------------------------------------
1 | debug = require('debug')('adb:command:waitboot')
2 |
3 | Command = require '../../command'
4 | Protocol = require '../../protocol'
5 |
6 | class WaitBootCompleteCommand extends Command
7 | execute: ->
8 | this._send \
9 | 'shell:while getprop sys.boot_completed 2>/dev/null; do sleep 1; done'
10 | @parser.readAscii 4
11 | .then (reply) =>
12 | switch reply
13 | when Protocol.OKAY
14 | @parser.searchLine /^1$/
15 | .finally =>
16 | @parser.end()
17 | .then ->
18 | true
19 | when Protocol.FAIL
20 | @parser.readError()
21 | else
22 | @parser.unexpected reply, 'OKAY or FAIL'
23 |
24 | module.exports = WaitBootCompleteCommand
25 |
--------------------------------------------------------------------------------
/src/adb/command/host/connect.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class ConnectCommand extends Command
5 | # Possible replies:
6 | # "unable to connect to 192.168.2.2:5555"
7 | # "connected to 192.168.2.2:5555"
8 | # "already connected to 192.168.2.2:5555"
9 | RE_OK = /connected to|already connected/
10 |
11 | execute: (host, port) ->
12 | this._send "host:connect:#{host}:#{port}"
13 | @parser.readAscii 4
14 | .then (reply) =>
15 | switch reply
16 | when Protocol.OKAY
17 | @parser.readValue()
18 | .then (value) ->
19 | if RE_OK.test value
20 | "#{host}:#{port}"
21 | else
22 | throw new Error value.toString()
23 | when Protocol.FAIL
24 | @parser.readError()
25 | else
26 | @parser.unexpected reply, 'OKAY or FAIL'
27 |
28 | module.exports = ConnectCommand
29 |
--------------------------------------------------------------------------------
/src/adb/command/host/devices.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class HostDevicesCommand extends Command
5 | execute: ->
6 | this._send 'host:devices'
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | this._readDevices()
12 | when Protocol.FAIL
13 | @parser.readError()
14 | else
15 | @parser.unexpected reply, 'OKAY or FAIL'
16 |
17 | _readDevices: ->
18 | @parser.readValue()
19 | .then (value) =>
20 | this._parseDevices value
21 |
22 | _parseDevices: (value) ->
23 | devices = []
24 | return devices unless value.length
25 | for line in value.toString('ascii').split '\n'
26 | if line
27 | [id, type] = line.split '\t'
28 | devices.push id: id, type: type
29 | return devices
30 |
31 | module.exports = HostDevicesCommand
32 |
--------------------------------------------------------------------------------
/src/adb/command/host/deviceswithpaths.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class HostDevicesWithPathsCommand extends Command
5 | execute: ->
6 | this._send 'host:devices-l'
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | this._readDevices()
12 | when Protocol.FAIL
13 | @parser.readError()
14 | else
15 | @parser.unexpected reply, 'OKAY or FAIL'
16 |
17 | _readDevices: ->
18 | @parser.readValue()
19 | .then (value) =>
20 | this._parseDevices value
21 |
22 | _parseDevices: (value) ->
23 | devices = []
24 | return devices unless value.length
25 | for line in value.toString('ascii').split '\n'
26 | if line
27 | # For some reason, the columns are separated by spaces instead of tabs
28 | [id, type, path] = line.split /\s+/
29 | devices.push id: id, type: type, path: path
30 | return devices
31 |
32 | module.exports = HostDevicesWithPathsCommand
33 |
--------------------------------------------------------------------------------
/src/adb/command/host/disconnect.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class DisconnectCommand extends Command
5 | # Possible replies:
6 | # "No such device 192.168.2.2:5555"
7 | # ""
8 | RE_OK = /^$/
9 |
10 | execute: (host, port) ->
11 | this._send "host:disconnect:#{host}:#{port}"
12 | @parser.readAscii 4
13 | .then (reply) =>
14 | switch reply
15 | when Protocol.OKAY
16 | @parser.readValue()
17 | .then (value) ->
18 | if RE_OK.test value
19 | "#{host}:#{port}"
20 | else
21 | throw new Error value.toString()
22 | when Protocol.FAIL
23 | @parser.readError()
24 | else
25 | @parser.unexpected reply, 'OKAY or FAIL'
26 |
27 | module.exports = DisconnectCommand
28 |
--------------------------------------------------------------------------------
/src/adb/command/host/kill.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class HostKillCommand extends Command
5 | execute: ->
6 | this._send 'host:kill'
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | true
12 | when Protocol.FAIL
13 | @parser.readError()
14 | else
15 | @parser.unexpected reply, 'OKAY or FAIL'
16 |
17 | module.exports = HostKillCommand
18 |
--------------------------------------------------------------------------------
/src/adb/command/host/trackdevices.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 | Tracker = require '../../tracker'
4 | HostDevicesCommand = require './devices'
5 |
6 | class HostTrackDevicesCommand extends HostDevicesCommand
7 | execute: ->
8 | this._send 'host:track-devices'
9 | @parser.readAscii 4
10 | .then (reply) =>
11 | switch reply
12 | when Protocol.OKAY
13 | new Tracker this
14 | when Protocol.FAIL
15 | @parser.readError()
16 | else
17 | @parser.unexpected reply, 'OKAY or FAIL'
18 |
19 | module.exports = HostTrackDevicesCommand
20 |
--------------------------------------------------------------------------------
/src/adb/command/host/transport.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class HostTransportCommand extends Command
5 | execute: (serial) ->
6 | this._send "host:transport:#{serial}"
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | true
12 | when Protocol.FAIL
13 | @parser.readError()
14 | else
15 | @parser.unexpected reply, 'OKAY or FAIL'
16 |
17 | module.exports = HostTransportCommand
18 |
--------------------------------------------------------------------------------
/src/adb/command/host/version.coffee:
--------------------------------------------------------------------------------
1 | Command = require '../../command'
2 | Protocol = require '../../protocol'
3 |
4 | class HostVersionCommand extends Command
5 | execute: ->
6 | this._send 'host:version'
7 | @parser.readAscii 4
8 | .then (reply) =>
9 | switch reply
10 | when Protocol.OKAY
11 | @parser.readValue()
12 | .then (value) =>
13 | this._parseVersion value
14 | when Protocol.FAIL
15 | @parser.readError()
16 | else
17 | this._parseVersion reply
18 |
19 | _parseVersion: (version) ->
20 | parseInt version, 16
21 |
22 | module.exports = HostVersionCommand
23 |
--------------------------------------------------------------------------------
/src/adb/connection.coffee:
--------------------------------------------------------------------------------
1 | Net = require 'net'
2 | debug = require('debug')('adb:connection')
3 | {EventEmitter} = require 'events'
4 | {execFile} = require 'child_process'
5 |
6 | Parser = require './parser'
7 | dump = require './dump'
8 |
9 | class Connection extends EventEmitter
10 | constructor: (@options) ->
11 | @socket = null
12 | @parser = null
13 | @triedStarting = false
14 |
15 | connect: ->
16 | @socket = Net.connect @options
17 | @socket.setNoDelay true
18 | @parser = new Parser @socket
19 | @socket.on 'connect', =>
20 | this.emit 'connect'
21 | @socket.on 'end', =>
22 | this.emit 'end'
23 | @socket.on 'drain', =>
24 | this.emit 'drain'
25 | @socket.on 'timeout', =>
26 | this.emit 'timeout'
27 | @socket.on 'error', (err) =>
28 | this._handleError err
29 | @socket.on 'close', (hadError) =>
30 | this.emit 'close', hadError
31 | return this
32 |
33 | end: ->
34 | @socket.end()
35 | return this
36 |
37 | write: (data, callback) ->
38 | @socket.write dump(data), callback
39 | return this
40 |
41 | startServer: (callback) ->
42 | debug "Starting ADB server via '#{@options.bin} start-server'"
43 | return this._exec ['start-server'], {}, callback
44 |
45 | _exec: (args, options, callback) ->
46 | debug "CLI: #{@options.bin} #{args.join ' '}"
47 | execFile @options.bin, args, options, callback
48 | return this
49 |
50 | _handleError: (err) ->
51 | if err.code is 'ECONNREFUSED' and not @triedStarting
52 | debug "Connection was refused, let's try starting the server once"
53 | @triedStarting = true
54 | this.startServer (err) =>
55 | return this._handleError err if err
56 | this.connect()
57 | else
58 | debug "Connection had an error: #{err.message}"
59 | this.emit 'error', err
60 | this.end()
61 | return
62 |
63 | module.exports = Connection
64 |
--------------------------------------------------------------------------------
/src/adb/dump.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 |
3 | if process.env.ADBKIT_DUMP
4 | out = fs.createWriteStream 'adbkit.dump'
5 | module.exports = (chunk) ->
6 | out.write chunk
7 | chunk
8 | else
9 | module.exports = (chunk) -> chunk
10 |
--------------------------------------------------------------------------------
/src/adb/framebuffer/rgbtransform.coffee:
--------------------------------------------------------------------------------
1 | Assert = require 'assert'
2 | Stream = require 'stream'
3 |
4 | class RgbTransform extends Stream.Transform
5 | constructor: (@meta, options) ->
6 | @_buffer = new Buffer ''
7 | Assert.ok (@meta.bpp is 24 or @meta.bpp is 32),
8 | 'Only 24-bit and 32-bit raw images with 8-bits per color are supported'
9 | @_r_pos = @meta.red_offset / 8
10 | @_g_pos = @meta.green_offset / 8
11 | @_b_pos = @meta.blue_offset / 8
12 | @_a_pos = @meta.alpha_offset / 8
13 | @_pixel_bytes = @meta.bpp / 8
14 | super options
15 |
16 | _transform: (chunk, encoding, done) ->
17 | if @_buffer.length
18 | @_buffer = Buffer.concat [@_buffer, chunk], @_buffer.length + chunk.length
19 | else
20 | @_buffer = chunk
21 | sourceCursor = 0
22 | targetCursor = 0
23 | target = if @_pixel_bytes is 3 \
24 | then @_buffer
25 | else new Buffer Math.max 4, chunk.length / @_pixel_bytes * 3
26 | while @_buffer.length - sourceCursor >= @_pixel_bytes
27 | r = @_buffer[sourceCursor + @_r_pos]
28 | g = @_buffer[sourceCursor + @_g_pos]
29 | b = @_buffer[sourceCursor + @_b_pos]
30 | target[targetCursor + 0] = r
31 | target[targetCursor + 1] = g
32 | target[targetCursor + 2] = b
33 | sourceCursor += @_pixel_bytes
34 | targetCursor += 3
35 | if targetCursor
36 | this.push target.slice 0, targetCursor
37 | @_buffer = @_buffer.slice sourceCursor
38 | done()
39 | return
40 |
41 | module.exports = RgbTransform
42 |
--------------------------------------------------------------------------------
/src/adb/keycode.coffee:
--------------------------------------------------------------------------------
1 | # Generated by `grunt keycode` on Tue, 26 Nov 2013 08:02:49 GMT
2 | # KeyEvent.java Copyright (C) 2006 The Android Open Source Project
3 |
4 | module.exports =
5 | KEYCODE_UNKNOWN: 0
6 | KEYCODE_SOFT_LEFT: 1
7 | KEYCODE_SOFT_RIGHT: 2
8 | KEYCODE_HOME: 3
9 | KEYCODE_BACK: 4
10 | KEYCODE_CALL: 5
11 | KEYCODE_ENDCALL: 6
12 | KEYCODE_0: 7
13 | KEYCODE_1: 8
14 | KEYCODE_2: 9
15 | KEYCODE_3: 10
16 | KEYCODE_4: 11
17 | KEYCODE_5: 12
18 | KEYCODE_6: 13
19 | KEYCODE_7: 14
20 | KEYCODE_8: 15
21 | KEYCODE_9: 16
22 | KEYCODE_STAR: 17
23 | KEYCODE_POUND: 18
24 | KEYCODE_DPAD_UP: 19
25 | KEYCODE_DPAD_DOWN: 20
26 | KEYCODE_DPAD_LEFT: 21
27 | KEYCODE_DPAD_RIGHT: 22
28 | KEYCODE_DPAD_CENTER: 23
29 | KEYCODE_VOLUME_UP: 24
30 | KEYCODE_VOLUME_DOWN: 25
31 | KEYCODE_POWER: 26
32 | KEYCODE_CAMERA: 27
33 | KEYCODE_CLEAR: 28
34 | KEYCODE_A: 29
35 | KEYCODE_B: 30
36 | KEYCODE_C: 31
37 | KEYCODE_D: 32
38 | KEYCODE_E: 33
39 | KEYCODE_F: 34
40 | KEYCODE_G: 35
41 | KEYCODE_H: 36
42 | KEYCODE_I: 37
43 | KEYCODE_J: 38
44 | KEYCODE_K: 39
45 | KEYCODE_L: 40
46 | KEYCODE_M: 41
47 | KEYCODE_N: 42
48 | KEYCODE_O: 43
49 | KEYCODE_P: 44
50 | KEYCODE_Q: 45
51 | KEYCODE_R: 46
52 | KEYCODE_S: 47
53 | KEYCODE_T: 48
54 | KEYCODE_U: 49
55 | KEYCODE_V: 50
56 | KEYCODE_W: 51
57 | KEYCODE_X: 52
58 | KEYCODE_Y: 53
59 | KEYCODE_Z: 54
60 | KEYCODE_COMMA: 55
61 | KEYCODE_PERIOD: 56
62 | KEYCODE_ALT_LEFT: 57
63 | KEYCODE_ALT_RIGHT: 58
64 | KEYCODE_SHIFT_LEFT: 59
65 | KEYCODE_SHIFT_RIGHT: 60
66 | KEYCODE_TAB: 61
67 | KEYCODE_SPACE: 62
68 | KEYCODE_SYM: 63
69 | KEYCODE_EXPLORER: 64
70 | KEYCODE_ENVELOPE: 65
71 | KEYCODE_ENTER: 66
72 | KEYCODE_DEL: 67
73 | KEYCODE_GRAVE: 68
74 | KEYCODE_MINUS: 69
75 | KEYCODE_EQUALS: 70
76 | KEYCODE_LEFT_BRACKET: 71
77 | KEYCODE_RIGHT_BRACKET: 72
78 | KEYCODE_BACKSLASH: 73
79 | KEYCODE_SEMICOLON: 74
80 | KEYCODE_APOSTROPHE: 75
81 | KEYCODE_SLASH: 76
82 | KEYCODE_AT: 77
83 | KEYCODE_NUM: 78
84 | KEYCODE_HEADSETHOOK: 79
85 | KEYCODE_FOCUS: 80
86 | KEYCODE_PLUS: 81
87 | KEYCODE_MENU: 82
88 | KEYCODE_NOTIFICATION: 83
89 | KEYCODE_SEARCH: 84
90 | KEYCODE_MEDIA_PLAY_PAUSE: 85
91 | KEYCODE_MEDIA_STOP: 86
92 | KEYCODE_MEDIA_NEXT: 87
93 | KEYCODE_MEDIA_PREVIOUS: 88
94 | KEYCODE_MEDIA_REWIND: 89
95 | KEYCODE_MEDIA_FAST_FORWARD: 90
96 | KEYCODE_MUTE: 91
97 | KEYCODE_PAGE_UP: 92
98 | KEYCODE_PAGE_DOWN: 93
99 | KEYCODE_PICTSYMBOLS: 94
100 | KEYCODE_SWITCH_CHARSET: 95
101 | KEYCODE_BUTTON_A: 96
102 | KEYCODE_BUTTON_B: 97
103 | KEYCODE_BUTTON_C: 98
104 | KEYCODE_BUTTON_X: 99
105 | KEYCODE_BUTTON_Y: 100
106 | KEYCODE_BUTTON_Z: 101
107 | KEYCODE_BUTTON_L1: 102
108 | KEYCODE_BUTTON_R1: 103
109 | KEYCODE_BUTTON_L2: 104
110 | KEYCODE_BUTTON_R2: 105
111 | KEYCODE_BUTTON_THUMBL: 106
112 | KEYCODE_BUTTON_THUMBR: 107
113 | KEYCODE_BUTTON_START: 108
114 | KEYCODE_BUTTON_SELECT: 109
115 | KEYCODE_BUTTON_MODE: 110
116 | KEYCODE_ESCAPE: 111
117 | KEYCODE_FORWARD_DEL: 112
118 | KEYCODE_CTRL_LEFT: 113
119 | KEYCODE_CTRL_RIGHT: 114
120 | KEYCODE_CAPS_LOCK: 115
121 | KEYCODE_SCROLL_LOCK: 116
122 | KEYCODE_META_LEFT: 117
123 | KEYCODE_META_RIGHT: 118
124 | KEYCODE_FUNCTION: 119
125 | KEYCODE_SYSRQ: 120
126 | KEYCODE_BREAK: 121
127 | KEYCODE_MOVE_HOME: 122
128 | KEYCODE_MOVE_END: 123
129 | KEYCODE_INSERT: 124
130 | KEYCODE_FORWARD: 125
131 | KEYCODE_MEDIA_PLAY: 126
132 | KEYCODE_MEDIA_PAUSE: 127
133 | KEYCODE_MEDIA_CLOSE: 128
134 | KEYCODE_MEDIA_EJECT: 129
135 | KEYCODE_MEDIA_RECORD: 130
136 | KEYCODE_F1: 131
137 | KEYCODE_F2: 132
138 | KEYCODE_F3: 133
139 | KEYCODE_F4: 134
140 | KEYCODE_F5: 135
141 | KEYCODE_F6: 136
142 | KEYCODE_F7: 137
143 | KEYCODE_F8: 138
144 | KEYCODE_F9: 139
145 | KEYCODE_F10: 140
146 | KEYCODE_F11: 141
147 | KEYCODE_F12: 142
148 | KEYCODE_NUM_LOCK: 143
149 | KEYCODE_NUMPAD_0: 144
150 | KEYCODE_NUMPAD_1: 145
151 | KEYCODE_NUMPAD_2: 146
152 | KEYCODE_NUMPAD_3: 147
153 | KEYCODE_NUMPAD_4: 148
154 | KEYCODE_NUMPAD_5: 149
155 | KEYCODE_NUMPAD_6: 150
156 | KEYCODE_NUMPAD_7: 151
157 | KEYCODE_NUMPAD_8: 152
158 | KEYCODE_NUMPAD_9: 153
159 | KEYCODE_NUMPAD_DIVIDE: 154
160 | KEYCODE_NUMPAD_MULTIPLY: 155
161 | KEYCODE_NUMPAD_SUBTRACT: 156
162 | KEYCODE_NUMPAD_ADD: 157
163 | KEYCODE_NUMPAD_DOT: 158
164 | KEYCODE_NUMPAD_COMMA: 159
165 | KEYCODE_NUMPAD_ENTER: 160
166 | KEYCODE_NUMPAD_EQUALS: 161
167 | KEYCODE_NUMPAD_LEFT_PAREN: 162
168 | KEYCODE_NUMPAD_RIGHT_PAREN: 163
169 | KEYCODE_VOLUME_MUTE: 164
170 | KEYCODE_INFO: 165
171 | KEYCODE_CHANNEL_UP: 166
172 | KEYCODE_CHANNEL_DOWN: 167
173 | KEYCODE_ZOOM_IN: 168
174 | KEYCODE_ZOOM_OUT: 169
175 | KEYCODE_TV: 170
176 | KEYCODE_WINDOW: 171
177 | KEYCODE_GUIDE: 172
178 | KEYCODE_DVR: 173
179 | KEYCODE_BOOKMARK: 174
180 | KEYCODE_CAPTIONS: 175
181 | KEYCODE_SETTINGS: 176
182 | KEYCODE_TV_POWER: 177
183 | KEYCODE_TV_INPUT: 178
184 | KEYCODE_STB_POWER: 179
185 | KEYCODE_STB_INPUT: 180
186 | KEYCODE_AVR_POWER: 181
187 | KEYCODE_AVR_INPUT: 182
188 | KEYCODE_PROG_RED: 183
189 | KEYCODE_PROG_GREEN: 184
190 | KEYCODE_PROG_YELLOW: 185
191 | KEYCODE_PROG_BLUE: 186
192 | KEYCODE_APP_SWITCH: 187
193 | KEYCODE_BUTTON_1: 188
194 | KEYCODE_BUTTON_2: 189
195 | KEYCODE_BUTTON_3: 190
196 | KEYCODE_BUTTON_4: 191
197 | KEYCODE_BUTTON_5: 192
198 | KEYCODE_BUTTON_6: 193
199 | KEYCODE_BUTTON_7: 194
200 | KEYCODE_BUTTON_8: 195
201 | KEYCODE_BUTTON_9: 196
202 | KEYCODE_BUTTON_10: 197
203 | KEYCODE_BUTTON_11: 198
204 | KEYCODE_BUTTON_12: 199
205 | KEYCODE_BUTTON_13: 200
206 | KEYCODE_BUTTON_14: 201
207 | KEYCODE_BUTTON_15: 202
208 | KEYCODE_BUTTON_16: 203
209 | KEYCODE_LANGUAGE_SWITCH: 204
210 | KEYCODE_MANNER_MODE: 205
211 | KEYCODE_3D_MODE: 206
212 | KEYCODE_CONTACTS: 207
213 | KEYCODE_CALENDAR: 208
214 | KEYCODE_MUSIC: 209
215 | KEYCODE_CALCULATOR: 210
216 | KEYCODE_ZENKAKU_HANKAKU: 211
217 | KEYCODE_EISU: 212
218 | KEYCODE_MUHENKAN: 213
219 | KEYCODE_HENKAN: 214
220 | KEYCODE_KATAKANA_HIRAGANA: 215
221 | KEYCODE_YEN: 216
222 | KEYCODE_RO: 217
223 | KEYCODE_KANA: 218
224 | KEYCODE_ASSIST: 219
225 | KEYCODE_BRIGHTNESS_DOWN: 220
226 | KEYCODE_BRIGHTNESS_UP: 221
227 | KEYCODE_MEDIA_AUDIO_TRACK: 222
228 |
--------------------------------------------------------------------------------
/src/adb/linetransform.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 |
3 | class LineTransform extends Stream.Transform
4 | constructor: (options = {}) ->
5 | @savedR = null
6 | @autoDetect = options.autoDetect or false
7 | @transformNeeded = true
8 | @skipBytes = 0
9 | delete options.autoDetect
10 | super options
11 |
12 | _nullTransform: (chunk, encoding, done) ->
13 | this.push chunk
14 | done()
15 | return
16 |
17 | # Sadly, the ADB shell is not very smart. It automatically converts every
18 | # 0x0a ('\n') it can find to 0x0d 0x0a ('\r\n'). This also applies to binary
19 | # content. We could get rid of this behavior by setting `stty raw`, but
20 | # unfortunately it's not available by default (you'd have to install busybox)
21 | # or something similar. On the up side, it really does do this for all line
22 | # feeds, so a simple transform works fine.
23 | _transform: (chunk, encoding, done) ->
24 | # If auto detection is enabled, check the first byte. The first two
25 | # bytes must be either 0x0a .. or 0x0d 0x0a. This causes a need to skip
26 | # either one or two bytes. The autodetection runs only once.
27 | if @autoDetect
28 | if chunk[0] is 0x0a
29 | @transformNeeded = false
30 | @skipBytes = 1
31 | else
32 | @skipBytes = 2
33 | @autoDetect = false
34 |
35 | # It's technically possible that we may receive the first two bytes
36 | # in two separate chunks. That's why the autodetect bytes are skipped
37 | # here, separately.
38 | if @skipBytes
39 | skip = Math.min chunk.length, @skipBytes
40 | chunk = chunk.slice skip
41 | @skipBytes -= skip
42 |
43 | # It's possible that skipping bytes has created an empty chunk.
44 | return done() unless chunk.length
45 |
46 | # At this point all bytes that needed to be skipped should have been
47 | # skipped. If transform is not needed, shortcut to null transform.
48 | return this._nullTransform(chunk, encoding, done) unless @transformNeeded
49 |
50 | # Ok looks like we're transforming.
51 | lo = 0
52 | hi = 0
53 | if @savedR
54 | this.push @savedR unless chunk[0] is 0x0a
55 | @savedR = null
56 | last = chunk.length - 1
57 | while hi <= last
58 | if chunk[hi] is 0x0d
59 | if hi is last
60 | @savedR = chunk.slice last
61 | break # Stop hi from incrementing, we want to skip the last byte.
62 | else if chunk[hi + 1] is 0x0a
63 | this.push chunk.slice lo, hi
64 | lo = hi + 1
65 | hi += 1
66 | unless hi is lo
67 | this.push chunk.slice lo, hi
68 | done()
69 | return
70 |
71 | # When the stream ends on an '\r', output it as-is (assume binary data).
72 | _flush: (done) ->
73 | this.push @savedR if @savedR
74 | done()
75 |
76 | module.exports = LineTransform
77 |
--------------------------------------------------------------------------------
/src/adb/parser.coffee:
--------------------------------------------------------------------------------
1 | Promise = require 'bluebird'
2 |
3 | Protocol = require './protocol'
4 |
5 | class Parser
6 | constructor: (@stream) ->
7 | @ended = false
8 |
9 | end: ->
10 | return Promise.resolve(true) if @ended
11 |
12 | resolver = Promise.defer()
13 |
14 | tryRead = =>
15 | while @stream.read()
16 | continue
17 | return
18 |
19 | @stream.on 'readable', tryRead
20 |
21 | @stream.on 'error', errorListener = (err) ->
22 | resolver.reject err
23 |
24 | @stream.on 'end', endListener = =>
25 | @ended = true
26 | resolver.resolve true
27 |
28 | @stream.read(0)
29 | @stream.end()
30 |
31 | resolver.promise.cancellable().finally =>
32 | @stream.removeListener 'readable', tryRead
33 | @stream.removeListener 'error', errorListener
34 | @stream.removeListener 'end', endListener
35 |
36 | raw: ->
37 | @stream
38 |
39 | readAll: ->
40 | all = new Buffer 0
41 | resolver = Promise.defer()
42 |
43 | tryRead = =>
44 | while chunk = @stream.read()
45 | all = Buffer.concat [all, chunk]
46 | resolver.resolve all if @ended
47 |
48 | @stream.on 'readable', tryRead
49 |
50 | @stream.on 'error', errorListener = (err) ->
51 | resolver.reject err
52 |
53 | @stream.on 'end', endListener = =>
54 | @ended = true
55 | resolver.resolve all
56 |
57 | tryRead()
58 |
59 | resolver.promise.cancellable().finally =>
60 | @stream.removeListener 'readable', tryRead
61 | @stream.removeListener 'error', errorListener
62 | @stream.removeListener 'end', endListener
63 |
64 | readAscii: (howMany) ->
65 | this.readBytes howMany
66 | .then (chunk) ->
67 | chunk.toString 'ascii'
68 |
69 | readBytes: (howMany) ->
70 | resolver = Promise.defer()
71 |
72 | tryRead = =>
73 | if howMany
74 | if chunk = @stream.read howMany
75 | # If the stream ends while still having unread bytes, the read call
76 | # will ignore the limit and just return what it's got.
77 | howMany -= chunk.length
78 | return resolver.resolve chunk if howMany is 0
79 | resolver.reject new Parser.PrematureEOFError howMany if @ended
80 | else
81 | resolver.resolve new Buffer 0
82 |
83 | endListener = =>
84 | @ended = true
85 | resolver.reject new Parser.PrematureEOFError howMany
86 |
87 | errorListener = (err) ->
88 | resolver.reject err
89 |
90 | @stream.on 'readable', tryRead
91 | @stream.on 'error', errorListener
92 | @stream.on 'end', endListener
93 |
94 | tryRead()
95 |
96 | resolver.promise.cancellable().finally =>
97 | @stream.removeListener 'readable', tryRead
98 | @stream.removeListener 'error', errorListener
99 | @stream.removeListener 'end', endListener
100 |
101 | readByteFlow: (howMany, targetStream) ->
102 | resolver = Promise.defer()
103 |
104 | tryRead = =>
105 | if howMany
106 | # Try to get the exact amount we need first. If unsuccessful, take
107 | # whatever is available, which will be less than the needed amount.
108 | while chunk = @stream.read(howMany) or @stream.read()
109 | howMany -= chunk.length
110 | targetStream.write chunk
111 | return resolver.resolve() if howMany is 0
112 | resolver.reject new Parser.PrematureEOFError howMany if @ended
113 | else
114 | resolver.resolve()
115 |
116 | endListener = =>
117 | @ended = true
118 | resolver.reject new Parser.PrematureEOFError howMany
119 |
120 | errorListener = (err) ->
121 | resolver.reject err
122 |
123 | @stream.on 'readable', tryRead
124 | @stream.on 'error', errorListener
125 | @stream.on 'end', endListener
126 |
127 | tryRead()
128 |
129 | resolver.promise.cancellable().finally =>
130 | @stream.removeListener 'readable', tryRead
131 | @stream.removeListener 'error', errorListener
132 | @stream.removeListener 'end', endListener
133 |
134 | readError: ->
135 | this.readValue()
136 | .then (value) ->
137 | Promise.reject new Parser.FailError value.toString()
138 |
139 | readValue: ->
140 | this.readAscii 4
141 | .then (value) =>
142 | length = Protocol.decodeLength value
143 | this.readBytes length
144 |
145 | readUntil: (code) ->
146 | skipped = new Buffer 0
147 | read = =>
148 | this.readBytes 1
149 | .then (chunk) ->
150 | if chunk[0] is code
151 | skipped
152 | else
153 | skipped = Buffer.concat [skipped, chunk]
154 | read()
155 | read()
156 |
157 | searchLine: (re) ->
158 | this.readLine()
159 | .then (line) =>
160 | if match = re.exec line
161 | match
162 | else
163 | this.searchLine re
164 |
165 | readLine: ->
166 | this.readUntil 0x0a # '\n'
167 | .then (line) ->
168 | if line[line.length - 1] is 0x0d # '\r'
169 | line.slice 0, -1
170 | else
171 | line
172 |
173 | unexpected: (data, expected) ->
174 | Promise.reject new Parser.UnexpectedDataError data, expected
175 |
176 | class Parser.FailError extends Error
177 | constructor: (message) ->
178 | Error.call this
179 | this.name = 'FailError'
180 | this.message = "Failure: '#{message}'"
181 | Error.captureStackTrace this, Parser.FailError
182 |
183 | class Parser.PrematureEOFError extends Error
184 | constructor: (howManyMissing) ->
185 | Error.call this
186 | this.name = 'PrematureEOFError'
187 | this.message = "Premature end of stream, needed #{howManyMissing}
188 | more bytes"
189 | this.missingBytes = howManyMissing
190 | Error.captureStackTrace this, Parser.PrematureEOFError
191 |
192 | class Parser.UnexpectedDataError extends Error
193 | constructor: (unexpected, expected) ->
194 | Error.call this
195 | this.name = 'UnexpectedDataError'
196 | this.message = "Unexpected '#{unexpected}', was expecting #{expected}"
197 | this.unexpected = unexpected
198 | this.expected = expected
199 | Error.captureStackTrace this, Parser.UnexpectedDataError
200 |
201 | module.exports = Parser
202 |
--------------------------------------------------------------------------------
/src/adb/proc/stat.coffee:
--------------------------------------------------------------------------------
1 | {EventEmitter} = require 'events'
2 | split = require 'split'
3 |
4 | Parser = require '../parser'
5 |
6 | class ProcStat extends EventEmitter
7 | RE_CPULINE = /^cpu[0-9]+ .*$/mg
8 | RE_COLSEP = /\ +/g
9 |
10 | constructor: (@sync) ->
11 | @interval = 1000
12 | @stats = this._emptyStats()
13 | @_ignore = {}
14 | @_timer = setInterval =>
15 | this.update()
16 | , @interval
17 | this.update()
18 |
19 | end: ->
20 | clearInterval @_timer
21 | @sync.end()
22 | @sync = null
23 |
24 | update: ->
25 | new Parser(@sync.pull '/proc/stat')
26 | .readAll()
27 | .then (out) =>
28 | this._parse out
29 | .catch (err) =>
30 | this._error err
31 | return
32 |
33 | _parse: (out) ->
34 | stats = this._emptyStats()
35 | while match = RE_CPULINE.exec out
36 | line = match[0]
37 | cols = line.split RE_COLSEP
38 | type = cols.shift()
39 | continue if @_ignore[type] is line
40 | total = 0
41 | total += +val for val in cols
42 | stats.cpus[type] =
43 | line: line
44 | user: +cols[0] or 0
45 | nice: +cols[1] or 0
46 | system: +cols[2] or 0
47 | idle: +cols[3] or 0
48 | iowait: +cols[4] or 0
49 | irq: +cols[5] or 0
50 | softirq: +cols[6] or 0
51 | steal: +cols[7] or 0
52 | guest: +cols[8] or 0
53 | guestnice: +cols[9] or 0
54 | total: total
55 | this._set stats
56 |
57 | _set: (stats) ->
58 | loads = {}
59 | found = false
60 | for id, cur of stats.cpus
61 | old = @stats.cpus[id]
62 | continue unless old
63 | ticks = cur.total - old.total
64 | if ticks > 0
65 | found = true
66 | # Calculate percentages for everything. For ease of formatting,
67 | # let's do `x / y * 100` as `100 / y * x`.
68 | m = 100 / ticks
69 | loads[id] =
70 | user: Math.floor m * (cur.user - old.user)
71 | nice: Math.floor m * (cur.nice - old.nice)
72 | system: Math.floor m * (cur.system - old.system)
73 | idle: Math.floor m * (cur.idle - old.idle)
74 | iowait: Math.floor m * (cur.iowait - old.iowait)
75 | irq: Math.floor m * (cur.irq - old.irq)
76 | softirq: Math.floor m * (cur.softirq - old.softirq)
77 | steal: Math.floor m * (cur.steal - old.steal)
78 | guest: Math.floor m * (cur.guest - old.guest)
79 | guestnice: Math.floor m * (cur.guestnice - old.guestnice)
80 | total: 100
81 | else
82 | # The CPU is either offline (nothing was done) or it mysteriously
83 | # warped back in time (idle stat dropped significantly), causing the
84 | # total tick count to be <0. The latter seems to only happen on
85 | # Galaxy S4 so far. Either way we don't want those anomalies in our
86 | # stats. We'll also ignore the line in the next cycle. This doesn't
87 | # completely eliminate the anomalies, but it helps.
88 | @_ignore[id] = cur.line
89 | delete stats.cpus[id]
90 | this.emit 'load', loads if found
91 | @stats = stats
92 |
93 | _error: (err) ->
94 | this.emit 'error', err
95 |
96 | _emptyStats: ->
97 | cpus: {}
98 |
99 | module.exports = ProcStat
100 |
--------------------------------------------------------------------------------
/src/adb/protocol.coffee:
--------------------------------------------------------------------------------
1 | class Protocol
2 | @OKAY = 'OKAY'
3 | @FAIL = 'FAIL'
4 | @STAT = 'STAT'
5 | @LIST = 'LIST'
6 | @DENT = 'DENT'
7 | @RECV = 'RECV'
8 | @DATA = 'DATA'
9 | @DONE = 'DONE'
10 | @SEND = 'SEND'
11 | @QUIT = 'QUIT'
12 |
13 | @decodeLength: (length) ->
14 | parseInt length, 16
15 |
16 | @encodeLength: (length) ->
17 | ('0000' + length.toString 16).slice(-4).toUpperCase()
18 |
19 | @encodeData: (data) ->
20 | unless Buffer.isBuffer data
21 | data = new Buffer data
22 | Buffer.concat [new Buffer(Protocol.encodeLength data.length), data]
23 |
24 | module.exports = Protocol
25 |
--------------------------------------------------------------------------------
/src/adb/sync.coffee:
--------------------------------------------------------------------------------
1 | Fs = require 'fs'
2 | Path = require 'path'
3 | Promise = require 'bluebird'
4 | {EventEmitter} = require 'events'
5 | debug = require('debug')('adb:sync')
6 |
7 | Parser = require './parser'
8 | Protocol = require './protocol'
9 | Stats = require './sync/stats'
10 | Entry = require './sync/entry'
11 | PushTransfer = require './sync/pushtransfer'
12 | PullTransfer = require './sync/pulltransfer'
13 |
14 | class Sync extends EventEmitter
15 | TEMP_PATH = '/data/local/tmp'
16 | DEFAULT_CHMOD = 0o644
17 | DATA_MAX_LENGTH = 65536
18 |
19 | @temp: (path) ->
20 | "#{TEMP_PATH}/#{Path.basename path}"
21 |
22 | constructor: (@connection) ->
23 | @parser = @connection.parser
24 |
25 | stat: (path, callback) ->
26 | this._sendCommandWithArg Protocol.STAT, path
27 | @parser.readAscii 4
28 | .then (reply) =>
29 | switch reply
30 | when Protocol.STAT
31 | @parser.readBytes 12
32 | .then (stat) =>
33 | mode = stat.readUInt32LE 0
34 | size = stat.readUInt32LE 4
35 | mtime = stat.readUInt32LE 8
36 | if mode is 0
37 | this._enoent path
38 | else
39 | new Stats mode, size, mtime
40 | when Protocol.FAIL
41 | this._readError()
42 | else
43 | @parser.unexpected reply, 'STAT or FAIL'
44 | .nodeify callback
45 |
46 | readdir: (path, callback) ->
47 | files = []
48 |
49 | readNext = =>
50 | @parser.readAscii 4
51 | .then (reply) =>
52 | switch reply
53 | when Protocol.DENT
54 | @parser.readBytes 16
55 | .then (stat) =>
56 | mode = stat.readUInt32LE 0
57 | size = stat.readUInt32LE 4
58 | mtime = stat.readUInt32LE 8
59 | namelen = stat.readUInt32LE 12
60 | @parser.readBytes namelen
61 | .then (name) ->
62 | name = name.toString()
63 | # Skip '.' and '..' to match Node's fs.readdir().
64 | unless name is '.' or name is '..'
65 | files.push new Entry name, mode, size, mtime
66 | readNext()
67 | when Protocol.DONE
68 | @parser.readBytes 16
69 | .then (zero) ->
70 | files
71 | when Protocol.FAIL
72 | this._readError()
73 | else
74 | @parser.unexpected reply, 'DENT, DONE or FAIL'
75 |
76 | this._sendCommandWithArg Protocol.LIST, path
77 |
78 | readNext()
79 | .nodeify callback
80 |
81 | push: (contents, path, mode) ->
82 | if typeof contents is 'string'
83 | this.pushFile contents, path, mode
84 | else
85 | this.pushStream contents, path, mode
86 |
87 | pushFile: (file, path, mode = DEFAULT_CHMOD) ->
88 | mode or= DEFAULT_CHMOD
89 | this.pushStream Fs.createReadStream(file), path, mode
90 |
91 | pushStream: (stream, path, mode = DEFAULT_CHMOD) ->
92 | mode |= Stats.S_IFREG
93 | this._sendCommandWithArg Protocol.SEND, "#{path},#{mode}"
94 | this._writeData stream, Math.floor(Date.now() / 1000)
95 |
96 | pull: (path) ->
97 | this._sendCommandWithArg Protocol.RECV, "#{path}"
98 | this._readData()
99 |
100 | end: ->
101 | @connection.end()
102 | return this
103 |
104 | tempFile: (path) ->
105 | Sync.temp path
106 |
107 | _writeData: (stream, timeStamp) ->
108 | transfer = new PushTransfer
109 |
110 | writeData = =>
111 | resolver = Promise.defer()
112 | writer = Promise.resolve()
113 | .cancellable()
114 |
115 | stream.on 'end', endListener = =>
116 | writer.then =>
117 | this._sendCommandWithLength Protocol.DONE, timeStamp
118 | resolver.resolve()
119 |
120 | waitForDrain = =>
121 | resolver = Promise.defer()
122 |
123 | @connection.on 'drain', drainListener = ->
124 | resolver.resolve()
125 |
126 | resolver.promise.finally =>
127 | @connection.removeListener 'drain', drainListener
128 |
129 | track = ->
130 | transfer.pop()
131 |
132 | writeNext = =>
133 | if chunk = stream.read(DATA_MAX_LENGTH) or stream.read()
134 | this._sendCommandWithLength Protocol.DATA, chunk.length
135 | transfer.push chunk.length
136 | if @connection.write chunk, track
137 | writeNext()
138 | else
139 | waitForDrain()
140 | .then writeNext
141 | else
142 | Promise.resolve()
143 |
144 | stream.on 'readable', readableListener = ->
145 | writer.then writeNext
146 |
147 | stream.on 'error', errorListener = (err) ->
148 | resolver.reject err
149 |
150 | resolver.promise.finally ->
151 | stream.removeListener 'end', endListener
152 | stream.removeListener 'readable', readableListener
153 | stream.removeListener 'error', errorListener
154 | writer.cancel()
155 |
156 | readReply = =>
157 | @parser.readAscii 4
158 | .then (reply) =>
159 | switch reply
160 | when Protocol.OKAY
161 | @parser.readBytes 4
162 | .then (zero) ->
163 | true
164 | when Protocol.FAIL
165 | this._readError()
166 | else
167 | @parser.unexpected reply, 'OKAY or FAIL'
168 |
169 | # While I can't think of a case that would break this double-Promise
170 | # writer-reader arrangement right now, it's not immediately obvious
171 | # that the code is correct and it may or may not have some failing
172 | # edge cases. Refactor pending.
173 |
174 | writer = writeData()
175 | .cancellable()
176 | .catch Promise.CancellationError, (err) =>
177 | @connection.end()
178 | .catch (err) ->
179 | transfer.emit 'error', err
180 | reader.cancel()
181 |
182 | reader = readReply()
183 | .cancellable()
184 | .catch Promise.CancellationError, (err) ->
185 | true
186 | .catch (err) ->
187 | transfer.emit 'error', err
188 | writer.cancel()
189 | .finally ->
190 | transfer.end()
191 |
192 | transfer.on 'cancel', ->
193 | writer.cancel()
194 | reader.cancel()
195 |
196 | return transfer
197 |
198 | _readData: ->
199 | transfer = new PullTransfer
200 |
201 | readNext = =>
202 | @parser.readAscii 4
203 | .cancellable()
204 | .then (reply) =>
205 | switch reply
206 | when Protocol.DATA
207 | @parser.readBytes 4
208 | .then (lengthData) =>
209 | length = lengthData.readUInt32LE 0
210 | @parser.readByteFlow(length, transfer)
211 | .then readNext
212 | when Protocol.DONE
213 | @parser.readBytes 4
214 | .then (zero) ->
215 | true
216 | when Protocol.FAIL
217 | this._readError()
218 | else
219 | @parser.unexpected reply, 'DATA, DONE or FAIL'
220 |
221 | reader = readNext()
222 | .catch Promise.CancellationError, (err) =>
223 | @connection.end()
224 | .catch (err) ->
225 | transfer.emit 'error', err
226 | .finally ->
227 | transfer.removeListener 'cancel', cancelListener
228 | transfer.end()
229 |
230 | transfer.on 'cancel', cancelListener = ->
231 | reader.cancel()
232 |
233 | return transfer
234 |
235 | _readError: ->
236 | @parser.readBytes 4
237 | .then (length) =>
238 | @parser.readBytes length.readUInt32LE(0)
239 | .then (buf) ->
240 | Promise.reject new Parser.FailError buf.toString()
241 | .finally =>
242 | @parser.end()
243 |
244 | _sendCommandWithLength: (cmd, length) ->
245 | debug cmd unless cmd is Protocol.DATA
246 | payload = new Buffer cmd.length + 4
247 | payload.write cmd, 0, cmd.length
248 | payload.writeUInt32LE length, cmd.length
249 | @connection.write payload
250 |
251 | _sendCommandWithArg: (cmd, arg) ->
252 | debug "#{cmd} #{arg}"
253 | arglen = Buffer.byteLength arg, 'utf-8'
254 | payload = new Buffer cmd.length + 4 + arglen
255 | pos = 0
256 | payload.write cmd, pos, cmd.length
257 | pos += cmd.length
258 | payload.writeUInt32LE arglen, pos
259 | pos += 4
260 | payload.write arg, pos
261 | @connection.write payload
262 |
263 | _enoent: (path) ->
264 | err = new Error "ENOENT, no such file or directory '#{path}'"
265 | err.errno = 34
266 | err.code = 'ENOENT'
267 | err.path = path
268 | Promise.reject err
269 |
270 | module.exports = Sync
271 |
--------------------------------------------------------------------------------
/src/adb/sync/entry.coffee:
--------------------------------------------------------------------------------
1 | Stats = require './stats'
2 |
3 | class Entry extends Stats
4 | constructor: (@name, mode, size, mtime) ->
5 | super mode, size, mtime
6 |
7 | toString: ->
8 | @name
9 |
10 | module.exports = Entry
11 |
--------------------------------------------------------------------------------
/src/adb/sync/pulltransfer.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 |
3 | class PullTransfer extends Stream.PassThrough
4 | constructor: ->
5 | @stats =
6 | bytesTransferred: 0
7 | super()
8 |
9 | cancel: ->
10 | this.emit 'cancel'
11 |
12 | write: (chunk, encoding, callback) ->
13 | @stats.bytesTransferred += chunk.length
14 | this.emit 'progress', @stats
15 | super chunk, encoding, callback
16 |
17 | module.exports = PullTransfer
18 |
--------------------------------------------------------------------------------
/src/adb/sync/pushtransfer.coffee:
--------------------------------------------------------------------------------
1 | {EventEmitter} = require 'events'
2 |
3 | class PushTransfer extends EventEmitter
4 | constructor: ->
5 | @_stack = []
6 | @stats =
7 | bytesTransferred: 0
8 |
9 | cancel: ->
10 | this.emit 'cancel'
11 |
12 | push: (byteCount) ->
13 | @_stack.push byteCount
14 |
15 | pop: ->
16 | byteCount = @_stack.pop()
17 | @stats.bytesTransferred += byteCount
18 | this.emit 'progress', @stats
19 |
20 | end: ->
21 | this.emit 'end'
22 |
23 | module.exports = PushTransfer
24 |
--------------------------------------------------------------------------------
/src/adb/sync/stats.coffee:
--------------------------------------------------------------------------------
1 | Fs = require 'fs'
2 |
3 | class Stats extends Fs.Stats
4 | # The following constant were extracted from `man 2 stat` on Ubuntu 12.10.
5 | @S_IFMT = 0o170000 # bit mask for the file type bit fields
6 | @S_IFSOCK = 0o140000 # socket
7 | @S_IFLNK = 0o120000 # symbolic link
8 | @S_IFREG = 0o100000 # regular file
9 | @S_IFBLK = 0o060000 # block device
10 | @S_IFDIR = 0o040000 # directory
11 | @S_IFCHR = 0o020000 # character device
12 | @S_IFIFO = 0o010000 # FIFO
13 | @S_ISUID = 0o004000 # set UID bit
14 | @S_ISGID = 0o002000 # set-group-ID bit (see below)
15 | @S_ISVTX = 0o001000 # sticky bit (see below)
16 | @S_IRWXU = 0o0700 # mask for file owner permissions
17 | @S_IRUSR = 0o0400 # owner has read permission
18 | @S_IWUSR = 0o0200 # owner has write permission
19 | @S_IXUSR = 0o0100 # owner has execute permission
20 | @S_IRWXG = 0o0070 # mask for group permissions
21 | @S_IRGRP = 0o0040 # group has read permission
22 |
23 | constructor: (@mode, @size, mtime) ->
24 | @mtime = new Date mtime * 1000
25 |
26 | module.exports = Stats
27 |
--------------------------------------------------------------------------------
/src/adb/tcpusb/packet.coffee:
--------------------------------------------------------------------------------
1 | class Packet
2 | @A_SYNC = 0x434e5953
3 | @A_CNXN = 0x4e584e43
4 | @A_OPEN = 0x4e45504f
5 | @A_OKAY = 0x59414b4f
6 | @A_CLSE = 0x45534c43
7 | @A_WRTE = 0x45545257
8 | @A_AUTH = 0x48545541
9 |
10 | @checksum: (data) ->
11 | sum = 0
12 | sum += char for char in data if data
13 | return sum
14 |
15 | @magic: (command) ->
16 | # We need the full uint32 range, which ">>> 0" thankfully allows us to use
17 | (command ^ 0xffffffff) >>> 0
18 |
19 | @assemble: (command, arg0, arg1, data) ->
20 | if data
21 | chunk = new Buffer 24 + data.length
22 | chunk.writeUInt32LE command, 0
23 | chunk.writeUInt32LE arg0, 4
24 | chunk.writeUInt32LE arg1, 8
25 | chunk.writeUInt32LE data.length, 12
26 | chunk.writeUInt32LE Packet.checksum(data), 16
27 | chunk.writeUInt32LE Packet.magic(command), 20
28 | data.copy chunk, 24
29 | chunk
30 | else
31 | chunk = new Buffer 24
32 | chunk.writeUInt32LE command, 0
33 | chunk.writeUInt32LE arg0, 4
34 | chunk.writeUInt32LE arg1, 8
35 | chunk.writeUInt32LE 0, 12
36 | chunk.writeUInt32LE 0, 16
37 | chunk.writeUInt32LE Packet.magic(command), 20
38 | chunk
39 |
40 | @swap32: (n) ->
41 | buffer = new Buffer(4)
42 | buffer.writeUInt32LE(n, 0)
43 | buffer.readUInt32BE(0)
44 |
45 | constructor: (@command, @arg0, @arg1, @length, @check, @magic, @data) ->
46 |
47 | verifyChecksum: ->
48 | @check is Packet.checksum @data
49 |
50 | verifyMagic: ->
51 | @magic is Packet.magic @command
52 |
53 | toString: ->
54 | type = switch @command
55 | when Packet.A_SYNC
56 | "SYNC"
57 | when Packet.A_CNXN
58 | "CNXN"
59 | when Packet.A_OPEN
60 | "OPEN"
61 | when Packet.A_OKAY
62 | "OKAY"
63 | when Packet.A_CLSE
64 | "CLSE"
65 | when Packet.A_WRTE
66 | "WRTE"
67 | when Packet.A_AUTH
68 | "AUTH"
69 | else
70 | throw new Error "Unknown command {@command}"
71 | "#{type} arg0=#{@arg0} arg1=#{@arg1} length=#{@length}"
72 |
73 | module.exports = Packet
74 |
--------------------------------------------------------------------------------
/src/adb/tcpusb/packetreader.coffee:
--------------------------------------------------------------------------------
1 | {EventEmitter} = require 'events'
2 |
3 | Packet = require './packet'
4 |
5 | class PacketReader extends EventEmitter
6 | constructor: (@stream) ->
7 | super()
8 | @inBody = false
9 | @buffer = null
10 | @packet = null
11 | @stream.on 'readable', this._tryRead.bind(this)
12 | @stream.on 'error', (err) => this.emit 'error', err
13 | @stream.on 'end', => this.emit 'end'
14 | setImmediate this._tryRead.bind(this)
15 |
16 | _tryRead: ->
17 | while this._appendChunk()
18 | while @buffer
19 | if @inBody
20 | break unless @buffer.length >= @packet.length
21 | @packet.data = this._consume(@packet.length)
22 | unless @packet.verifyChecksum()
23 | this.emit 'error', new PacketReader.ChecksumError(@packet)
24 | return
25 | this.emit 'packet', @packet
26 | @inBody = false
27 | else
28 | break unless @buffer.length >= 24
29 | header = this._consume(24)
30 | @packet = new Packet(
31 | header.readUInt32LE 0
32 | header.readUInt32LE 4
33 | header.readUInt32LE 8
34 | header.readUInt32LE 12
35 | header.readUInt32LE 16
36 | header.readUInt32LE 20
37 | new Buffer(0)
38 | )
39 | unless @packet.verifyMagic()
40 | this.emit 'error', new PacketReader.MagicError(@packet)
41 | return
42 | if @packet.length is 0
43 | this.emit 'packet', @packet
44 | else
45 | @inBody = true
46 |
47 | _appendChunk: ->
48 | if chunk = @stream.read()
49 | if @buffer
50 | @buffer = Buffer.concat([@buffer, chunk], @buffer.length + chunk.length)
51 | else
52 | @buffer = chunk
53 | else
54 | null
55 |
56 | _consume: (length) ->
57 | chunk = @buffer.slice(0, length)
58 | @buffer = if length is @buffer.length then null else @buffer.slice(length)
59 | chunk
60 |
61 | class PacketReader.ChecksumError extends Error
62 | constructor: (@packet) ->
63 | Error.call this
64 | @name = 'ChecksumError'
65 | @message = "Checksum mismatch"
66 | Error.captureStackTrace this, PacketReader.ChecksumError
67 |
68 | class PacketReader.MagicError extends Error
69 | constructor: (@packet) ->
70 | Error.call this
71 | @name = 'MagicError'
72 | @message = "Magic value mismatch"
73 | Error.captureStackTrace this, PacketReader.MagicError
74 |
75 | module.exports = PacketReader
76 |
--------------------------------------------------------------------------------
/src/adb/tcpusb/rollingcounter.coffee:
--------------------------------------------------------------------------------
1 | class RollingCounter
2 | constructor: (@max, @min = 1) ->
3 | @now = @min
4 |
5 | next: ->
6 | @now = @min unless @now < @max
7 | return ++@now
8 |
9 | module.exports = RollingCounter
10 |
--------------------------------------------------------------------------------
/src/adb/tcpusb/server.coffee:
--------------------------------------------------------------------------------
1 | Net = require 'net'
2 | {EventEmitter} = require 'events'
3 |
4 | Socket = require './socket'
5 |
6 | class Server extends EventEmitter
7 | constructor: (@client, @serial, @options) ->
8 | @connections = []
9 | @server = Net.createServer allowHalfOpen: true
10 | @server.on 'error', (err) =>
11 | this.emit 'error', err
12 | @server.on 'listening', =>
13 | this.emit 'listening'
14 | @server.on 'close', =>
15 | this.emit 'close'
16 | @server.on 'connection', (conn) =>
17 | socket = new Socket @client, @serial, conn, @options
18 | @connections.push socket
19 | socket.on 'error', (err) =>
20 | # 'conn' is guaranteed to get ended
21 | this.emit 'error', err
22 | socket.once 'end', =>
23 | # 'conn' is guaranteed to get ended
24 | @connections = @connections.filter (val) -> val isnt socket
25 | this.emit 'connection', socket
26 |
27 | listen: ->
28 | @server.listen.apply @server, arguments
29 | return this
30 |
31 | close: ->
32 | @server.close()
33 | return this
34 |
35 | end: ->
36 | conn.end() for conn in @connections
37 | return this
38 |
39 | module.exports = Server
40 |
--------------------------------------------------------------------------------
/src/adb/tcpusb/service.coffee:
--------------------------------------------------------------------------------
1 | {EventEmitter} = require 'events'
2 |
3 | Promise = require 'bluebird'
4 | debug = require('debug')('adb:tcpusb:service')
5 |
6 | Parser = require '../parser'
7 | Protocol = require '../protocol'
8 | Packet = require './packet'
9 |
10 | class Service extends EventEmitter
11 | constructor: (@client, @serial, @localId, @remoteId, @socket) ->
12 | super()
13 | @opened = false
14 | @ended = false
15 | @transport = null
16 | @needAck = false
17 |
18 | end: ->
19 | @transport.end() if @transport
20 | return this if @ended
21 | debug 'O:A_CLSE'
22 | localId = if @opened then @localId else 0 # Zero can only mean a failed open
23 | try
24 | # We may or may not have gotten here due to @socket ending, so write
25 | # may fail.
26 | @socket.write Packet.assemble(Packet.A_CLSE, localId, @remoteId, null)
27 | catch err
28 | # Let it go
29 | @transport = null
30 | @ended = true
31 | this.emit 'end'
32 | return this
33 |
34 | handle: (packet) ->
35 | Promise.try =>
36 | switch packet.command
37 | when Packet.A_OPEN
38 | this._handleOpenPacket(packet)
39 | when Packet.A_OKAY
40 | this._handleOkayPacket(packet)
41 | when Packet.A_WRTE
42 | this._handleWritePacket(packet)
43 | when Packet.A_CLSE
44 | this._handleClosePacket(packet)
45 | else
46 | throw new Error "Unexpected packet #{packet.command}"
47 | .catch (err) =>
48 | this.emit 'error', err
49 | this.end()
50 |
51 | _handleOpenPacket: (packet) ->
52 | debug 'I:A_OPEN', packet
53 | @client.transport @serial
54 | .then (@transport) =>
55 | throw new LateTransportError() if @ended
56 | @transport.write Protocol.encodeData \
57 | packet.data.slice(0, -1) # Discard null byte at end
58 | @transport.parser.readAscii 4
59 | .then (reply) =>
60 | switch reply
61 | when Protocol.OKAY
62 | debug 'O:A_OKAY'
63 | @socket.write \
64 | Packet.assemble(Packet.A_OKAY, @localId, @remoteId, null)
65 | @opened = true
66 | when Protocol.FAIL
67 | @transport.parser.readError()
68 | else
69 | @transport.parser.unexpected reply, 'OKAY or FAIL'
70 | .then =>
71 | new Promise (resolve, reject) =>
72 | @transport.socket
73 | .on 'readable', => this._tryPush()
74 | .on 'end', resolve
75 | .on 'error', reject
76 | this._tryPush()
77 | .finally =>
78 | this.end()
79 |
80 | _handleOkayPacket: (packet) ->
81 | debug 'I:A_OKAY', packet
82 | return if @ended
83 | throw new Service.PrematurePacketError(packet) unless @transport
84 | @needAck = false
85 | this._tryPush()
86 |
87 | _handleWritePacket: (packet) ->
88 | debug 'I:A_WRTE', packet
89 | return if @ended
90 | throw new Service.PrematurePacketError(packet) unless @transport
91 | @transport.write packet.data if packet.data
92 | debug 'O:A_OKAY'
93 | @socket.write Packet.assemble(Packet.A_OKAY, @localId, @remoteId, null)
94 |
95 | _handleClosePacket: (packet) ->
96 | debug 'I:A_CLSE', packet
97 | return if @ended
98 | throw new Service.PrematurePacketError(packet) unless @transport
99 | this.end()
100 |
101 | _tryPush: ->
102 | return if @needAck or @ended
103 | if chunk = this._readChunk(@transport.socket)
104 | debug 'O:A_WRTE'
105 | @socket.write Packet.assemble(Packet.A_WRTE, @localId, @remoteId, chunk)
106 | @needAck = true
107 |
108 | _readChunk: (stream) ->
109 | stream.read(@socket.maxPayload) or stream.read()
110 |
111 | class Service.PrematurePacketError extends Error
112 | constructor: (@packet) ->
113 | Error.call this
114 | @name = 'PrematurePacketError'
115 | @message = "Premature packet"
116 | Error.captureStackTrace this, Service.PrematurePacketError
117 |
118 | class Service.LateTransportError extends Error
119 | constructor: ->
120 | Error.call this
121 | @name = 'LateTransportError'
122 | @message = "Late transport"
123 | Error.captureStackTrace this, Service.LateTransportError
124 |
125 | module.exports = Service
126 |
--------------------------------------------------------------------------------
/src/adb/tcpusb/servicemap.coffee:
--------------------------------------------------------------------------------
1 | class ServiceMap
2 | constructor: ->
3 | @remotes = Object.create null
4 | @count = 0
5 |
6 | end: ->
7 | for remoteId, remote of @remotes
8 | remote.end()
9 | @remotes = Object.create null
10 | @count = 0
11 | return
12 |
13 | insert: (remoteId, socket) ->
14 | if @remotes[remoteId]
15 | throw new Error "Remote ID #{remoteId} is already being used"
16 | else
17 | @count += 1
18 | @remotes[remoteId] = socket
19 |
20 | get: (remoteId) ->
21 | @remotes[remoteId] or null
22 |
23 | remove: (remoteId) ->
24 | if remote = @remotes[remoteId]
25 | delete @remotes[remoteId]
26 | @count -= 1
27 | remote
28 | else
29 | null
30 |
31 | module.exports = ServiceMap
32 |
--------------------------------------------------------------------------------
/src/adb/tcpusb/socket.coffee:
--------------------------------------------------------------------------------
1 | crypto = require 'crypto'
2 | {EventEmitter} = require 'events'
3 |
4 | Promise = require 'bluebird'
5 | Forge = require 'node-forge'
6 | debug = require('debug')('adb:tcpusb:socket')
7 |
8 | Parser = require '../parser'
9 | Protocol = require '../protocol'
10 | Auth = require '../auth'
11 | Packet = require './packet'
12 | PacketReader = require './packetreader'
13 | Service = require './service'
14 | ServiceMap = require './servicemap'
15 | RollingCounter = require './rollingcounter'
16 |
17 | class Socket extends EventEmitter
18 | UINT32_MAX = 0xFFFFFFFF
19 | UINT16_MAX = 0xFFFF
20 |
21 | AUTH_TOKEN = 1
22 | AUTH_SIGNATURE = 2
23 | AUTH_RSAPUBLICKEY = 3
24 |
25 | TOKEN_LENGTH = 20
26 |
27 | constructor: (@client, @serial, @socket, @options = {}) ->
28 | @options.auth or= Promise.resolve true
29 | @ended = false
30 | @socket.setNoDelay true
31 | @reader = new PacketReader @socket
32 | .on 'packet', this._handle.bind(this)
33 | .on 'error', (err) =>
34 | debug "PacketReader error: #{err.message}"
35 | this.end()
36 | .on 'end', this.end.bind(this)
37 | @version = 1
38 | @maxPayload = 4096
39 | @authorized = false
40 | @syncToken = new RollingCounter UINT32_MAX
41 | @remoteId = new RollingCounter UINT32_MAX
42 | @services = new ServiceMap
43 | @remoteAddress = @socket.remoteAddress
44 | @token = null
45 | @signature = null
46 |
47 | end: ->
48 | return this if @ended
49 | # End services first so that they can send a final payload before FIN.
50 | @services.end()
51 | @socket.end()
52 | @ended = true
53 | this.emit 'end'
54 | return this
55 |
56 | _error: (err) ->
57 | this.emit 'error', err
58 | this.end()
59 |
60 | _handle: (packet) ->
61 | return if @ended
62 | this.emit 'userActivity', packet
63 | Promise.try =>
64 | switch packet.command
65 | when Packet.A_SYNC
66 | this._handleSyncPacket packet
67 | when Packet.A_CNXN
68 | this._handleConnectionPacket packet
69 | when Packet.A_OPEN
70 | this._handleOpenPacket packet
71 | when Packet.A_OKAY, Packet.A_WRTE, Packet.A_CLSE
72 | this._forwardServicePacket packet
73 | when Packet.A_AUTH
74 | this._handleAuthPacket packet
75 | else
76 | throw new Error "Unknown command #{packet.command}"
77 | .catch Socket.AuthError, =>
78 | this.end()
79 | .catch Socket.UnauthorizedError, =>
80 | this.end()
81 | .catch (err) =>
82 | this._error err
83 |
84 | _handleSyncPacket: (packet) ->
85 | # No need to do anything?
86 | debug 'I:A_SYNC'
87 | debug 'O:A_SYNC'
88 | this.write Packet.assemble(Packet.A_SYNC, 1, @syncToken.next(), null)
89 |
90 | _handleConnectionPacket: (packet) ->
91 | debug 'I:A_CNXN', packet
92 | version = Packet.swap32(packet.arg0)
93 | @maxPayload = Math.min UINT16_MAX, packet.arg1
94 | this._createToken()
95 | .then (@token) =>
96 | debug "Created challenge '#{@token.toString('base64')}'"
97 | debug 'O:A_AUTH'
98 | this.write Packet.assemble(Packet.A_AUTH, AUTH_TOKEN, 0, @token)
99 |
100 | _handleAuthPacket: (packet) ->
101 | debug 'I:A_AUTH', packet
102 | switch packet.arg0
103 | when AUTH_SIGNATURE
104 | # Store first signature, ignore the rest
105 | debug "Received signature '#{packet.data.toString('base64')}'"
106 | @signature = packet.data unless @signature
107 | debug 'O:A_AUTH'
108 | this.write Packet.assemble(Packet.A_AUTH, AUTH_TOKEN, 0, @token)
109 | when AUTH_RSAPUBLICKEY
110 | unless @signature
111 | throw new Socket.AuthError "Public key sent before signature"
112 | unless packet.data and packet.data.length >= 2
113 | throw new Socket.AuthError "Empty RSA public key"
114 | debug "Received RSA public key '#{packet.data.toString('base64')}'"
115 | Auth.parsePublicKey this._skipNull(packet.data)
116 | .then (key) =>
117 | digest = @token.toString 'binary'
118 | sig = @signature.toString 'binary'
119 | unless key.verify digest, sig
120 | debug "Signature mismatch"
121 | throw new Socket.AuthError "Signature mismatch"
122 | debug "Signature verified"
123 | key
124 | .then (key) =>
125 | @options.auth key
126 | .catch (err) ->
127 | debug "Connection rejected by user-defined auth handler"
128 | throw new Socket.AuthError "Rejected by user-defined handler"
129 | .then =>
130 | this._deviceId()
131 | .then (id) =>
132 | @authorized = true
133 | debug 'O:A_CNXN'
134 | this.write Packet.assemble(Packet.A_CNXN,
135 | Packet.swap32(@version), @maxPayload, id)
136 | else
137 | throw new Error "Unknown authentication method #{packet.arg0}"
138 |
139 | _handleOpenPacket: (packet) ->
140 | throw new Socket.UnauthorizedError() unless @authorized
141 | remoteId = packet.arg0
142 | localId = @remoteId.next()
143 | unless packet.data and packet.data.length >= 2
144 | throw new Error "Empty service name"
145 | name = this._skipNull(packet.data)
146 | debug "Calling #{name}"
147 | service = new Service @client, @serial, localId, remoteId, this
148 | new Promise (resolve, reject) =>
149 | service.on 'error', reject
150 | service.on 'end', resolve
151 | @services.insert localId, service
152 | debug "Handling #{@services.count} services simultaneously"
153 | service.handle packet
154 | .catch (err) ->
155 | true
156 | .finally =>
157 | @services.remove localId
158 | debug "Handling #{@services.count} services simultaneously"
159 | service.end()
160 |
161 | _forwardServicePacket: (packet) ->
162 | throw new Socket.UnauthorizedError() unless @authorized
163 | remoteId = packet.arg0
164 | localId = packet.arg1
165 | if service = @services.get localId
166 | service.handle packet
167 | else
168 | debug "Received a packet to a service that may have been closed already"
169 |
170 | write: (chunk) ->
171 | return if @ended
172 | @socket.write chunk
173 |
174 | _createToken: ->
175 | Promise.promisify(crypto.randomBytes)(TOKEN_LENGTH)
176 |
177 | _skipNull: (data) ->
178 | data.slice 0, -1 # Discard null byte at end
179 |
180 | _deviceId: ->
181 | debug "Loading device properties to form a standard device ID"
182 | @client.getProperties @serial
183 | .then (properties) ->
184 | id = ("#{prop}=#{properties[prop]};" for prop in [
185 | 'ro.product.name'
186 | 'ro.product.model'
187 | 'ro.product.device'
188 | ]).join('')
189 | new Buffer "device::#{id}\0"
190 |
191 | class Socket.AuthError extends Error
192 | constructor: (@message) ->
193 | Error.call this
194 | @name = 'AuthError'
195 | Error.captureStackTrace this, Socket.AuthError
196 |
197 | class Socket.UnauthorizedError extends Error
198 | constructor: ->
199 | Error.call this
200 | @name = 'UnauthorizedError'
201 | @message = "Unauthorized access"
202 | Error.captureStackTrace this, Socket.UnauthorizedError
203 |
204 | module.exports = Socket
205 |
--------------------------------------------------------------------------------
/src/adb/tracker.coffee:
--------------------------------------------------------------------------------
1 | {EventEmitter} = require 'events'
2 | Promise = require 'bluebird'
3 |
4 | Parser = require './parser'
5 |
6 | class Tracker extends EventEmitter
7 | constructor: (@command) ->
8 | @deviceList = []
9 | @deviceMap = {}
10 | @reader = this.read()
11 | .catch Promise.CancellationError, ->
12 | true
13 | .catch Parser.PrematureEOFError, ->
14 | throw new Error 'Connection closed'
15 | .catch (err) =>
16 | this.emit 'error', err
17 | return
18 | .finally =>
19 | @command.parser.end()
20 | .then =>
21 | this.emit 'end'
22 |
23 | read: ->
24 | @command._readDevices()
25 | .cancellable()
26 | .then (list) =>
27 | this.update list
28 | this.read()
29 |
30 | update: (newList) ->
31 | changeSet =
32 | removed: []
33 | changed: []
34 | added: []
35 | newMap = {}
36 | for device in newList
37 | oldDevice = @deviceMap[device.id]
38 | if oldDevice
39 | unless oldDevice.type is device.type
40 | changeSet.changed.push device
41 | this.emit 'change', device, oldDevice
42 | else
43 | changeSet.added.push device
44 | this.emit 'add', device
45 | newMap[device.id] = device
46 | for device in @deviceList
47 | unless newMap[device.id]
48 | changeSet.removed.push device
49 | this.emit 'remove', device
50 | this.emit 'changeSet', changeSet
51 | @deviceList = newList
52 | @deviceMap = newMap
53 | return this
54 |
55 | end: ->
56 | @reader.cancel()
57 | return this
58 |
59 | module.exports = Tracker
60 |
--------------------------------------------------------------------------------
/src/adb/util.coffee:
--------------------------------------------------------------------------------
1 | Parser = require './parser'
2 | Auth = require './auth'
3 |
4 | module.exports.readAll = (stream, callback) ->
5 | new Parser(stream).readAll stream
6 | .nodeify callback
7 |
8 | module.exports.parsePublicKey = (keyString, callback) ->
9 | Auth.parsePublicKey keyString
10 | .nodeify callback
11 |
--------------------------------------------------------------------------------
/src/cli.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 | program = require 'commander'
3 | Promise = require 'bluebird'
4 | forge = require 'node-forge'
5 |
6 | pkg = require '../package'
7 | Adb = require './adb'
8 | Auth = require './adb/auth'
9 | PacketReader = require './adb/tcpusb/packetreader'
10 |
11 | Promise.longStackTraces()
12 |
13 | program
14 | .version pkg.version
15 |
16 | program
17 | .command 'pubkey-convert '
18 | .option '-f, --format ', 'format (pem or openssh)', String, 'pem'
19 | .description 'Converts an ADB-generated public key into PEM format.'
20 | .action (file, options) ->
21 | Auth.parsePublicKey fs.readFileSync file
22 | .then (key) ->
23 | switch options.format.toLowerCase()
24 | when 'pem'
25 | console.log forge.pki.publicKeyToPem(key).trim()
26 | when 'openssh'
27 | console.log forge.ssh.publicKeyToOpenSSH(key, 'adbkey').trim()
28 | else
29 | console.error "Unsupported format '#{options.format}'"
30 | process.exit 1
31 |
32 | program
33 | .command 'pubkey-fingerprint '
34 | .description 'Outputs the fingerprint of an ADB-generated public key.'
35 | .action (file) ->
36 | Auth.parsePublicKey fs.readFileSync file
37 | .then (key) ->
38 | console.log '%s %s', key.fingerprint, key.comment
39 |
40 | program
41 | .command 'usb-device-to-tcp '
42 | .option '-p, --port ', 'port number', String, 6174
43 | .description 'Provides an USB device over TCP using a translating proxy.'
44 | .action (serial, options) ->
45 | adb = Adb.createClient()
46 | server = adb.createTcpUsbBridge(serial, auth: -> Promise.resolve())
47 | .on 'listening', ->
48 | console.info 'Connect with `adb connect localhost:%d`', options.port
49 | .on 'error', (err) ->
50 | console.error "An error occured: #{err.message}"
51 | server.listen options.port
52 |
53 | program
54 | .command 'parse-tcp-packets '
55 | .description 'Parses ADB TCP packets from the given file.'
56 | .action (file, options) ->
57 | reader = new PacketReader fs.createReadStream(file)
58 | reader.on 'packet', (packet) ->
59 | console.log packet.toString()
60 |
61 | program.parse process.argv
62 |
--------------------------------------------------------------------------------
/tasks/keycode.coffee:
--------------------------------------------------------------------------------
1 | Https = require 'https'
2 |
3 | module.exports = (grunt) ->
4 |
5 | grunt.registerMultiTask 'keycode', 'Updates KeyEvent mapping.', ->
6 |
7 | repo_path = '/android/platform_frameworks_base/master'
8 | done = this.async()
9 | options = this.options
10 | original:
11 | hostname: 'raw.github.com'
12 | path: "#{repo_path}/core/java/android/view/KeyEvent.java"
13 | method: 'GET'
14 | regex: /public static final int (KEYCODE_[^\s]+)\s*=\s*([0-9]+);/g
15 |
16 | grunt.util.async.forEach this.files, (file, next) ->
17 | req = Https.request options.original, (res) ->
18 | unless res.statusCode is 200
19 | grunt.fail.warn \
20 | "Unable to retrieve KeyEvent.java (HTTP #{res.statusCode})"
21 | return next()
22 |
23 | raw = new Buffer ''
24 |
25 | res.on 'data', (chunk) ->
26 | raw = Buffer.concat [raw, chunk]
27 |
28 | res.on 'end', ->
29 | code = raw.toString()
30 | date = new Date().toUTCString()
31 | coffee = []
32 | coffee.push "# Generated by `grunt keycode` on #{date}"
33 | coffee.push \
34 | "# KeyEvent.java Copyright (C) 2006 The Android Open Source Project"
35 | coffee.push ''
36 | coffee.push 'module.exports ='
37 |
38 | while match = options.regex.exec code
39 | coffee.push " #{match[1]}: #{match[2]}"
40 |
41 | coffee.push ''
42 |
43 | grunt.file.write file.dest, coffee.join '\n'
44 | grunt.log.ok "File #{file.dest} created"
45 |
46 | next()
47 |
48 | req.on 'error', next
49 |
50 | req.end()
51 |
52 | , done
53 |
--------------------------------------------------------------------------------
/test/adb.coffee:
--------------------------------------------------------------------------------
1 | {expect} = require 'chai'
2 |
3 | Adb = require '../src/adb'
4 | Client = require '../src/adb/client'
5 | Keycode = require '../src/adb/keycode'
6 | util = require '../src/adb/util'
7 |
8 | describe 'Adb', ->
9 |
10 | it "should expose Keycode", (done) ->
11 | expect(Adb).to.have.property 'Keycode'
12 | expect(Adb.Keycode).to.equal Keycode
13 | done()
14 |
15 | it "should expose util", (done) ->
16 | expect(Adb).to.have.property 'util'
17 | expect(Adb.util).to.equal util
18 | done()
19 |
20 | describe '@createClient(options)', ->
21 |
22 | it "should return a Client instance", (done) ->
23 | expect(Adb.createClient()).to.be.an.instanceOf Client
24 | done()
25 |
--------------------------------------------------------------------------------
/test/adb/command/host-serial/waitfordevice.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | WaitForDeviceCommand = \
10 | require '../../../../src/adb/command/host-serial/waitfordevice'
11 |
12 | describe 'WaitForDeviceCommand', ->
13 |
14 | it "should send 'host-serial::wait-for-any'", (done) ->
15 | conn = new MockConnection
16 | cmd = new WaitForDeviceCommand conn
17 | conn.socket.on 'write', (chunk) ->
18 | expect(chunk.toString()).to.equal \
19 | Protocol.encodeData('host-serial:abba:wait-for-any').toString()
20 | setImmediate ->
21 | conn.socket.causeRead Protocol.OKAY
22 | conn.socket.causeRead Protocol.OKAY
23 | conn.socket.causeEnd()
24 | cmd.execute 'abba'
25 | .then ->
26 | done()
27 |
28 | it "should resolve with id when the device is connected", (done) ->
29 | conn = new MockConnection
30 | cmd = new WaitForDeviceCommand conn
31 | setImmediate ->
32 | conn.socket.causeRead Protocol.OKAY
33 | conn.socket.causeRead Protocol.OKAY
34 | conn.socket.causeEnd()
35 | cmd.execute 'abba'
36 | .then (id) ->
37 | expect(id).to.equal 'abba'
38 | done()
39 |
40 | it "should reject with error if unable to connect", (done) ->
41 | conn = new MockConnection
42 | cmd = new WaitForDeviceCommand conn
43 | setImmediate ->
44 | conn.socket.causeRead Protocol.OKAY
45 | conn.socket.causeRead Protocol.FAIL
46 | conn.socket.causeRead \
47 | Protocol.encodeData('not sure how this might happen')
48 | conn.socket.causeEnd()
49 | cmd.execute 'abba'
50 | .catch (err) ->
51 | expect(err.message).to.contain 'not sure how this might happen'
52 | done()
53 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/clear.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | ClearCommand = require '../../../../src/adb/command/host-transport/clear'
10 |
11 | describe 'ClearCommand', ->
12 |
13 | it "should send 'pm clear '", (done) ->
14 | conn = new MockConnection
15 | cmd = new ClearCommand conn
16 | conn.socket.on 'write', (chunk) ->
17 | expect(chunk.toString()).to.equal \
18 | Protocol.encodeData('shell:pm clear foo.bar.c').toString()
19 | conn.socket.causeRead Protocol.OKAY
20 | conn.socket.causeRead 'Success\r\n'
21 | conn.socket.causeEnd()
22 | cmd.execute 'foo.bar.c'
23 | .then ->
24 | done()
25 |
26 | it "should succeed on 'Success'", (done) ->
27 | conn = new MockConnection
28 | cmd = new ClearCommand conn
29 | conn.socket.on 'write', (chunk) ->
30 | conn.socket.causeRead Protocol.OKAY
31 | conn.socket.causeRead 'Success\r\n'
32 | conn.socket.causeEnd()
33 | cmd.execute 'foo.bar.c'
34 | .then ->
35 | done()
36 |
37 | it "should error on 'Failed'", (done) ->
38 | conn = new MockConnection
39 | cmd = new ClearCommand conn
40 | conn.socket.on 'write', (chunk) ->
41 | conn.socket.causeRead Protocol.OKAY
42 | conn.socket.causeRead 'Failed\r\n'
43 | conn.socket.causeEnd()
44 | cmd.execute 'foo.bar.c'
45 | .catch (err) ->
46 | expect(err).to.be.an.instanceof Error
47 | done()
48 |
49 | it "should error on 'Failed' even if connection not closed by
50 | device", (done) ->
51 | conn = new MockConnection
52 | cmd = new ClearCommand conn
53 | conn.socket.on 'write', (chunk) ->
54 | conn.socket.causeRead Protocol.OKAY
55 | conn.socket.causeRead 'Failed\r\n'
56 | cmd.execute 'foo.bar.c'
57 | .catch (err) ->
58 | expect(err).to.be.an.instanceof Error
59 | done()
60 |
61 | it "should ignore irrelevant lines", (done) ->
62 | conn = new MockConnection
63 | cmd = new ClearCommand conn
64 | conn.socket.on 'write', (chunk) ->
65 | conn.socket.causeRead Protocol.OKAY
66 | conn.socket.causeRead 'Open: foo error\n\n'
67 | conn.socket.causeRead 'Success\r\n'
68 | conn.socket.causeEnd()
69 | cmd.execute 'foo.bar.c'
70 | .then ->
71 | done()
72 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/framebuffer.coffee:
--------------------------------------------------------------------------------
1 | Sinon = require 'sinon'
2 | Chai = require 'chai'
3 | Chai.use require 'sinon-chai'
4 | {expect} = Chai
5 |
6 | MockConnection = require '../../../mock/connection'
7 | Protocol = require '../../../../src/adb/protocol'
8 | FrameBufferCommand =
9 | require '../../../../src/adb/command/host-transport/framebuffer'
10 |
11 | describe 'FrameBufferCommand', ->
12 |
13 | it "should send 'framebuffer:'", (done) ->
14 | conn = new MockConnection
15 | cmd = new FrameBufferCommand conn
16 | conn.socket.on 'write', (chunk) ->
17 | expect(chunk.toString()).to.equal \
18 | Protocol.encodeData('framebuffer:').toString()
19 | setImmediate ->
20 | meta = new Buffer 52
21 | meta.fill 0
22 | conn.socket.causeRead Protocol.OKAY
23 | conn.socket.causeRead meta
24 | conn.socket.causeEnd()
25 | cmd.execute 'raw'
26 | .then ->
27 | done()
28 |
29 | it "should parse meta header and return it as the 'meta'
30 | property of the stream", (done) ->
31 | conn = new MockConnection
32 | cmd = new FrameBufferCommand conn
33 | conn.socket.on 'write', (chunk) ->
34 | expect(chunk.toString()).to.equal \
35 | Protocol.encodeData('framebuffer:').toString()
36 | setImmediate ->
37 | meta = new Buffer 52
38 | offset = 0
39 | meta.writeUInt32LE 1, offset
40 | meta.writeUInt32LE 32, offset += 4
41 | meta.writeUInt32LE 819200, offset += 4
42 | meta.writeUInt32LE 640, offset += 4
43 | meta.writeUInt32LE 320, offset += 4
44 | meta.writeUInt32LE 0, offset += 4
45 | meta.writeUInt32LE 8, offset += 4
46 | meta.writeUInt32LE 16, offset += 4
47 | meta.writeUInt32LE 8, offset += 4
48 | meta.writeUInt32LE 8, offset += 4
49 | meta.writeUInt32LE 8, offset += 4
50 | meta.writeUInt32LE 24, offset += 4
51 | meta.writeUInt32LE 8, offset += 4
52 | conn.socket.causeRead Protocol.OKAY
53 | conn.socket.causeRead meta
54 | conn.socket.causeEnd()
55 | cmd.execute 'raw'
56 | .then (stream) ->
57 | expect(stream).to.have.property 'meta'
58 | expect(stream.meta).to.eql
59 | version: 1
60 | bpp: 32
61 | size: 819200
62 | width: 640
63 | height: 320
64 | red_offset: 0
65 | red_length: 8
66 | blue_offset: 16
67 | blue_length: 8
68 | green_offset: 8
69 | green_length: 8
70 | alpha_offset: 24
71 | alpha_length: 8
72 | format: 'rgba'
73 | done()
74 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/getfeatures.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | GetFeaturesCommand =
10 | require '../../../../src/adb/command/host-transport/getfeatures'
11 |
12 | describe 'GetFeaturesCommand', ->
13 |
14 | it "should send 'pm list features'", (done) ->
15 | conn = new MockConnection
16 | cmd = new GetFeaturesCommand conn
17 | conn.socket.on 'write', (chunk) ->
18 | expect(chunk.toString()).to.equal \
19 | Protocol.encodeData('shell:pm list features 2>/dev/null').toString()
20 | setImmediate ->
21 | conn.socket.causeRead Protocol.OKAY
22 | conn.socket.causeEnd()
23 | cmd.execute()
24 | .then ->
25 | done()
26 |
27 | it "should return an empty object for an empty feature list", (done) ->
28 | conn = new MockConnection
29 | cmd = new GetFeaturesCommand conn
30 | setImmediate ->
31 | conn.socket.causeRead Protocol.OKAY
32 | conn.socket.causeEnd()
33 | cmd.execute()
34 | .then (features) ->
35 | expect(Object.keys(features)).to.be.empty
36 | done()
37 |
38 | it "should return a map of features", (done) ->
39 | conn = new MockConnection
40 | cmd = new GetFeaturesCommand conn
41 | setImmediate ->
42 | conn.socket.causeRead Protocol.OKAY
43 | conn.socket.causeRead """
44 | feature:reqGlEsVersion=0x20000
45 | feature:foo\r
46 | feature:bar
47 | """
48 | conn.socket.causeEnd()
49 | cmd.execute()
50 | .then (features) ->
51 | expect(Object.keys(features)).to.have.length 3
52 | expect(features).to.eql
53 | reqGlEsVersion: '0x20000'
54 | foo: true
55 | bar: true
56 | done()
57 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/getpackages.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | GetPackagesCommand =
10 | require '../../../../src/adb/command/host-transport/getpackages'
11 |
12 | describe 'GetPackagesCommand', ->
13 |
14 | it "should send 'pm list packages'", (done) ->
15 | conn = new MockConnection
16 | cmd = new GetPackagesCommand conn
17 | conn.socket.on 'write', (chunk) ->
18 | expect(chunk.toString()).to.equal \
19 | Protocol.encodeData('shell:pm list packages 2>/dev/null').toString()
20 | setImmediate ->
21 | conn.socket.causeRead Protocol.OKAY
22 | conn.socket.causeEnd()
23 | cmd.execute()
24 | .then ->
25 | done()
26 |
27 | it "should return an empty array for an empty package list", (done) ->
28 | conn = new MockConnection
29 | cmd = new GetPackagesCommand conn
30 | setImmediate ->
31 | conn.socket.causeRead Protocol.OKAY
32 | conn.socket.causeEnd()
33 | cmd.execute()
34 | .then (packages) ->
35 | expect(packages).to.be.empty
36 | done()
37 |
38 | it "should return an array of packages", (done) ->
39 | conn = new MockConnection
40 | cmd = new GetPackagesCommand conn
41 | setImmediate ->
42 | conn.socket.causeRead Protocol.OKAY
43 | conn.socket.causeRead """
44 | package:com.google.android.gm
45 | package:com.google.android.inputmethod.japanese
46 | package:com.google.android.tag\r
47 | package:com.google.android.GoogleCamera
48 | package:com.google.android.youtube
49 | package:com.google.android.apps.magazines
50 | package:com.google.earth
51 | """
52 | conn.socket.causeEnd()
53 | cmd.execute()
54 | .then (packages) ->
55 | expect(packages).to.have.length 7
56 | expect(packages).to.eql [
57 | 'com.google.android.gm'
58 | 'com.google.android.inputmethod.japanese'
59 | 'com.google.android.tag'
60 | 'com.google.android.GoogleCamera',
61 | 'com.google.android.youtube',
62 | 'com.google.android.apps.magazines',
63 | 'com.google.earth'
64 | ]
65 | done()
66 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/getproperties.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | GetPropertiesCommand =
10 | require '../../../../src/adb/command/host-transport/getproperties'
11 |
12 | describe 'GetPropertiesCommand', ->
13 |
14 | it "should send 'getprop'", (done) ->
15 | conn = new MockConnection
16 | cmd = new GetPropertiesCommand conn
17 | conn.socket.on 'write', (chunk) ->
18 | expect(chunk.toString()).to.equal \
19 | Protocol.encodeData('shell:getprop').toString()
20 | setImmediate ->
21 | conn.socket.causeRead Protocol.OKAY
22 | conn.socket.causeEnd()
23 | cmd.execute()
24 | .then ->
25 | done()
26 |
27 | it "should return an empty object for an empty property list", (done) ->
28 | conn = new MockConnection
29 | cmd = new GetPropertiesCommand conn
30 | setImmediate ->
31 | conn.socket.causeRead Protocol.OKAY
32 | conn.socket.causeEnd()
33 | cmd.execute()
34 | .then (properties) ->
35 | expect(Object.keys(properties)).to.be.empty
36 | done()
37 |
38 | it "should return a map of properties", (done) ->
39 | conn = new MockConnection
40 | cmd = new GetPropertiesCommand conn
41 | setImmediate ->
42 | conn.socket.causeRead Protocol.OKAY
43 | conn.socket.causeRead """
44 | [ro.product.locale.region]: [US]
45 | [ro.product.manufacturer]: [samsung]\r
46 | [ro.product.model]: [SC-04E]
47 | [ro.product.name]: [SC-04E]
48 | """
49 | conn.socket.causeEnd()
50 | cmd.execute()
51 | .then (properties) ->
52 | expect(Object.keys(properties)).to.have.length 4
53 | expect(properties).to.eql
54 | 'ro.product.locale.region': 'US'
55 | 'ro.product.manufacturer': 'samsung'
56 | 'ro.product.model': 'SC-04E'
57 | 'ro.product.name': 'SC-04E'
58 | done()
59 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/install.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | InstallCommand =
10 | require '../../../../src/adb/command/host-transport/install'
11 |
12 | describe 'InstallCommand', ->
13 |
14 | it "should send 'pm install -r '", (done) ->
15 | conn = new MockConnection
16 | cmd = new InstallCommand conn
17 | conn.socket.on 'write', (chunk) ->
18 | expect(chunk.toString()).to.equal \
19 | Protocol.encodeData('shell:pm install -r "foo"').toString()
20 | setImmediate ->
21 | conn.socket.causeRead Protocol.OKAY
22 | conn.socket.causeRead 'Success\r\n'
23 | conn.socket.causeEnd()
24 | cmd.execute 'foo'
25 | .then ->
26 | done()
27 |
28 | it "should succeed when command responds with 'Success'", (done) ->
29 | conn = new MockConnection
30 | cmd = new InstallCommand conn
31 | setImmediate ->
32 | conn.socket.causeRead Protocol.OKAY
33 | conn.socket.causeRead 'Success\r\n'
34 | conn.socket.causeEnd()
35 | cmd.execute 'foo'
36 | .then ->
37 | done()
38 |
39 | it "should reject if command responds with 'Failure [REASON]'", (done) ->
40 | conn = new MockConnection
41 | cmd = new InstallCommand conn
42 | setImmediate ->
43 | conn.socket.causeRead Protocol.OKAY
44 | conn.socket.causeRead 'Failure [BAR]\r\n'
45 | conn.socket.causeEnd()
46 | cmd.execute 'foo'
47 | .catch (err) ->
48 | done()
49 |
50 | it "should give detailed reason in rejection's code property", (done) ->
51 | conn = new MockConnection
52 | cmd = new InstallCommand conn
53 | setImmediate ->
54 | conn.socket.causeRead Protocol.OKAY
55 | conn.socket.causeRead 'Failure [ALREADY_EXISTS]\r\n'
56 | conn.socket.causeEnd()
57 | cmd.execute 'foo'
58 | .catch (err) ->
59 | expect(err.code).to.equal 'ALREADY_EXISTS'
60 | done()
61 |
62 | it "should ignore any other data", (done) ->
63 | conn = new MockConnection
64 | cmd = new InstallCommand conn
65 | setImmediate ->
66 | conn.socket.causeRead Protocol.OKAY
67 | conn.socket.causeRead 'open: Permission failed\r\n'
68 | conn.socket.causeRead 'Success\r\n'
69 | conn.socket.causeEnd()
70 | cmd.execute 'foo'
71 | .then ->
72 | done()
73 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/isinstalled.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | IsInstalledCommand =
10 | require '../../../../src/adb/command/host-transport/isinstalled'
11 |
12 | describe 'IsInstalledCommand', ->
13 |
14 | it "should send 'pm path '", (done) ->
15 | conn = new MockConnection
16 | cmd = new IsInstalledCommand conn
17 | conn.socket.on 'write', (chunk) ->
18 | expect(chunk.toString()).to.equal \
19 | Protocol.encodeData("shell:pm path foo 2>/dev/null").toString()
20 | setImmediate ->
21 | conn.socket.causeRead Protocol.OKAY
22 | conn.socket.causeRead 'package:foo\r\n'
23 | conn.socket.causeEnd()
24 | cmd.execute 'foo'
25 | .then ->
26 | done()
27 |
28 | it "should resolve with true if package returned by command", (done) ->
29 | conn = new MockConnection
30 | cmd = new IsInstalledCommand conn
31 | setImmediate ->
32 | conn.socket.causeRead Protocol.OKAY
33 | conn.socket.causeRead 'package:bar\r\n'
34 | conn.socket.causeEnd()
35 | cmd.execute 'foo'
36 | .then (found) ->
37 | expect(found).to.be.true
38 | done()
39 |
40 | it "should resolve with false if no package returned", (done) ->
41 | conn = new MockConnection
42 | cmd = new IsInstalledCommand conn
43 | setImmediate ->
44 | conn.socket.causeRead Protocol.OKAY
45 | conn.socket.causeEnd()
46 | cmd.execute 'foo'
47 | .then (found) ->
48 | expect(found).to.be.false
49 | done()
50 |
51 | it "should fail if any other data is received", (done) ->
52 | conn = new MockConnection
53 | cmd = new IsInstalledCommand conn
54 | setImmediate ->
55 | conn.socket.causeRead Protocol.OKAY
56 | conn.socket.causeRead 'open: Permission failed\r\n'
57 | conn.socket.causeEnd()
58 | cmd.execute 'foo'
59 | .catch (err) ->
60 | expect(err).to.be.an.instanceof Error
61 | done()
62 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/local.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | LocalCommand = require '../../../../src/adb/command/host-transport/local'
10 |
11 | describe 'LocalCommand', ->
12 |
13 | it "should send 'localfilesystem:'", (done) ->
14 | conn = new MockConnection
15 | cmd = new LocalCommand conn
16 | conn.socket.on 'write', (chunk) ->
17 | expect(chunk.toString()).to.equal \
18 | Protocol.encodeData('localfilesystem:/foo.sock').toString()
19 | setImmediate ->
20 | conn.socket.causeRead Protocol.OKAY
21 | conn.socket.causeEnd()
22 | cmd.execute '/foo.sock'
23 | .then (stream) ->
24 | done()
25 |
26 | it "should send ':' if prefixed with ':'", (done) ->
27 | conn = new MockConnection
28 | cmd = new LocalCommand conn
29 | conn.socket.on 'write', (chunk) ->
30 | expect(chunk.toString()).to.equal \
31 | Protocol.encodeData('localabstract:/foo.sock').toString()
32 | setImmediate ->
33 | conn.socket.causeRead Protocol.OKAY
34 | conn.socket.causeEnd()
35 | cmd.execute 'localabstract:/foo.sock'
36 | .then (stream) ->
37 | done()
38 |
39 | it "should resolve with the stream", (done) ->
40 | conn = new MockConnection
41 | cmd = new LocalCommand conn
42 | setImmediate ->
43 | conn.socket.causeRead Protocol.OKAY
44 | cmd.execute '/foo.sock'
45 | .then (stream) ->
46 | stream.end()
47 | expect(stream).to.be.an.instanceof Stream.Readable
48 | done()
49 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/log.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | LogCommand = require '../../../../src/adb/command/host-transport/log'
10 |
11 | describe 'LogCommand', ->
12 |
13 | it "should send 'log:'", (done) ->
14 | conn = new MockConnection
15 | cmd = new LogCommand conn
16 | conn.socket.on 'write', (chunk) ->
17 | expect(chunk.toString()).to.equal \
18 | Protocol.encodeData('log:main').toString()
19 | setImmediate ->
20 | conn.socket.causeRead Protocol.OKAY
21 | conn.socket.causeEnd()
22 | cmd.execute 'main'
23 | .then (stream) ->
24 | done()
25 |
26 | it "should resolve with the log stream", (done) ->
27 | conn = new MockConnection
28 | cmd = new LogCommand conn
29 | setImmediate ->
30 | conn.socket.causeRead Protocol.OKAY
31 | cmd.execute 'main'
32 | .then (stream) ->
33 | stream.end()
34 | expect(stream).to.be.an.instanceof Stream.Readable
35 | done()
36 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/logcat.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Promise = require 'bluebird'
3 | Sinon = require 'sinon'
4 | Chai = require 'chai'
5 | Chai.use require 'sinon-chai'
6 | {expect} = Chai
7 |
8 | MockConnection = require '../../../mock/connection'
9 | Protocol = require '../../../../src/adb/protocol'
10 | Parser = require '../../../../src/adb/parser'
11 | LogcatCommand = require '../../../../src/adb/command/host-transport/logcat'
12 |
13 | describe 'LogcatCommand', ->
14 |
15 | it "should send 'echo && logcat -B *:I'", (done) ->
16 | conn = new MockConnection
17 | cmd = new LogcatCommand conn
18 | conn.socket.on 'write', (chunk) ->
19 | expect(chunk.toString()).to.equal \
20 | Protocol.encodeData('shell:echo &&
21 | logcat -B *:I 2>/dev/null').toString()
22 | setImmediate ->
23 | conn.socket.causeRead Protocol.OKAY
24 | conn.socket.causeEnd()
25 | cmd.execute()
26 | .then (stream) ->
27 | done()
28 |
29 | it "should send 'echo && logcat -c && logcat -B *:I' if options.clear
30 | is set", (done) ->
31 | conn = new MockConnection
32 | cmd = new LogcatCommand conn
33 | conn.socket.on 'write', (chunk) ->
34 | expect(chunk.toString()).to.equal \
35 | Protocol.encodeData('shell:echo && logcat -c 2>/dev/null &&
36 | logcat -B *:I 2>/dev/null').toString()
37 | setImmediate ->
38 | conn.socket.causeRead Protocol.OKAY
39 | conn.socket.causeEnd()
40 | cmd.execute clear: true
41 | .then (stream) ->
42 | done()
43 |
44 | it "should resolve with the logcat stream", (done) ->
45 | conn = new MockConnection
46 | cmd = new LogcatCommand conn
47 | setImmediate ->
48 | conn.socket.causeRead Protocol.OKAY
49 | cmd.execute()
50 | .then (stream) ->
51 | stream.end()
52 | expect(stream).to.be.an.instanceof Stream.Readable
53 | done()
54 |
55 | it "should perform CRLF transformation by default", (done) ->
56 | conn = new MockConnection
57 | cmd = new LogcatCommand conn
58 | setImmediate ->
59 | conn.socket.causeRead Protocol.OKAY
60 | conn.socket.causeRead '\r\nfoo\r\n'
61 | conn.socket.causeEnd()
62 | cmd.execute()
63 | .then (stream) ->
64 | new Parser(stream).readAll()
65 | .then (out) ->
66 | expect(out.toString()).to.equal 'foo\n'
67 | done()
68 |
69 | it "should not perform CRLF transformation if not needed", (done) ->
70 | conn = new MockConnection
71 | cmd = new LogcatCommand conn
72 | setImmediate ->
73 | conn.socket.causeRead Protocol.OKAY
74 | conn.socket.causeRead '\nfoo\r\n'
75 | conn.socket.causeEnd()
76 | cmd.execute()
77 | .then (stream) ->
78 | new Parser(stream).readAll()
79 | .then (out) ->
80 | expect(out.toString()).to.equal 'foo\r\n'
81 | done()
82 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/monkey.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Promise = require 'bluebird'
3 | Sinon = require 'sinon'
4 | Chai = require 'chai'
5 | Chai.use require 'sinon-chai'
6 | {expect} = Chai
7 |
8 | MockConnection = require '../../../mock/connection'
9 | Protocol = require '../../../../src/adb/protocol'
10 | MonkeyCommand = require '../../../../src/adb/command/host-transport/monkey'
11 |
12 | describe 'MonkeyCommand', ->
13 |
14 | it "should send 'monkey --port -v'", (done) ->
15 | conn = new MockConnection
16 | cmd = new MonkeyCommand conn
17 | conn.socket.on 'write', (chunk) ->
18 | expect(chunk.toString()).to.equal \
19 | Protocol.encodeData('shell:EXTERNAL_STORAGE=/data/local/tmp monkey
20 | --port 1080 -v').toString()
21 | setImmediate ->
22 | conn.socket.causeRead Protocol.OKAY
23 | conn.socket.causeRead ':Monkey: foo\n'
24 | cmd.execute 1080
25 | .then (stream) ->
26 | done()
27 |
28 | it "should resolve with the output stream", (done) ->
29 | conn = new MockConnection
30 | cmd = new MonkeyCommand conn
31 | setImmediate ->
32 | conn.socket.causeRead Protocol.OKAY
33 | conn.socket.causeRead ':Monkey: foo\n'
34 | cmd.execute 1080
35 | .then (stream) ->
36 | stream.end()
37 | expect(stream).to.be.an.instanceof Stream.Readable
38 | done()
39 |
40 | it "should resolve after a timeout if result can't be judged from
41 | output", (done) ->
42 | conn = new MockConnection
43 | cmd = new MonkeyCommand conn
44 | setImmediate ->
45 | conn.socket.causeRead Protocol.OKAY
46 | cmd.execute 1080
47 | .then (stream) ->
48 | stream.end()
49 | expect(stream).to.be.an.instanceof Stream.Readable
50 | done()
51 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/reboot.coffee:
--------------------------------------------------------------------------------
1 | Sinon = require 'sinon'
2 | Chai = require 'chai'
3 | Chai.use require 'sinon-chai'
4 | {expect} = Chai
5 |
6 | MockConnection = require '../../../mock/connection'
7 | Protocol = require '../../../../src/adb/protocol'
8 | RebootCommand = require '../../../../src/adb/command/host-transport/reboot'
9 |
10 | describe 'RebootCommand', ->
11 |
12 | it "should send 'reboot:'", (done) ->
13 | conn = new MockConnection
14 | cmd = new RebootCommand conn
15 | conn.socket.on 'write', (chunk) ->
16 | expect(chunk.toString()).to.equal \
17 | Protocol.encodeData('reboot:').toString()
18 | setImmediate ->
19 | conn.socket.causeRead Protocol.OKAY
20 | conn.socket.causeEnd()
21 | cmd.execute()
22 | .then ->
23 | done()
24 |
25 | it "should send wait for the connection to end", (done) ->
26 | conn = new MockConnection
27 | cmd = new RebootCommand conn
28 | ended = false
29 | conn.socket.on 'write', (chunk) ->
30 | expect(chunk.toString()).to.equal \
31 | Protocol.encodeData('reboot:').toString()
32 | setImmediate ->
33 | conn.socket.causeRead Protocol.OKAY
34 | setImmediate ->
35 | ended = true
36 | conn.socket.causeEnd()
37 | cmd.execute()
38 | .then ->
39 | expect(ended).to.be.true
40 | done()
41 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/remount.coffee:
--------------------------------------------------------------------------------
1 | Sinon = require 'sinon'
2 | Chai = require 'chai'
3 | Chai.use require 'sinon-chai'
4 | {expect} = Chai
5 |
6 | MockConnection = require '../../../mock/connection'
7 | Protocol = require '../../../../src/adb/protocol'
8 | RemountCommand = require '../../../../src/adb/command/host-transport/remount'
9 |
10 | describe 'RemountCommand', ->
11 |
12 | it "should send 'remount:'", (done) ->
13 | conn = new MockConnection
14 | cmd = new RemountCommand conn
15 | conn.socket.on 'write', (chunk) ->
16 | expect(chunk.toString()).to.equal \
17 | Protocol.encodeData('remount:').toString()
18 | conn.socket.causeRead Protocol.OKAY
19 | conn.socket.causeEnd()
20 | cmd.execute()
21 | .then ->
22 | done()
23 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/root.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | RootCommand = require '../../../../src/adb/command/host-transport/root'
10 |
11 | describe 'RootCommand', ->
12 |
13 | it "should send 'root:'", (done) ->
14 | conn = new MockConnection
15 | cmd = new RootCommand conn
16 | conn.socket.on 'write', (chunk) ->
17 | expect(chunk.toString()).to.equal \
18 | Protocol.encodeData('root:').toString()
19 | setImmediate ->
20 | conn.socket.causeRead Protocol.OKAY
21 | conn.socket.causeRead "restarting adbd as root\n"
22 | conn.socket.causeEnd()
23 | cmd.execute()
24 | .then (val) ->
25 | expect(val).to.be.true
26 | done()
27 |
28 | it "should reject on unexpected reply", (done) ->
29 | conn = new MockConnection
30 | cmd = new RootCommand conn
31 | setImmediate ->
32 | conn.socket.causeRead Protocol.OKAY
33 | conn.socket.causeRead "adbd cannot run as root in production builds\n"
34 | conn.socket.causeEnd()
35 | cmd.execute()
36 | .catch (err) ->
37 | expect(err.message).to.eql \
38 | 'adbd cannot run as root in production builds'
39 | done()
40 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/screencap.coffee:
--------------------------------------------------------------------------------
1 | Sinon = require 'sinon'
2 | Chai = require 'chai'
3 | Chai.use require 'sinon-chai'
4 | {expect} = Chai
5 |
6 | MockConnection = require '../../../mock/connection'
7 | Protocol = require '../../../../src/adb/protocol'
8 | Parser = require '../../../../src/adb/parser'
9 | ScreencapCommand =
10 | require '../../../../src/adb/command/host-transport/screencap'
11 |
12 | describe 'ScreencapCommand', ->
13 |
14 | it "should send 'screencap -p'", (done) ->
15 | conn = new MockConnection
16 | cmd = new ScreencapCommand conn
17 | conn.socket.on 'write', (chunk) ->
18 | expect(chunk.toString()).to.equal \
19 | Protocol.encodeData('shell:echo && screencap -p 2>/dev/null').toString()
20 | setImmediate ->
21 | conn.socket.causeRead Protocol.OKAY
22 | conn.socket.causeRead '\r\nlegit image'
23 | conn.socket.causeEnd()
24 | cmd.execute()
25 | .then (stream) ->
26 | done()
27 |
28 | it "should resolve with the PNG stream", (done) ->
29 | conn = new MockConnection
30 | cmd = new ScreencapCommand conn
31 | setImmediate ->
32 | conn.socket.causeRead Protocol.OKAY
33 | conn.socket.causeRead '\r\nlegit image'
34 | conn.socket.causeEnd()
35 | cmd.execute()
36 | .then (stream) ->
37 | new Parser(stream).readAll()
38 | .then (out) ->
39 | expect(out.toString()).to.equal 'legit image'
40 | done()
41 |
42 | it "should reject if command not supported", (done) ->
43 | conn = new MockConnection
44 | cmd = new ScreencapCommand conn
45 | setImmediate ->
46 | conn.socket.causeRead Protocol.OKAY
47 | conn.socket.causeEnd()
48 | cmd.execute()
49 | .catch (err) ->
50 | done()
51 |
52 | it "should perform CRLF transformation by default", (done) ->
53 | conn = new MockConnection
54 | cmd = new ScreencapCommand conn
55 | setImmediate ->
56 | conn.socket.causeRead Protocol.OKAY
57 | conn.socket.causeRead '\r\nfoo\r\n'
58 | conn.socket.causeEnd()
59 | cmd.execute()
60 | .then (stream) ->
61 | new Parser(stream).readAll()
62 | .then (out) ->
63 | expect(out.toString()).to.equal 'foo\n'
64 | done()
65 |
66 | it "should not perform CRLF transformation if not needed", (done) ->
67 | conn = new MockConnection
68 | cmd = new ScreencapCommand conn
69 | setImmediate ->
70 | conn.socket.causeRead Protocol.OKAY
71 | conn.socket.causeRead '\nfoo\r\n'
72 | conn.socket.causeEnd()
73 | cmd.execute()
74 | .then (stream) ->
75 | new Parser(stream).readAll()
76 | .then (out) ->
77 | expect(out.toString()).to.equal 'foo\r\n'
78 | done()
79 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/shell.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | Parser = require '../../../../src/adb/parser'
10 | ShellCommand =
11 | require '../../../../src/adb/command/host-transport/shell'
12 |
13 | describe 'ShellCommand', ->
14 |
15 | it "should pass String commands as-is", (done) ->
16 | conn = new MockConnection
17 | cmd = new ShellCommand conn
18 | conn.socket.on 'write', (chunk) ->
19 | expect(chunk.toString()).to.equal \
20 | Protocol.encodeData('shell:foo \'bar').toString()
21 | setImmediate ->
22 | conn.socket.causeRead Protocol.OKAY
23 | conn.socket.causeEnd()
24 | cmd.execute 'foo \'bar'
25 | .then (out) ->
26 | done()
27 |
28 | it "should escape Array commands", (done) ->
29 | conn = new MockConnection
30 | cmd = new ShellCommand conn
31 | conn.socket.on 'write', (chunk) ->
32 | expect(chunk.toString()).to.equal \
33 | Protocol.encodeData("""shell:'foo' ''"'"'bar'"'"'' '"'""").toString()
34 | setImmediate ->
35 | conn.socket.causeRead Protocol.OKAY
36 | conn.socket.causeEnd()
37 | cmd.execute ['foo', '\'bar\'', '"']
38 | .then (out) ->
39 | done()
40 |
41 | it "should not escape numbers in arguments", (done) ->
42 | conn = new MockConnection
43 | cmd = new ShellCommand conn
44 | conn.socket.on 'write', (chunk) ->
45 | expect(chunk.toString()).to.equal \
46 | Protocol.encodeData("""shell:'foo' 67""").toString()
47 | setImmediate ->
48 | conn.socket.causeRead Protocol.OKAY
49 | conn.socket.causeEnd()
50 | cmd.execute ['foo', 67]
51 | .then (out) ->
52 | done()
53 |
54 | it "should reject with FailError on ADB failure (not command
55 | failure)", (done) ->
56 | conn = new MockConnection
57 | cmd = new ShellCommand conn
58 | conn.socket.on 'write', (chunk) ->
59 | expect(chunk.toString()).to.equal \
60 | Protocol.encodeData("""shell:'foo'""").toString()
61 | setImmediate ->
62 | conn.socket.causeRead Protocol.FAIL
63 | conn.socket.causeRead Protocol.encodeData 'mystery'
64 | conn.socket.causeEnd()
65 | cmd.execute ['foo']
66 | .catch Parser.FailError, (err) ->
67 | done()
68 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/startservice.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | StartServiceCommand = require \
10 | '../../../../src/adb/command/host-transport/startservice'
11 |
12 | describe 'StartServiceCommand', ->
13 |
14 | it "should succeed when 'Success' returned", (done) ->
15 | conn = new MockConnection
16 | cmd = new StartServiceCommand conn
17 | setImmediate ->
18 | conn.socket.causeRead Protocol.OKAY
19 | conn.socket.causeRead 'Success'
20 | conn.socket.causeEnd()
21 | options =
22 | component: 'com.dummy.component/.Main'
23 | cmd.execute options
24 | .then ->
25 | done()
26 |
27 | it "should fail when 'Error' returned", (done) ->
28 | conn = new MockConnection
29 | cmd = new StartServiceCommand conn
30 | setImmediate ->
31 | conn.socket.causeRead Protocol.OKAY
32 | conn.socket.causeRead 'Error: foo\n'
33 | conn.socket.causeEnd()
34 | options =
35 | component: 'com.dummy.component/.Main'
36 | cmd.execute options
37 | .catch (err) ->
38 | expect(err).to.be.be.an.instanceOf Error
39 | done()
40 |
41 | it "should send 'am startservice --user 0 -n '", (done) ->
42 | conn = new MockConnection
43 | cmd = new StartServiceCommand conn
44 | conn.socket.on 'write', (chunk) ->
45 | expect(chunk.toString()).to.equal \
46 | Protocol.encodeData("shell:am startservice
47 | -n 'com.dummy.component/.Main'
48 | --user 0").toString()
49 | setImmediate ->
50 | conn.socket.causeRead Protocol.OKAY
51 | conn.socket.causeRead 'Success\n'
52 | conn.socket.causeEnd()
53 | options =
54 | component: 'com.dummy.component/.Main'
55 | user: 0
56 | cmd.execute options
57 | .then ->
58 | done()
59 |
60 | it "should not send user option if not set'", (done) ->
61 | conn = new MockConnection
62 | cmd = new StartServiceCommand conn
63 | conn.socket.on 'write', (chunk) ->
64 | expect(chunk.toString()).to.equal \
65 | Protocol.encodeData("shell:am startservice
66 | -n 'com.dummy.component/.Main'").toString()
67 | setImmediate ->
68 | conn.socket.causeRead Protocol.OKAY
69 | conn.socket.causeRead 'Success\n'
70 | conn.socket.causeEnd()
71 | options =
72 | component: 'com.dummy.component/.Main'
73 | cmd.execute options
74 | .then ->
75 | done()
76 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/sync.coffee:
--------------------------------------------------------------------------------
1 | Sinon = require 'sinon'
2 | Chai = require 'chai'
3 | Chai.use require 'sinon-chai'
4 | {expect} = Chai
5 |
6 | MockConnection = require '../../../mock/connection'
7 | Protocol = require '../../../../src/adb/protocol'
8 | SyncCommand = require '../../../../src/adb/command/host-transport/sync'
9 |
10 | describe 'SyncCommand', ->
11 |
12 | it "should send 'sync:'", (done) ->
13 | conn = new MockConnection
14 | cmd = new SyncCommand conn
15 | conn.socket.on 'write', (chunk) ->
16 | expect(chunk.toString()).to.equal \
17 | Protocol.encodeData('sync:').toString()
18 | conn.socket.causeRead Protocol.OKAY
19 | conn.socket.causeEnd()
20 | cmd.execute()
21 | .then ->
22 | done()
23 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/tcp.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | TcpCommand = require '../../../../src/adb/command/host-transport/tcp'
10 |
11 | describe 'TcpCommand', ->
12 |
13 | it "should send 'tcp:' when no host given", (done) ->
14 | conn = new MockConnection
15 | cmd = new TcpCommand conn
16 | conn.socket.on 'write', (chunk) ->
17 | expect(chunk.toString()).to.equal \
18 | Protocol.encodeData('tcp:8080').toString()
19 | setImmediate ->
20 | conn.socket.causeRead Protocol.OKAY
21 | conn.socket.causeEnd()
22 | cmd.execute 8080
23 | .then (stream) ->
24 | done()
25 |
26 | it "should send 'tcp::' when host given", (done) ->
27 | conn = new MockConnection
28 | cmd = new TcpCommand conn
29 | conn.socket.on 'write', (chunk) ->
30 | expect(chunk.toString()).to.equal \
31 | Protocol.encodeData('tcp:8080:127.0.0.1').toString()
32 | setImmediate ->
33 | conn.socket.causeRead Protocol.OKAY
34 | conn.socket.causeEnd()
35 | cmd.execute 8080, '127.0.0.1'
36 | .then (stream) ->
37 | done()
38 |
39 | it "should resolve with the tcp stream", (done) ->
40 | conn = new MockConnection
41 | cmd = new TcpCommand conn
42 | setImmediate ->
43 | conn.socket.causeRead Protocol.OKAY
44 | cmd.execute 8080
45 | .then (stream) ->
46 | stream.end()
47 | expect(stream).to.be.an.instanceof Stream.Readable
48 | done()
49 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/tcpip.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | TcpIpCommand = require '../../../../src/adb/command/host-transport/tcpip'
10 |
11 | describe 'TcpIpCommand', ->
12 |
13 | it "should send 'tcp:'", (done) ->
14 | conn = new MockConnection
15 | cmd = new TcpIpCommand conn
16 | conn.socket.on 'write', (chunk) ->
17 | expect(chunk.toString()).to.equal \
18 | Protocol.encodeData('tcpip:5555').toString()
19 | setImmediate ->
20 | conn.socket.causeRead Protocol.OKAY
21 | conn.socket.causeRead "restarting in TCP mode port: 5555\n"
22 | conn.socket.causeEnd()
23 | cmd.execute 5555
24 | .then ->
25 | done()
26 |
27 | it "should resolve with the port", (done) ->
28 | conn = new MockConnection
29 | cmd = new TcpIpCommand conn
30 | setImmediate ->
31 | conn.socket.causeRead Protocol.OKAY
32 | conn.socket.causeRead "restarting in TCP mode port: 5555\n"
33 | conn.socket.causeEnd()
34 | cmd.execute 5555
35 | .then (port) ->
36 | expect(port).to.equal 5555
37 | done()
38 |
39 | it "should reject on unexpected reply", (done) ->
40 | conn = new MockConnection
41 | cmd = new TcpIpCommand conn
42 | setImmediate ->
43 | conn.socket.causeRead Protocol.OKAY
44 | conn.socket.causeRead "not sure what this could be\n"
45 | conn.socket.causeEnd()
46 | cmd.execute 5555
47 | .catch (err) ->
48 | expect(err.message).to.eql 'not sure what this could be'
49 | done()
50 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/uninstall.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | Parser = require '../../../../src/adb/parser'
10 | UninstallCommand =
11 | require '../../../../src/adb/command/host-transport/uninstall'
12 |
13 | describe 'UninstallCommand', ->
14 |
15 | it "should succeed when command responds with 'Success'", (done) ->
16 | conn = new MockConnection
17 | cmd = new UninstallCommand conn
18 | conn.socket.on 'write', (chunk) ->
19 | expect(chunk.toString()).to.equal \
20 | Protocol.encodeData('shell:pm uninstall foo').toString()
21 | setImmediate ->
22 | conn.socket.causeRead Protocol.OKAY
23 | conn.socket.causeRead 'Success\r\n'
24 | conn.socket.causeEnd()
25 | cmd.execute 'foo'
26 | .then ->
27 | done()
28 |
29 | it "should succeed even if command responds with 'Failure'", (done) ->
30 | conn = new MockConnection
31 | cmd = new UninstallCommand conn
32 | conn.socket.on 'write', (chunk) ->
33 | expect(chunk.toString()).to.equal \
34 | Protocol.encodeData('shell:pm uninstall foo').toString()
35 | setImmediate ->
36 | conn.socket.causeRead Protocol.OKAY
37 | conn.socket.causeRead 'Failure\r\n'
38 | conn.socket.causeEnd()
39 | cmd.execute 'foo'
40 | .then ->
41 | done()
42 |
43 | it "should succeed even if command responds with 'Failure'
44 | with info in standard format", (done) ->
45 | conn = new MockConnection
46 | cmd = new UninstallCommand conn
47 | conn.socket.on 'write', (chunk) ->
48 | expect(chunk.toString()).to.equal \
49 | Protocol.encodeData('shell:pm uninstall foo').toString()
50 | setImmediate ->
51 | conn.socket.causeRead Protocol.OKAY
52 | conn.socket.causeRead 'Failure [DELETE_FAILED_INTERNAL_ERROR]\r\n'
53 | conn.socket.causeEnd()
54 | cmd.execute 'foo'
55 | .then ->
56 | done()
57 |
58 | it "should succeed even if command responds with 'Failure'
59 | with info info in weird format", (done) ->
60 | conn = new MockConnection
61 | cmd = new UninstallCommand conn
62 | setImmediate ->
63 | conn.socket.causeRead Protocol.OKAY
64 | conn.socket.causeRead 'Failure - not installed for 0\r\n'
65 | conn.socket.causeEnd()
66 | cmd.execute 'foo'
67 | .then ->
68 | done()
69 |
70 | it "should succeed even if command responds with a buggy exception", (done) ->
71 | conn = new MockConnection
72 | cmd = new UninstallCommand conn
73 | setImmediate ->
74 | conn.socket.causeRead Protocol.OKAY
75 | # coffeelint: disable=max_line_length
76 | conn.socket.causeRead """
77 |
78 | Exception occurred while dumping:
79 | java.lang.IllegalArgumentException: Unknown package: foo
80 | at com.android.server.pm.Settings.isOrphaned(Settings.java:4134)
81 | at com.android.server.pm.PackageManagerService.isOrphaned(PackageManagerService.java:18066)
82 | at com.android.server.pm.PackageManagerService.deletePackage(PackageManagerService.java:15483)
83 | at com.android.server.pm.PackageInstallerService.uninstall(PackageInstallerService.java:888)
84 | at com.android.server.pm.PackageManagerShellCommand.runUninstall(PackageManagerShellCommand.java:765)
85 | at com.android.server.pm.PackageManagerShellCommand.onCommand(PackageManagerShellCommand.java:113)
86 | at android.os.ShellCommand.exec(ShellCommand.java:94)
87 | at com.android.server.pm.PackageManagerService.onShellCommand(PackageManagerService.java:18324)
88 | at android.os.Binder.shellCommand(Binder.java:468)
89 | at android.os.Binder.onTransact(Binder.java:367)
90 | at android.content.pm.IPackageManager$Stub.onTransact(IPackageManager.java:2387)
91 | at com.android.server.pm.PackageManagerService.onTransact(PackageManagerService.java:3019)
92 | at android.os.Binder.execTransact(Binder.java:565)
93 | """
94 | # coffeelint: enable=max_line_length
95 | conn.socket.causeEnd()
96 | cmd.execute 'foo'
97 | .then ->
98 | done()
99 |
100 | it "should reject with Parser.PrematureEOFError if stream ends
101 | before match", (done) ->
102 | conn = new MockConnection
103 | cmd = new UninstallCommand conn
104 | setImmediate ->
105 | conn.socket.causeRead Protocol.OKAY
106 | conn.socket.causeRead 'Hello. Is it me you are looking for?\r\n'
107 | conn.socket.causeEnd()
108 | cmd.execute 'foo'
109 | .catch Parser.PrematureEOFError, (err) ->
110 | done()
111 |
112 | it "should ignore any other data", (done) ->
113 | conn = new MockConnection
114 | cmd = new UninstallCommand conn
115 | conn.socket.on 'write', (chunk) ->
116 | expect(chunk.toString()).to.equal \
117 | Protocol.encodeData('shell:pm uninstall foo').toString()
118 | setImmediate ->
119 | conn.socket.causeRead Protocol.OKAY
120 | conn.socket.causeRead 'open: Permission failed\r\n'
121 | conn.socket.causeRead 'Failure\r\n'
122 | conn.socket.causeEnd()
123 | cmd.execute 'foo'
124 | .then ->
125 | done()
126 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/usb.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | UsbCommand = require '../../../../src/adb/command/host-transport/usb'
10 |
11 | describe 'UsbCommand', ->
12 |
13 | it "should send 'usb:'", (done) ->
14 | conn = new MockConnection
15 | cmd = new UsbCommand conn
16 | conn.socket.on 'write', (chunk) ->
17 | expect(chunk.toString()).to.equal \
18 | Protocol.encodeData('usb:').toString()
19 | setImmediate ->
20 | conn.socket.causeRead Protocol.OKAY
21 | conn.socket.causeRead "restarting in USB mode\n"
22 | conn.socket.causeEnd()
23 | cmd.execute()
24 | .then (val) ->
25 | expect(val).to.be.true
26 | done()
27 |
28 | it "should reject on unexpected reply", (done) ->
29 | conn = new MockConnection
30 | cmd = new UsbCommand conn
31 | setImmediate ->
32 | conn.socket.causeRead Protocol.OKAY
33 | conn.socket.causeRead "invalid port\n"
34 | conn.socket.causeEnd()
35 | cmd.execute()
36 | .catch (err) ->
37 | expect(err.message).to.eql 'invalid port'
38 | done()
39 |
--------------------------------------------------------------------------------
/test/adb/command/host-transport/waitbootcomplete.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | Parser = require '../../../../src/adb/parser'
10 | WaitBootCompleteCommand =
11 | require '../../../../src/adb/command/host-transport/waitbootcomplete'
12 |
13 | describe 'WaitBootCompleteCommand', ->
14 |
15 | it "should send a while loop with boot check", (done) ->
16 | conn = new MockConnection
17 | cmd = new WaitBootCompleteCommand conn
18 | want =
19 | 'shell:while getprop sys.boot_completed 2>/dev/null; do sleep 1; done'
20 | conn.socket.on 'write', (chunk) ->
21 | expect(chunk.toString()).to.equal \
22 | Protocol.encodeData(want).toString()
23 | setImmediate ->
24 | conn.socket.causeRead Protocol.OKAY
25 | conn.socket.causeRead '1\r\n'
26 | conn.socket.causeEnd()
27 | cmd.execute()
28 | .then ->
29 | done()
30 |
31 | it "should reject with Parser.PrematureEOFError if connection cuts
32 | prematurely", (done) ->
33 | conn = new MockConnection
34 | cmd = new WaitBootCompleteCommand conn
35 | setImmediate ->
36 | conn.socket.causeRead Protocol.OKAY
37 | conn.socket.causeEnd()
38 | cmd.execute()
39 | .then ->
40 | done new Error 'Succeeded even though it should not'
41 | .catch Parser.PrematureEOFError, (err) ->
42 | done()
43 |
44 | it "should not return until boot is complete", (done) ->
45 | conn = new MockConnection
46 | cmd = new WaitBootCompleteCommand conn
47 | sent = false
48 | setImmediate ->
49 | conn.socket.causeRead Protocol.OKAY
50 | conn.socket.causeRead '\r\n'
51 | conn.socket.causeRead '\r\n'
52 | conn.socket.causeRead '\r\n'
53 | conn.socket.causeRead '\r\n'
54 | conn.socket.causeRead '\r\n'
55 | conn.socket.causeRead '\r\n'
56 | conn.socket.causeRead '\r\n'
57 | conn.socket.causeRead '\r\n'
58 | conn.socket.causeRead '\r\n'
59 | conn.socket.causeRead '\r\n'
60 | setTimeout ->
61 | sent = true
62 | conn.socket.causeRead '1\r\n'
63 | , 50
64 | cmd.execute()
65 | .then ->
66 | expect(sent).to.be.true
67 | done()
68 |
69 | it "should close connection when done", (done) ->
70 | conn = new MockConnection
71 | cmd = new WaitBootCompleteCommand conn
72 | setImmediate ->
73 | conn.socket.causeRead Protocol.OKAY
74 | conn.socket.causeRead '1\r\n'
75 | conn.socket.on 'end', ->
76 | done()
77 | cmd.execute()
78 |
--------------------------------------------------------------------------------
/test/adb/command/host/connect.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | ConnectCommand = require '../../../../src/adb/command/host/connect'
10 |
11 | describe 'ConnectCommand', ->
12 |
13 | it "should send 'host:connect::'", (done) ->
14 | conn = new MockConnection
15 | cmd = new ConnectCommand conn
16 | conn.socket.on 'write', (chunk) ->
17 | expect(chunk.toString()).to.equal \
18 | Protocol.encodeData('host:connect:192.168.2.2:5555').toString()
19 | setImmediate ->
20 | conn.socket.causeRead Protocol.OKAY
21 | conn.socket.causeRead Protocol.encodeData('connected to 192.168.2.2:5555')
22 | conn.socket.causeEnd()
23 | cmd.execute '192.168.2.2', 5555
24 | .then ->
25 | done()
26 |
27 | it "should resolve with the new device id if connected", (done) ->
28 | conn = new MockConnection
29 | cmd = new ConnectCommand conn
30 | setImmediate ->
31 | conn.socket.causeRead Protocol.OKAY
32 | conn.socket.causeRead Protocol.encodeData('connected to 192.168.2.2:5555')
33 | conn.socket.causeEnd()
34 | cmd.execute '192.168.2.2', 5555
35 | .then (val) ->
36 | expect(val).to.be.equal '192.168.2.2:5555'
37 | done()
38 |
39 | it "should resolve with the new device id if already connected", (done) ->
40 | conn = new MockConnection
41 | cmd = new ConnectCommand conn
42 | setImmediate ->
43 | conn.socket.causeRead Protocol.OKAY
44 | conn.socket.causeRead \
45 | Protocol.encodeData('already connected to 192.168.2.2:5555')
46 | conn.socket.causeEnd()
47 | cmd.execute '192.168.2.2', 5555
48 | .then (val) ->
49 | expect(val).to.be.equal '192.168.2.2:5555'
50 | done()
51 |
52 | it "should reject with error if unable to connect", (done) ->
53 | conn = new MockConnection
54 | cmd = new ConnectCommand conn
55 | setImmediate ->
56 | conn.socket.causeRead Protocol.OKAY
57 | conn.socket.causeRead \
58 | Protocol.encodeData('unable to connect to 192.168.2.2:5555')
59 | conn.socket.causeEnd()
60 | cmd.execute '192.168.2.2', 5555
61 | .catch (err) ->
62 | expect(err.message).to.eql 'unable to connect to 192.168.2.2:5555'
63 | done()
64 |
--------------------------------------------------------------------------------
/test/adb/command/host/disconnect.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | MockConnection = require '../../../mock/connection'
8 | Protocol = require '../../../../src/adb/protocol'
9 | DisconnectCommand = require '../../../../src/adb/command/host/disconnect'
10 |
11 | describe 'DisconnectCommand', ->
12 |
13 | it "should send 'host:disconnect::'", (done) ->
14 | conn = new MockConnection
15 | cmd = new DisconnectCommand conn
16 | conn.socket.on 'write', (chunk) ->
17 | expect(chunk.toString()).to.equal \
18 | Protocol.encodeData('host:disconnect:192.168.2.2:5555').toString()
19 | setImmediate ->
20 | conn.socket.causeRead Protocol.OKAY
21 | conn.socket.causeRead Protocol.encodeData('')
22 | conn.socket.causeEnd()
23 | cmd.execute '192.168.2.2', 5555
24 | .then ->
25 | done()
26 |
27 | it "should resolve with the new device id if disconnected", (done) ->
28 | conn = new MockConnection
29 | cmd = new DisconnectCommand conn
30 | setImmediate ->
31 | conn.socket.causeRead Protocol.OKAY
32 | conn.socket.causeRead Protocol.encodeData('')
33 | conn.socket.causeEnd()
34 | cmd.execute '192.168.2.2', 5555
35 | .then (val) ->
36 | expect(val).to.be.equal '192.168.2.2:5555'
37 | done()
38 |
39 | it "should reject with error if unable to disconnect", (done) ->
40 | conn = new MockConnection
41 | cmd = new DisconnectCommand conn
42 | setImmediate ->
43 | conn.socket.causeRead Protocol.OKAY
44 | conn.socket.causeRead \
45 | Protocol.encodeData('No such device 192.168.2.2:5555')
46 | conn.socket.causeEnd()
47 | cmd.execute '192.168.2.2', 5555
48 | .catch (err) ->
49 | expect(err.message).to.eql 'No such device 192.168.2.2:5555'
50 | done()
51 |
--------------------------------------------------------------------------------
/test/adb/command/host/version.coffee:
--------------------------------------------------------------------------------
1 | Sinon = require 'sinon'
2 | Chai = require 'chai'
3 | Chai.use require 'sinon-chai'
4 | {expect} = Chai
5 |
6 | MockConnection = require '../../../mock/connection'
7 | Protocol = require '../../../../src/adb/protocol'
8 | HostVersionCommand = require '../../../../src/adb/command/host/version'
9 |
10 | describe 'HostVersionCommand', ->
11 |
12 | it "should send 'host:version'", (done) ->
13 | conn = new MockConnection
14 | cmd = new HostVersionCommand conn
15 | conn.socket.on 'write', (chunk) ->
16 | expect(chunk.toString()).to.equal \
17 | Protocol.encodeData('host:version').toString()
18 | setImmediate ->
19 | conn.socket.causeRead Protocol.OKAY
20 | conn.socket.causeRead Protocol.encodeData '0000'
21 | conn.socket.causeEnd()
22 | cmd.execute()
23 | .then (version) ->
24 | done()
25 |
26 | it "should resolve with version", (done) ->
27 | conn = new MockConnection
28 | cmd = new HostVersionCommand conn
29 | setImmediate ->
30 | conn.socket.causeRead Protocol.OKAY
31 | conn.socket.causeRead Protocol.encodeData (0x1234).toString 16
32 | conn.socket.causeEnd()
33 | cmd.execute()
34 | .then (version) ->
35 | expect(version).to.equal 0x1234
36 | done()
37 |
38 | it "should handle old-style version", (done) ->
39 | conn = new MockConnection
40 | cmd = new HostVersionCommand conn
41 | setImmediate ->
42 | conn.socket.causeRead (0x1234).toString 16
43 | conn.socket.causeEnd()
44 | cmd.execute()
45 | .then (version) ->
46 | expect(version).to.equal 0x1234
47 | done()
48 |
--------------------------------------------------------------------------------
/test/adb/framebuffer/rgbtransform.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | RgbTransform = require '../../../src/adb/framebuffer/rgbtransform'
8 |
9 | describe 'RgbTransform', ->
10 |
11 | it "should transform BGRA into RGB", (done) ->
12 | meta =
13 | bpp: 32
14 | red_offset: 16
15 | red_length: 8
16 | green_offset: 8
17 | green_length: 8
18 | blue_offset: 0
19 | blue_length: 8
20 | alpha_offset: 24
21 | alpha_length: 8
22 | pixel = new Buffer 4
23 | pixel.writeUInt8 50, 0
24 | pixel.writeUInt8 100, 1
25 | pixel.writeUInt8 150, 2
26 | pixel.writeUInt8 200, 3
27 | stream = new Stream.PassThrough
28 | transform = new RgbTransform meta
29 | stream.pipe transform
30 | transform.on 'data', (chunk) ->
31 | expect(chunk).to.have.length 3
32 | expect(chunk.readUInt8 0).to.equal 150
33 | expect(chunk.readUInt8 1).to.equal 100
34 | expect(chunk.readUInt8 2).to.equal 50
35 | done()
36 | stream.write pixel
37 | stream.end()
38 |
39 | it "should transform BGR into RGB", (done) ->
40 | meta =
41 | bpp: 32
42 | red_offset: 16
43 | red_length: 8
44 | green_offset: 8
45 | green_length: 8
46 | blue_offset: 0
47 | blue_length: 8
48 | alpha_offset: 0
49 | alpha_length: 0
50 | pixel = new Buffer 4
51 | pixel.writeUInt8 50, 0
52 | pixel.writeUInt8 100, 1
53 | pixel.writeUInt8 150, 2
54 | stream = new Stream.PassThrough
55 | transform = new RgbTransform meta
56 | stream.pipe transform
57 | transform.on 'data', (chunk) ->
58 | expect(chunk).to.have.length 3
59 | expect(chunk.readUInt8 0).to.equal 150
60 | expect(chunk.readUInt8 1).to.equal 100
61 | expect(chunk.readUInt8 2).to.equal 50
62 | done()
63 | stream.write pixel
64 | stream.end()
65 |
66 | it "should transform RGB into RGB", (done) ->
67 | meta =
68 | bpp: 24
69 | red_offset: 0
70 | red_length: 8
71 | green_offset: 8
72 | green_length: 8
73 | blue_offset: 16
74 | blue_length: 8
75 | alpha_offset: 0
76 | alpha_length: 0
77 | pixel = new Buffer 3
78 | pixel.writeUInt8 50, 0
79 | pixel.writeUInt8 100, 1
80 | pixel.writeUInt8 150, 2
81 | stream = new Stream.PassThrough
82 | transform = new RgbTransform meta
83 | stream.pipe transform
84 | transform.on 'data', (chunk) ->
85 | expect(chunk).to.have.length 3
86 | expect(chunk.readUInt8 0).to.equal 50
87 | expect(chunk.readUInt8 1).to.equal 100
88 | expect(chunk.readUInt8 2).to.equal 150
89 | done()
90 | stream.write pixel
91 | stream.end()
92 |
93 | it "should transform RGBA into RGB", (done) ->
94 | meta =
95 | bpp: 32
96 | red_offset: 0
97 | red_length: 8
98 | green_offset: 8
99 | green_length: 8
100 | blue_offset: 16
101 | blue_length: 8
102 | alpha_offset: 24
103 | alpha_length: 8
104 | pixel = new Buffer 4
105 | pixel.writeUInt8 50, 0
106 | pixel.writeUInt8 100, 1
107 | pixel.writeUInt8 150, 2
108 | pixel.writeUInt8 200, 3
109 | stream = new Stream.PassThrough
110 | transform = new RgbTransform meta
111 | stream.pipe transform
112 | transform.on 'data', (chunk) ->
113 | expect(chunk).to.have.length 3
114 | expect(chunk.readUInt8 0).to.equal 50
115 | expect(chunk.readUInt8 1).to.equal 100
116 | expect(chunk.readUInt8 2).to.equal 150
117 | done()
118 | stream.write pixel
119 | stream.end()
120 |
121 | it "should wait for a complete pixel before transforming", (done) ->
122 | meta =
123 | bpp: 32
124 | red_offset: 0
125 | red_length: 8
126 | green_offset: 8
127 | green_length: 8
128 | blue_offset: 16
129 | blue_length: 8
130 | alpha_offset: 24
131 | alpha_length: 8
132 | pixel = new Buffer 4
133 | pixel.writeUInt8 50, 0
134 | pixel.writeUInt8 100, 1
135 | pixel.writeUInt8 150, 2
136 | pixel.writeUInt8 200, 3
137 | stream = new Stream.PassThrough
138 | transform = new RgbTransform meta
139 | stream.pipe transform
140 | transform.on 'data', (chunk) ->
141 | expect(chunk).to.have.length 3
142 | expect(chunk.readUInt8 0).to.equal 50
143 | expect(chunk.readUInt8 1).to.equal 100
144 | expect(chunk.readUInt8 2).to.equal 150
145 | done()
146 | stream.write pixel.slice 0, 2
147 | stream.write pixel.slice 2, 3
148 | stream.write pixel.slice 3, 4
149 | stream.end()
150 |
151 | it "should transform a stream of multiple pixels", (done) ->
152 | meta =
153 | bpp: 32
154 | red_offset: 16
155 | red_length: 8
156 | green_offset: 8
157 | green_length: 8
158 | blue_offset: 0
159 | blue_length: 8
160 | alpha_offset: 24
161 | alpha_length: 8
162 | pixel1 = new Buffer 4
163 | pixel1.writeUInt8 50, 0
164 | pixel1.writeUInt8 100, 1
165 | pixel1.writeUInt8 150, 2
166 | pixel1.writeUInt8 200, 3
167 | pixel2 = new Buffer 4
168 | pixel2.writeUInt8 51, 0
169 | pixel2.writeUInt8 101, 1
170 | pixel2.writeUInt8 151, 2
171 | pixel2.writeUInt8 201, 3
172 | stream = new Stream.PassThrough
173 | transform = new RgbTransform meta
174 | stream.pipe transform
175 | all = new Buffer ''
176 | transform.on 'data', (chunk) ->
177 | all = Buffer.concat [all, chunk]
178 | transform.on 'end', ->
179 | expect(all).to.have.length 15
180 | expect(all.readUInt8 0).to.equal 150
181 | expect(all.readUInt8 1).to.equal 100
182 | expect(all.readUInt8 2).to.equal 50
183 | expect(all.readUInt8 3).to.equal 151
184 | expect(all.readUInt8 4).to.equal 101
185 | expect(all.readUInt8 5).to.equal 51
186 | done()
187 | stream.write pixel1
188 | stream.write pixel2
189 | stream.write pixel1
190 | stream.write pixel2
191 | stream.write pixel1
192 | stream.end()
193 |
--------------------------------------------------------------------------------
/test/adb/linetransform.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = Chai
6 |
7 | LineTransform = require '../../src/adb/linetransform'
8 | MockDuplex = require '../mock/duplex'
9 |
10 | describe 'LineTransform', ->
11 |
12 | it "should implement stream.Transform", (done) ->
13 | expect(new LineTransform).to.be.an.instanceOf Stream.Transform
14 | done()
15 |
16 | describe 'with autoDetect', ->
17 | it "should not modify data if first byte is 0x0a", (done) ->
18 | duplex = new MockDuplex
19 | transform = new LineTransform autoDetect: true
20 | transform.on 'data', (data) ->
21 | expect(data.toString()).to.equal 'bar\r\n'
22 | done()
23 | duplex.pipe transform
24 | duplex.causeRead '\nbar\r\n'
25 | duplex.causeEnd()
26 |
27 | it "should not include initial 0x0a", (done) ->
28 | duplex = new MockDuplex
29 | transform = new LineTransform autoDetect: true
30 | buffer = new Buffer ''
31 | transform.on 'data', (data) ->
32 | buffer = Buffer.concat [buffer, data]
33 | transform.on 'end', ->
34 | expect(buffer.toString()).to.equal 'bar\r\n'
35 | done()
36 | duplex.pipe transform
37 | duplex.causeRead '\nbar\r\n'
38 | duplex.causeEnd()
39 |
40 | it "should not include initial 0x0d 0x0a", (done) ->
41 | duplex = new MockDuplex
42 | transform = new LineTransform autoDetect: true
43 | buffer = new Buffer ''
44 | transform.on 'data', (data) ->
45 | buffer = Buffer.concat [buffer, data]
46 | transform.on 'end', ->
47 | expect(buffer.toString()).to.equal 'bar\n'
48 | done()
49 | duplex.pipe transform
50 | duplex.causeRead '\r\nbar\r\n'
51 | duplex.causeEnd()
52 |
53 | it "should not include initial 0x0d 0x0a even if in separate
54 | chunks", (done) ->
55 | duplex = new MockDuplex
56 | transform = new LineTransform autoDetect: true
57 | buffer = new Buffer ''
58 | transform.on 'data', (data) ->
59 | buffer = Buffer.concat [buffer, data]
60 | transform.on 'end', ->
61 | expect(buffer.toString()).to.equal 'bar\n'
62 | done()
63 | duplex.pipe transform
64 | duplex.causeRead '\r'
65 | duplex.causeRead '\nbar\r\n'
66 | duplex.causeEnd()
67 |
68 | it "should transform as usual if first byte is not 0x0a", (done) ->
69 | duplex = new MockDuplex
70 | transform = new LineTransform autoDetect: true
71 | buffer = new Buffer ''
72 | transform.on 'data', (data) ->
73 | buffer = Buffer.concat [buffer, data]
74 | transform.on 'end', ->
75 | expect(buffer.toString()).to.equal 'bar\nfoo'
76 | done()
77 | duplex.pipe transform
78 | duplex.causeRead '\r\nbar\r\nfoo'
79 | duplex.causeEnd()
80 |
81 | describe 'without autoDetect', ->
82 | it "should transform as usual even if first byte is 0x0a", (done) ->
83 | duplex = new MockDuplex
84 | transform = new LineTransform
85 | buffer = new Buffer ''
86 | transform.on 'data', (data) ->
87 | buffer = Buffer.concat [buffer, data]
88 | transform.on 'end', ->
89 | expect(buffer.toString()).to.equal '\n\nbar\nfoo'
90 | done()
91 | duplex.pipe transform
92 | duplex.causeRead '\n\r\nbar\r\nfoo'
93 | duplex.causeEnd()
94 |
95 | it "should not modify data that does not have 0x0d 0x0a in it", (done) ->
96 | duplex = new MockDuplex
97 | transform = new LineTransform
98 | transform.on 'data', (data) ->
99 | expect(data.toString()).to.equal 'foo'
100 | done()
101 | duplex.pipe transform
102 | duplex.causeRead 'foo'
103 | duplex.causeEnd()
104 |
105 | it "should not remove 0x0d if not followed by 0x0a", (done) ->
106 | duplex = new MockDuplex
107 | transform = new LineTransform
108 | transform.on 'data', (data) ->
109 | expect(data.length).to.equal 2
110 | expect(data[0]).to.equal 0x0d
111 | expect(data[1]).to.equal 0x05
112 | done()
113 | duplex.pipe transform
114 | duplex.causeRead new Buffer [0x0d, 0x05]
115 | duplex.causeEnd()
116 |
117 | it "should remove 0x0d if followed by 0x0a", (done) ->
118 | duplex = new MockDuplex
119 | transform = new LineTransform
120 | transform.on 'data', (data) ->
121 | expect(data.length).to.equal 2
122 | expect(data[0]).to.equal 0x0a
123 | expect(data[1]).to.equal 0x97
124 | done()
125 | duplex.pipe transform
126 | duplex.causeRead new Buffer [0x0d, 0x0a, 0x97]
127 | duplex.causeEnd()
128 |
129 | it "should push 0x0d without 0x0a if last in stream", (done) ->
130 | duplex = new MockDuplex
131 | transform = new LineTransform
132 | transform.on 'data', (data) ->
133 | expect(data.length).to.equal 1
134 | expect(data[0]).to.equal 0x0d
135 | done()
136 | duplex.pipe transform
137 | duplex.causeRead new Buffer [0x0d]
138 | duplex.causeEnd()
139 |
140 | it "should push saved 0x0d if next chunk does not start with 0x0a", (done) ->
141 | duplex = new MockDuplex
142 | transform = new LineTransform
143 | buffer = new Buffer ''
144 | transform.on 'data', (data) ->
145 | buffer = Buffer.concat [buffer, data]
146 | transform.on 'end', ->
147 | expect(buffer).to.have.length 3
148 | expect(buffer[0]).to.equal 0x62
149 | expect(buffer[1]).to.equal 0x0d
150 | expect(buffer[2]).to.equal 0x37
151 | done()
152 | duplex.pipe transform
153 | duplex.causeRead new Buffer [0x62, 0x0d]
154 | duplex.causeRead new Buffer [0x37]
155 | duplex.causeEnd()
156 | duplex.end()
157 |
158 | it "should remove saved 0x0d if next chunk starts with 0x0a", (done) ->
159 | duplex = new MockDuplex
160 | transform = new LineTransform
161 | buffer = new Buffer ''
162 | transform.on 'data', (data) ->
163 | buffer = Buffer.concat [buffer, data]
164 | transform.on 'end', ->
165 | expect(buffer).to.have.length 2
166 | expect(buffer[0]).to.equal 0x62
167 | expect(buffer[1]).to.equal 0x0a
168 | done()
169 | duplex.pipe transform
170 | duplex.causeRead new Buffer [0x62, 0x0d]
171 | duplex.causeRead new Buffer [0x0a]
172 | duplex.causeEnd()
173 | duplex.end()
174 |
--------------------------------------------------------------------------------
/test/adb/parser.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Promise = require 'bluebird'
3 | Sinon = require 'sinon'
4 | Chai = require 'chai'
5 | Chai.use require 'sinon-chai'
6 | {expect} = require 'chai'
7 |
8 | Parser = require '../../src/adb/parser'
9 |
10 | describe 'Parser', ->
11 |
12 | describe 'end()', ->
13 |
14 | it "should end the stream and consume all remaining data", (done) ->
15 | stream = new Stream.PassThrough
16 | parser = new Parser stream
17 | stream.write 'F'
18 | stream.write 'O'
19 | stream.write 'O'
20 | parser.end()
21 | .then ->
22 | done()
23 |
24 | describe 'readAll()', ->
25 |
26 | it "should return a cancellable Promise", (done) ->
27 | stream = new Stream.PassThrough
28 | parser = new Parser stream
29 | promise = parser.readAll()
30 | expect(promise).to.be.an.instanceOf Promise
31 | expect(promise.isCancellable()).to.be.true
32 | promise.catch Promise.CancellationError, (err) ->
33 | done()
34 | promise.cancel()
35 |
36 | it "should read all remaining content until the stream ends", (done) ->
37 | stream = new Stream.PassThrough
38 | parser = new Parser stream
39 | parser.readAll()
40 | .then (buf) ->
41 | expect(buf.length).to.equal 3
42 | expect(buf.toString()).to.equal 'FOO'
43 | done()
44 | stream.write 'F'
45 | stream.write 'O'
46 | stream.write 'O'
47 | stream.end()
48 |
49 | it "should resolve with an empty Buffer if the stream has already ended
50 | and there's nothing more to read", (done) ->
51 | stream = new Stream.PassThrough
52 | parser = new Parser stream
53 | parser.readAll()
54 | .then (buf) ->
55 | expect(buf.length).to.equal 0
56 | done()
57 | stream.end()
58 |
59 | describe 'readBytes(howMany)', ->
60 |
61 | it "should return a cancellable Promise", (done) ->
62 | stream = new Stream.PassThrough
63 | parser = new Parser stream
64 | promise = parser.readBytes 1
65 | expect(promise).to.be.an.instanceOf Promise
66 | expect(promise.isCancellable()).to.be.true
67 | promise.catch Promise.CancellationError, (err) ->
68 | done()
69 | promise.cancel()
70 |
71 | it "should read as many bytes as requested", (done) ->
72 | stream = new Stream.PassThrough
73 | parser = new Parser stream
74 | parser.readBytes 4
75 | .then (buf) ->
76 | expect(buf.length).to.equal 4
77 | expect(buf.toString()).to.equal 'OKAY'
78 | parser.readBytes 2
79 | .then (buf) ->
80 | expect(buf).to.have.length 2
81 | expect(buf.toString()).to.equal 'FA'
82 | done()
83 | stream.write 'OKAYFAIL'
84 |
85 | it "should wait for enough data to appear", (done) ->
86 | stream = new Stream.PassThrough
87 | parser = new Parser stream
88 | parser.readBytes 5
89 | .then (buf) ->
90 | expect(buf.toString()).to.equal 'BYTES'
91 | done()
92 | Promise.delay 50
93 | .then ->
94 | stream.write 'BYTES'
95 |
96 | it "should keep data waiting even when nothing has been
97 | requested", (done) ->
98 | stream = new Stream.PassThrough
99 | parser = new Parser stream
100 | stream.write 'FOO'
101 | Promise.delay 50
102 | .then ->
103 | parser.readBytes 2
104 | .then (buf) ->
105 | expect(buf.length).to.equal 2
106 | expect(buf.toString()).to.equal 'FO'
107 | done()
108 |
109 | it "should reject with Parser.PrematureEOFError if stream ends
110 | before enough bytes can be read", (done) ->
111 | stream = new Stream.PassThrough
112 | parser = new Parser stream
113 | stream.write 'F'
114 | parser.readBytes 10
115 | .catch Parser.PrematureEOFError, (err) ->
116 | expect(err.missingBytes).to.equal 9
117 | done()
118 | stream.end()
119 |
120 | describe 'readByteFlow(maxHowMany, targetStream)', ->
121 |
122 | it "should return a cancellable Promise", (done) ->
123 | stream = new Stream.PassThrough
124 | parser = new Parser stream
125 | target = new Stream.PassThrough
126 | promise = parser.readByteFlow 1, target
127 | expect(promise).to.be.an.instanceOf Promise
128 | expect(promise.isCancellable()).to.be.true
129 | promise.catch Promise.CancellationError, (err) ->
130 | done()
131 | promise.cancel()
132 |
133 | it "should read as many bytes as requested", (done) ->
134 | stream = new Stream.PassThrough
135 | parser = new Parser stream
136 | target = new Stream.PassThrough
137 | parser.readByteFlow 4, target
138 | .then ->
139 | expect(target.read()).to.eql new Buffer('OKAY')
140 | parser.readByteFlow 2, target
141 | .then ->
142 | expect(target.read()).to.eql new Buffer('FA')
143 | done()
144 | .catch done
145 | stream.write 'OKAYFAIL'
146 |
147 | it "should progress with new/partial chunk until maxHowMany", (done) ->
148 | stream = new Stream.PassThrough
149 | parser = new Parser stream
150 | target = new Stream.PassThrough
151 | parser.readByteFlow 3, target
152 | .then ->
153 | expect(target.read()).to.eql new Buffer('PIE')
154 | done()
155 | .catch done
156 | b1 = new Buffer 'P'
157 | b2 = new Buffer 'I'
158 | b3 = new Buffer 'ES'
159 | b4 = new Buffer 'R'
160 | stream.write b1
161 | stream.write b2
162 | stream.write b3
163 | stream.write b4
164 |
165 | describe 'readAscii(howMany)', ->
166 |
167 | it "should return a cancellable Promise", (done) ->
168 | stream = new Stream.PassThrough
169 | parser = new Parser stream
170 | promise = parser.readAscii 1
171 | expect(promise).to.be.an.instanceOf Promise
172 | expect(promise.isCancellable()).to.be.true
173 | promise.catch Promise.CancellationError, (err) ->
174 | done()
175 | promise.cancel()
176 |
177 | it "should read as many ascii characters as requested", (done) ->
178 | stream = new Stream.PassThrough
179 | parser = new Parser stream
180 | parser.readAscii 4
181 | .then (str) ->
182 | expect(str.length).to.equal 4
183 | expect(str).to.equal 'OKAY'
184 | done()
185 | stream.write 'OKAYFAIL'
186 |
187 | it "should reject with Parser.PrematureEOFError if stream ends
188 | before enough bytes can be read", (done) ->
189 | stream = new Stream.PassThrough
190 | parser = new Parser stream
191 | stream.write 'FOO'
192 | parser.readAscii 7
193 | .catch Parser.PrematureEOFError, (err) ->
194 | expect(err.missingBytes).to.equal 4
195 | done()
196 | stream.end()
197 |
198 | describe 'readValue()', ->
199 |
200 | it "should return a cancellable Promise", (done) ->
201 | stream = new Stream.PassThrough
202 | parser = new Parser stream
203 | promise = parser.readValue()
204 | expect(promise).to.be.an.instanceOf Promise
205 | expect(promise.isCancellable()).to.be.true
206 | promise.catch Promise.CancellationError, (err) ->
207 | done()
208 | promise.cancel()
209 |
210 | it "should read a protocol value as a Buffer", (done) ->
211 | stream = new Stream.PassThrough
212 | parser = new Parser stream
213 | parser.readValue()
214 | .then (value) ->
215 | expect(value).to.be.an.instanceOf Buffer
216 | expect(value).to.have.length 4
217 | expect(value.toString()).to.equal '001f'
218 | done()
219 | stream.write '0004001f'
220 |
221 | it "should return an empty value", (done) ->
222 | stream = new Stream.PassThrough
223 | parser = new Parser stream
224 | parser.readValue()
225 | .then (value) ->
226 | expect(value).to.be.an.instanceOf Buffer
227 | expect(value).to.have.length 0
228 | done()
229 | stream.write '0000'
230 |
231 | it "should reject with Parser.PrematureEOFError if stream ends
232 | before the value can be read", (done) ->
233 | stream = new Stream.PassThrough
234 | parser = new Parser stream
235 | parser.readValue()
236 | .catch Parser.PrematureEOFError, (err) ->
237 | done()
238 | stream.write '00ffabc'
239 | stream.end()
240 |
241 | describe 'readError()', ->
242 |
243 | it "should return a cancellable Promise", (done) ->
244 | stream = new Stream.PassThrough
245 | parser = new Parser stream
246 | promise = parser.readError()
247 | expect(promise).to.be.an.instanceOf Promise
248 | expect(promise.isCancellable()).to.be.true
249 | promise.catch Promise.CancellationError, (err) ->
250 | done()
251 | promise.cancel()
252 |
253 | it "should reject with Parser.FailError using the value", (done) ->
254 | stream = new Stream.PassThrough
255 | parser = new Parser stream
256 | parser.readError()
257 | .catch Parser.FailError, (err) ->
258 | expect(err.message).to.equal "Failure: 'epic failure'"
259 | done()
260 | stream.write '000cepic failure'
261 |
262 | it "should reject with Parser.PrematureEOFError if stream ends
263 | before the error can be read", (done) ->
264 | stream = new Stream.PassThrough
265 | parser = new Parser stream
266 | parser.readError()
267 | .catch Parser.PrematureEOFError, (err) ->
268 | done()
269 | stream.write '000cepic'
270 | stream.end()
271 |
272 | describe 'searchLine(re)', ->
273 |
274 | it "should return a cancellable Promise", (done) ->
275 | stream = new Stream.PassThrough
276 | parser = new Parser stream
277 | promise = parser.searchLine /foo/
278 | expect(promise).to.be.an.instanceOf Promise
279 | expect(promise.isCancellable()).to.be.true
280 | promise.catch Promise.CancellationError, (err) ->
281 | done()
282 | promise.cancel()
283 |
284 | it "should return the re.exec match of the matching line", (done) ->
285 | stream = new Stream.PassThrough
286 | parser = new Parser stream
287 | parser.searchLine /za(p)/
288 | .then (line) ->
289 | expect(line[0]).to.equal 'zap'
290 | expect(line[1]).to.equal 'p'
291 | expect(line.input).to.equal 'zip zap'
292 | done()
293 | stream.write 'foo bar\nzip zap\npip pop\n'
294 |
295 | it "should reject with Parser.PrematureEOFError if stream ends
296 | before a line is found", (done) ->
297 | stream = new Stream.PassThrough
298 | parser = new Parser stream
299 | parser.searchLine /nope/
300 | .catch Parser.PrematureEOFError, (err) ->
301 | done()
302 | stream.write 'foo bar'
303 | stream.end()
304 |
305 | describe 'readLine()', ->
306 |
307 | it "should return a cancellable Promise", (done) ->
308 | stream = new Stream.PassThrough
309 | parser = new Parser stream
310 | promise = parser.readLine()
311 | expect(promise).to.be.an.instanceOf Promise
312 | expect(promise.isCancellable()).to.be.true
313 | promise.catch Promise.CancellationError, (err) ->
314 | done()
315 | promise.cancel()
316 |
317 | it "should skip a line terminated by \\n", (done) ->
318 | stream = new Stream.PassThrough
319 | parser = new Parser stream
320 | parser.readLine()
321 | .then ->
322 | parser.readBytes 7
323 | .then (buf) ->
324 | expect(buf.toString()).to.equal 'zip zap'
325 | done()
326 | stream.write 'foo bar\nzip zap\npip pop'
327 |
328 | it "should return skipped line", (done) ->
329 | stream = new Stream.PassThrough
330 | parser = new Parser stream
331 | parser.readLine()
332 | .then (buf) ->
333 | expect(buf.toString()).to.equal 'foo bar'
334 | done()
335 | stream.write 'foo bar\nzip zap\npip pop'
336 |
337 | it "should strip trailing \\r", (done) ->
338 | stream = new Stream.PassThrough
339 | parser = new Parser stream
340 | parser.readLine()
341 | .then (buf) ->
342 | expect(buf.toString()).to.equal 'foo bar'
343 | done()
344 | stream.write 'foo bar\r\n'
345 |
346 | it "should reject with Parser.PrematureEOFError if stream ends
347 | before a line is found", (done) ->
348 | stream = new Stream.PassThrough
349 | parser = new Parser stream
350 | parser.readLine()
351 | .catch Parser.PrematureEOFError, (err) ->
352 | done()
353 | stream.write 'foo bar'
354 | stream.end()
355 |
356 | describe 'readUntil(code)', ->
357 |
358 | it "should return a cancellable Promise", (done) ->
359 | stream = new Stream.PassThrough
360 | parser = new Parser stream
361 | promise = parser.readUntil 0xa0
362 | expect(promise).to.be.an.instanceOf Promise
363 | expect(promise.isCancellable()).to.be.true
364 | promise.catch Promise.CancellationError, (err) ->
365 | done()
366 | promise.cancel()
367 |
368 | it "should return any characters before given value", (done) ->
369 | stream = new Stream.PassThrough
370 | parser = new Parser stream
371 | parser.readUntil 'p'.charCodeAt 0
372 | .then (buf) ->
373 | expect(buf.toString()).to.equal 'foo bar\nzi'
374 | done()
375 | stream.write 'foo bar\nzip zap\npip pop'
376 |
377 | it "should reject with Parser.PrematureEOFError if stream ends
378 | before a line is found", (done) ->
379 | stream = new Stream.PassThrough
380 | parser = new Parser stream
381 | parser.readUntil 'z'.charCodeAt 0
382 | .catch Parser.PrematureEOFError, (err) ->
383 | done()
384 | stream.write 'ho ho'
385 | stream.end()
386 |
387 | describe 'raw()', ->
388 |
389 | it "should return the resumed raw stream", (done) ->
390 | stream = new Stream.PassThrough
391 | parser = new Parser stream
392 | raw = parser.raw()
393 | expect(raw).to.equal stream
394 | raw.on 'data', ->
395 | done()
396 | raw.write 'foo'
397 |
398 | describe 'unexpected(data, expected)', ->
399 |
400 | it "should reject with Parser.UnexpectedDataError", (done) ->
401 | stream = new Stream.PassThrough
402 | parser = new Parser stream
403 | parser.unexpected('foo', "'bar' or end of stream")
404 | .catch Parser.UnexpectedDataError, (err) ->
405 | expect(err.message).to.equal "Unexpected 'foo', was expecting 'bar'
406 | or end of stream"
407 | expect(err.unexpected).to.equal 'foo'
408 | expect(err.expected).to.equal "'bar' or end of stream"
409 | done()
410 |
--------------------------------------------------------------------------------
/test/adb/protocol.coffee:
--------------------------------------------------------------------------------
1 | {expect} = require 'chai'
2 |
3 | Protocol = require '../../src/adb/protocol'
4 |
5 | describe 'Protocol', ->
6 |
7 | it "should expose a 'FAIL' property", (done) ->
8 | expect(Protocol).to.have.property 'FAIL'
9 | expect(Protocol.FAIL).to.equal 'FAIL'
10 | done()
11 |
12 | it "should expose an 'OKAY' property", (done) ->
13 | expect(Protocol).to.have.property 'OKAY'
14 | expect(Protocol.OKAY).to.equal 'OKAY'
15 | done()
16 |
17 | describe '@decodeLength(length)', ->
18 |
19 | it "should return a Number", (done) ->
20 | expect(Protocol.decodeLength '0x0046').to.be.a 'number'
21 | done()
22 |
23 | it "should accept a hexadecimal string", (done) ->
24 | expect(Protocol.decodeLength '0x5887').to.equal 0x5887
25 | done()
26 |
27 | describe '@encodeLength(length)', ->
28 |
29 | it "should return a String", (done) ->
30 | expect(Protocol.encodeLength 27).to.be.a 'string'
31 | done()
32 |
33 | it "should return a valid hexadecimal number", (done) ->
34 | expect(parseInt Protocol.encodeLength(32), 16).to.equal 32
35 | expect(parseInt Protocol.encodeLength(9999), 16).to.equal 9999
36 | done()
37 |
38 | it "should return uppercase hexadecimal digits", (done) ->
39 | expect(Protocol.encodeLength(0x0abc)).to.equal '0ABC'
40 | done()
41 |
42 | it "should pad short values with zeroes for a 4-byte size", (done) ->
43 | expect(Protocol.encodeLength 1).to.have.length 4
44 | expect(Protocol.encodeLength 2).to.have.length 4
45 | expect(Protocol.encodeLength 57).to.have.length 4
46 | done()
47 |
48 | it "should return 0000 for 0 length", (done) ->
49 | expect(Protocol.encodeLength 0).to.equal '0000'
50 | done()
51 |
52 | describe '@encodeData(data)', ->
53 |
54 | it "should return a Buffer", (done) ->
55 | expect(Protocol.encodeData new Buffer '').to.be.an.instanceOf Buffer
56 | done()
57 |
58 | it "should accept a string or a Buffer", (done) ->
59 | expect(Protocol.encodeData '').to.be.an.instanceOf Buffer
60 | expect(Protocol.encodeData new Buffer '').to.be.an.instanceOf Buffer
61 | done()
62 |
63 | it "should prefix data with length", (done) ->
64 | data = Protocol.encodeData new Buffer 0x270F
65 | expect(data).to.have.length 0x270F + 4
66 | expect(data.toString 'ascii', 0, 4).to.equal '270F'
67 | done()
68 |
--------------------------------------------------------------------------------
/test/adb/sync.coffee:
--------------------------------------------------------------------------------
1 | Fs = require 'fs'
2 | Stream = require 'stream'
3 | Promise = require 'bluebird'
4 | Sinon = require 'sinon'
5 | Chai = require 'chai'
6 | Chai.use require 'sinon-chai'
7 | {expect, assert} = Chai
8 |
9 | Adb = require '../../src/adb'
10 | Sync = require '../../src/adb/sync'
11 | Stats = require '../../src/adb/sync/stats'
12 | Entry = require '../../src/adb/sync/entry'
13 | PushTransfer = require '../../src/adb/sync/pushtransfer'
14 | PullTransfer = require '../../src/adb/sync/pulltransfer'
15 | MockConnection = require '../mock/connection'
16 |
17 | # This test suite is a bit special in that it requires a connected Android
18 | # device (or many devices). All will be tested.
19 | describe 'Sync', ->
20 | # By default, skip tests that require a device.
21 | dt = xit
22 | dt = it if process.env.RUN_DEVICE_TESTS
23 |
24 | SURELY_EXISTING_FILE = '/system/build.prop'
25 | SURELY_EXISTING_PATH = '/'
26 | SURELY_NONEXISTING_PATH = '/non-existing-path'
27 | SURELY_WRITABLE_FILE = '/data/local/tmp/_sync.test'
28 |
29 | client = null
30 | deviceList = null
31 |
32 | forEachSyncDevice = (iterator, done) ->
33 | assert deviceList.length > 0,
34 | 'At least one connected Android device is required'
35 |
36 | promises = deviceList.map (device) ->
37 | client.syncService device.id
38 | .then (sync) ->
39 | expect(sync).to.be.an.instanceof Sync
40 | Promise.cast iterator sync
41 | .finally ->
42 | sync.end()
43 |
44 | Promise.all promises
45 | .then ->
46 | done()
47 | .catch done
48 |
49 | before (done) ->
50 | client = Adb.createClient()
51 | client.listDevices()
52 | .then (devices) ->
53 | deviceList = devices
54 | done()
55 |
56 | describe 'end()', ->
57 |
58 | it "should end the sync connection", ->
59 | conn = new MockConnection
60 | sync = new Sync conn
61 | Sinon.stub conn, 'end'
62 | sync.end()
63 | expect(conn.end).to.have.been.called
64 |
65 | describe 'push(contents, path[, mode])', ->
66 |
67 | it "should call pushStream when contents is a Stream", ->
68 | conn = new MockConnection
69 | sync = new Sync conn
70 | stream = new Stream.PassThrough
71 | Sinon.stub sync, 'pushStream'
72 | sync.push stream, 'foo'
73 | expect(sync.pushStream).to.have.been.called
74 |
75 | it "should call pushFile when contents is a String", ->
76 | conn = new MockConnection
77 | sync = new Sync conn
78 | stream = new Stream.PassThrough
79 | Sinon.stub sync, 'pushFile'
80 | sync.push __filename, 'foo'
81 | expect(sync.pushFile).to.have.been.called
82 |
83 | it "should return a PushTransfer instance", ->
84 | conn = new MockConnection
85 | sync = new Sync conn
86 | stream = new Stream.PassThrough
87 | transfer = sync.push stream, 'foo'
88 | expect(transfer).to.be.an.instanceof PushTransfer
89 | transfer.cancel()
90 |
91 | describe 'pushStream(stream, path[, mode])', ->
92 |
93 | it "should return a PushTransfer instance", ->
94 | conn = new MockConnection
95 | sync = new Sync conn
96 | stream = new Stream.PassThrough
97 | transfer = sync.pushStream stream, 'foo'
98 | expect(transfer).to.be.an.instanceof PushTransfer
99 | transfer.cancel()
100 |
101 | dt "should be able to push >65536 byte chunks without error", (done) ->
102 | forEachSyncDevice (sync) ->
103 | new Promise (resolve, reject) ->
104 | stream = new Stream.PassThrough
105 | content = new Buffer 1000000
106 | transfer = sync.pushStream stream, SURELY_WRITABLE_FILE
107 | transfer.on 'error', reject
108 | transfer.on 'end', resolve
109 | stream.write content
110 | stream.end()
111 | , done
112 |
113 | describe 'pull(path)', ->
114 |
115 | dt "should retrieve the same content pushStream() pushed", (done) ->
116 | forEachSyncDevice (sync) ->
117 | new Promise (resolve, reject) ->
118 | stream = new Stream.PassThrough
119 | content = 'ABCDEFGHI' + Date.now()
120 | transfer = sync.pushStream stream, SURELY_WRITABLE_FILE
121 | expect(transfer).to.be.an.instanceof PushTransfer
122 | transfer.on 'error', reject
123 | transfer.on 'end', ->
124 | transfer = sync.pull SURELY_WRITABLE_FILE
125 | expect(transfer).to.be.an.instanceof PullTransfer
126 | transfer.on 'error', reject
127 | transfer.on 'readable', ->
128 | while chunk = transfer.read()
129 | expect(chunk).to.not.be.null
130 | expect(chunk.toString()).to.equal content
131 | return resolve()
132 | stream.write content
133 | stream.end()
134 | , done
135 |
136 | dt "should emit error for non-existing files", (done) ->
137 | forEachSyncDevice (sync) ->
138 | new Promise (resolve, reject) ->
139 | transfer = sync.pull SURELY_NONEXISTING_PATH
140 | transfer.on 'error', resolve
141 | , done
142 |
143 | dt "should return a PullTransfer instance", (done) ->
144 | forEachSyncDevice (sync) ->
145 | rval = sync.pull SURELY_EXISTING_FILE
146 | expect(rval).to.be.an.instanceof PullTransfer
147 | rval.cancel()
148 | , done
149 |
150 | describe 'Stream', ->
151 |
152 | dt "should emit 'end' when pull is done", (done) ->
153 | forEachSyncDevice (sync) ->
154 | new Promise (resolve, reject) ->
155 | transfer = sync.pull SURELY_EXISTING_FILE
156 | transfer.on 'error', reject
157 | transfer.on 'end', resolve
158 | transfer.resume()
159 | , done
160 |
161 | describe 'stat(path)', ->
162 |
163 | dt "should return a Promise", (done) ->
164 | forEachSyncDevice (sync) ->
165 | rval = sync.stat SURELY_EXISTING_PATH
166 | expect(rval).to.be.an.instanceof Promise
167 | rval
168 | , done
169 |
170 | dt "should call with an ENOENT error if the path does not exist", (done) ->
171 | forEachSyncDevice (sync) ->
172 | sync.stat SURELY_NONEXISTING_PATH
173 | .then (stats) ->
174 | throw new Error 'Should not reach success branch'
175 | .catch (err) ->
176 | expect(err).to.be.an.instanceof Error
177 | expect(err.code).to.equal 'ENOENT'
178 | expect(err.errno).to.equal 34
179 | expect(err.path).to.equal SURELY_NONEXISTING_PATH
180 | , done
181 |
182 | dt "should call with an fs.Stats instance for an existing path", (done) ->
183 | forEachSyncDevice (sync) ->
184 | sync.stat SURELY_EXISTING_PATH
185 | .then (stats) ->
186 | expect(stats).to.be.an.instanceof Fs.Stats
187 | , done
188 |
189 | describe 'Stats', ->
190 |
191 | it "should implement Fs.Stats", (done) ->
192 | expect(new Stats 0, 0, 0).to.be.an.instanceof Fs.Stats
193 | done()
194 |
195 | dt "should set the `.mode` property for isFile() etc", (done) ->
196 | forEachSyncDevice (sync) ->
197 | sync.stat SURELY_EXISTING_FILE
198 | .then (stats) ->
199 | expect(stats).to.be.an.instanceof Fs.Stats
200 | expect(stats.mode).to.be.above 0
201 | expect(stats.isFile()).to.be.true
202 | expect(stats.isDirectory()).to.be.false
203 | , done
204 |
205 | dt "should set the `.size` property", (done) ->
206 | forEachSyncDevice (sync) ->
207 | sync.stat SURELY_EXISTING_FILE
208 | .then (stats) ->
209 | expect(stats).to.be.an.instanceof Fs.Stats
210 | expect(stats.isFile()).to.be.true
211 | expect(stats.size).to.be.above 0
212 | , done
213 |
214 | dt "should set the `.mtime` property", (done) ->
215 | forEachSyncDevice (sync) ->
216 | sync.stat SURELY_EXISTING_FILE
217 | .then (stats) ->
218 | expect(stats).to.be.an.instanceof Fs.Stats
219 | expect(stats.mtime).to.be.an.instanceof Date
220 | , done
221 |
222 | describe 'Entry', ->
223 |
224 | it "should implement Stats", (done) ->
225 | expect(new Entry 'foo', 0, 0, 0).to.be.an.instanceof Stats
226 | done()
227 |
228 | dt "should set the `.name` property", (done) ->
229 | forEachSyncDevice (sync) ->
230 | sync.readdir SURELY_EXISTING_PATH
231 | .then (files) ->
232 | expect(files).to.be.an 'Array'
233 | files.forEach (file) ->
234 | expect(file.name).to.not.be.null
235 | expect(file).to.be.an.instanceof Entry
236 | , done
237 |
238 | dt "should set the Stats properties", (done) ->
239 | forEachSyncDevice (sync) ->
240 | sync.readdir SURELY_EXISTING_PATH
241 | .then (files) ->
242 | expect(files).to.be.an 'Array'
243 | files.forEach (file) ->
244 | expect(file.mode).to.not.be.null
245 | expect(file.size).to.not.be.null
246 | expect(file.mtime).to.not.be.null
247 | , done
248 |
--------------------------------------------------------------------------------
/test/adb/tracker.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Sinon = require 'sinon'
3 | Chai = require 'chai'
4 | Chai.use require 'sinon-chai'
5 | {expect} = require 'chai'
6 |
7 | Parser = require '../../src/adb/parser'
8 | Tracker = require '../../src/adb/tracker'
9 | Protocol = require '../../src/adb/protocol'
10 | HostTrackDevicesCommand = require '../../src/adb/command/host/trackdevices'
11 |
12 | describe 'Tracker', ->
13 |
14 | beforeEach ->
15 | @writer = new Stream.PassThrough
16 | @conn =
17 | parser: new Parser @writer
18 | end: ->
19 | @cmd = new HostTrackDevicesCommand @conn
20 | @tracker = new Tracker @cmd
21 |
22 | it "should emit 'add' when a device is added", (done) ->
23 | spy = Sinon.spy()
24 | @tracker.on 'add', spy
25 | device1 =
26 | id: 'a',
27 | type: 'device'
28 | device2 =
29 | id: 'b',
30 | type: 'device'
31 | @tracker.update [device1, device2]
32 | expect(spy).to.have.been.calledTwice
33 | expect(spy).to.have.been.calledWith device1
34 | expect(spy).to.have.been.calledWith device2
35 | done()
36 |
37 | it "should emit 'remove' when a device is removed", (done) ->
38 | spy = Sinon.spy()
39 | @tracker.on 'remove', spy
40 | device1 =
41 | id: 'a',
42 | type: 'device'
43 | device2 =
44 | id: 'b',
45 | type: 'device'
46 | @tracker.update [device1, device2]
47 | @tracker.update [device1]
48 | expect(spy).to.have.been.calledOnce
49 | expect(spy).to.have.been.calledWith device2
50 | done()
51 |
52 | it "should emit 'change' when a device changes", (done) ->
53 | spy = Sinon.spy()
54 | @tracker.on 'change', spy
55 | deviceOld =
56 | id: 'a',
57 | type: 'device'
58 | deviceNew =
59 | id: 'a',
60 | type: 'offline'
61 | @tracker.update [deviceOld]
62 | @tracker.update [deviceNew]
63 | expect(spy).to.have.been.calledOnce
64 | expect(spy).to.have.been.calledWith deviceNew, deviceOld
65 | done()
66 |
67 | it "should emit 'changeSet' with all changes", (done) ->
68 | spy = Sinon.spy()
69 | @tracker.on 'changeSet', spy
70 | device1 =
71 | id: 'a',
72 | type: 'device'
73 | device2 =
74 | id: 'b',
75 | type: 'device'
76 | device3 =
77 | id: 'c',
78 | type: 'device'
79 | device3New =
80 | id: 'c',
81 | type: 'offline'
82 | device4 =
83 | id: 'd',
84 | type: 'offline'
85 | @tracker.update [device1, device2, device3]
86 | @tracker.update [device1, device3New, device4]
87 | expect(spy).to.have.been.calledTwice
88 | expect(spy).to.have.been.calledWith
89 | added: [device1, device2, device3]
90 | changed: []
91 | removed: []
92 | expect(spy).to.have.been.calledWith
93 | added: [device4]
94 | changed: [device3New]
95 | removed: [device2]
96 | done()
97 |
98 | it "should emit 'error' and 'end' when connection ends", (done) ->
99 | @tracker.on 'error', =>
100 | @tracker.on 'end', ->
101 | done()
102 | @writer.end()
103 |
104 | it "should read devices from socket", (done) ->
105 | spy = Sinon.spy()
106 | @tracker.on 'changeSet', spy
107 | device1 =
108 | id: 'a',
109 | type: 'device'
110 | device2 =
111 | id: 'b',
112 | type: 'device'
113 | device3 =
114 | id: 'c',
115 | type: 'device'
116 | device3New =
117 | id: 'c',
118 | type: 'offline'
119 | device4 =
120 | id: 'd',
121 | type: 'offline'
122 | @writer.write Protocol.encodeData """
123 | a\tdevice
124 | b\tdevice
125 | c\tdevice
126 | """
127 | @writer.write Protocol.encodeData """
128 | a\tdevice
129 | c\toffline
130 | d\toffline
131 | """
132 | setTimeout ->
133 | expect(spy).to.have.been.calledTwice
134 | expect(spy).to.have.been.calledWith
135 | added: [device1, device2, device3]
136 | changed: []
137 | removed: []
138 | expect(spy).to.have.been.calledWith
139 | added: [device4]
140 | changed: [device3New]
141 | removed: [device2]
142 | done()
143 | , 10
144 |
145 | describe 'end()', ->
146 |
147 | it "should close the connection", (done) ->
148 | Sinon.spy @conn.parser, 'end'
149 | @tracker.on 'end', =>
150 | expect(@conn.parser.end).to.have.been.calledOnce
151 | done()
152 | @tracker.end()
153 |
154 | it "should not cause an error to be emit", (done) ->
155 | spy = Sinon.spy()
156 | @tracker.on 'error', spy
157 | @tracker.on 'end', ->
158 | expect(spy).to.not.have.been.called
159 | done()
160 | @tracker.end()
161 |
--------------------------------------------------------------------------------
/test/adb/util.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 | Promise = require 'bluebird'
3 | Sinon = require 'sinon'
4 | Chai = require 'chai'
5 | Chai.use require 'sinon-chai'
6 | {expect} = require 'chai'
7 |
8 | util = require '../../src/adb/util'
9 |
10 | describe 'util', ->
11 |
12 | describe 'readAll(stream)', ->
13 |
14 | it "should return a cancellable Promise", (done) ->
15 | stream = new Stream.PassThrough
16 | promise = util.readAll(stream)
17 | expect(promise).to.be.an.instanceOf Promise
18 | expect(promise.isCancellable()).to.be.true
19 | promise.catch Promise.CancellationError, (err) ->
20 | done()
21 | promise.cancel()
22 |
23 | it "should read all remaining content until the stream ends", (done) ->
24 | stream = new Stream.PassThrough
25 | util.readAll(stream)
26 | .then (buf) ->
27 | expect(buf.length).to.equal 3
28 | expect(buf.toString()).to.equal 'FOO'
29 | done()
30 | stream.write 'F'
31 | stream.write 'O'
32 | stream.write 'O'
33 | stream.end()
34 |
--------------------------------------------------------------------------------
/test/mock/connection.coffee:
--------------------------------------------------------------------------------
1 | Parser = require '../../src/adb/parser'
2 | MockDuplex = require './duplex'
3 |
4 | class MockConnection
5 | constructor: ->
6 | @socket = new MockDuplex
7 | @parser = new Parser @socket
8 |
9 | end: ->
10 | @socket.causeEnd()
11 | return this
12 |
13 | write: ->
14 | @socket.write.apply @socket, arguments
15 | return this
16 |
17 | module.exports = MockConnection
18 |
--------------------------------------------------------------------------------
/test/mock/duplex.coffee:
--------------------------------------------------------------------------------
1 | Stream = require 'stream'
2 |
3 | class MockDuplex extends Stream.Duplex
4 | _read: (size) ->
5 |
6 | _write: (chunk, encoding, callback) ->
7 | @emit 'write', chunk, encoding, callback
8 | callback null
9 | return
10 |
11 | causeRead: (chunk) ->
12 | unless Buffer.isBuffer chunk
13 | chunk = new Buffer chunk
14 | this.push chunk
15 | return
16 |
17 | causeEnd: ->
18 | this.push null
19 | return
20 |
21 | end: ->
22 | this.causeEnd() # In order to better emulate socket streams
23 | Stream.Duplex.prototype.end.apply this, arguments
24 |
25 | module.exports = MockDuplex
26 |
--------------------------------------------------------------------------------