"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const crypto = __importStar(require("crypto"));
const debug_1 = __importDefault(require("debug"));
const bluebird_1 = __importDefault(require("bluebird"));
const packetreader_1 = __importDefault(require("./packetreader"));
const rollingcounter_1 = __importDefault(require("./rollingcounter"));
const packet_1 = __importDefault(require("./packet"));
const auth_1 = __importDefault(require("../auth"));
const servicemap_1 = __importDefault(require("./servicemap"));
const service_1 = __importDefault(require("./service"));
const debug = (0, debug_1.default)('adb:tcpusb:socket');
const UINT32_MAX = 0xffffffff;
const UINT16_MAX = 0xffff;
const AUTH_TOKEN = 1;
const AUTH_SIGNATURE = 2;
const AUTH_RSAPUBLICKEY = 3;
const TOKEN_LENGTH = 20;
class AuthError extends Error {
    constructor(message) {
        super(message);
        Object.setPrototypeOf(this, AuthError.prototype);
        this.name = 'AuthError';
        Error.captureStackTrace(this, Socket.AuthError);
    }
}
class UnauthorizedError extends Error {
    constructor() {
        super('Unauthorized access');
        Object.setPrototypeOf(this, UnauthorizedError.prototype);
        this.name = 'UnauthorizedError';
        Error.captureStackTrace(this, Socket.UnauthorizedError);
    }
}
class Socket extends events_1.EventEmitter {
    constructor(client, serial, socket, options = {}) {
        super();
        this.client = client;
        this.serial = serial;
        this.socket = socket;
        this.options = options;
        this.ended = false;
        this.authorized = false;
        this.syncToken = new rollingcounter_1.default(UINT32_MAX);
        this.remoteId = new rollingcounter_1.default(UINT32_MAX);
        this.services = new servicemap_1.default();
        this.version = 1;
        this.maxPayload = 4096;
        let base;
        (base = this.options).auth || (base.auth = () => bluebird_1.default.resolve(true));
        this.socket.setNoDelay(true);
        this.reader = new packetreader_1.default(this.socket)
            .on('packet', this._handle.bind(this))
            .on('error', (err) => {
            debug(`PacketReader error: ${err.message}`);
            return this.end();
        })
            .on('end', this.end.bind(this));
        this.remoteAddress = this.socket.remoteAddress;
        this.token = undefined;
        this.signature = undefined;
    }
    end() {
        if (this.ended) {
            return this;
        }
        // End services first so that they can send a final payload before FIN.
        this.services.end();
        this.socket.end();
        this.ended = true;
        this.emit('end');
        return this;
    }
    _error(err) {
        this.emit('error', err);
        return this.end();
    }
    _handle(packet) {
        if (this.ended) {
            return bluebird_1.default.resolve(false);
        }
        this.emit('userActivity', packet);
        return bluebird_1.default.try(() => {
            switch (packet.command) {
                case packet_1.default.A_SYNC:
                    return bluebird_1.default.resolve(this._handleSyncPacket());
                case packet_1.default.A_CNXN:
                    return this._handleConnectionPacket(packet);
                case packet_1.default.A_OPEN:
                    return this._handleOpenPacket(packet).then((r) => !!r);
                case packet_1.default.A_OKAY:
                case packet_1.default.A_WRTE:
                case packet_1.default.A_CLSE:
                    return this._forwardServicePacket(packet).then((r) => !!r);
                case packet_1.default.A_AUTH:
                    return this._handleAuthPacket(packet);
                default:
                    throw new Error(`Unknown command ${packet.command}`);
            }
        })
            .catch(Socket.AuthError, () => {
            this.end();
            return false;
        })
            .catch(Socket.UnauthorizedError, () => {
            this.end();
            return false;
        })
            .catch((err) => {
            this._error(err);
            return false;
        });
    }
    _handleSyncPacket() {
        // No need to do anything?
        debug('I:A_SYNC');
        debug('O:A_SYNC');
        return this.write(packet_1.default.assemble(packet_1.default.A_SYNC, 1, this.syncToken.next()));
    }
    _handleConnectionPacket(packet) {
        debug('I:A_CNXN', packet);
        this.version = packet_1.default.swap32(packet.arg0);
        this.maxPayload = Math.min(UINT16_MAX, packet.arg1);
        return this._createToken().then((token) => {
            this.token = token;
            debug(`Created challenge '${this.token.toString('base64')}'`);
            debug('O:A_AUTH');
            return this.write(packet_1.default.assemble(packet_1.default.A_AUTH, AUTH_TOKEN, 0, this.token));
        });
    }
    _handleAuthPacket(packet) {
        debug('I:A_AUTH', packet);
        switch (packet.arg0) {
            case AUTH_SIGNATURE:
                // Store first signature, ignore the rest
                if (packet.data)
                    debug(`Received signature '${packet.data.toString('base64')}'`);
                if (!this.signature) {
                    this.signature = packet.data;
                }
                debug('O:A_AUTH');
                const b = this.write(packet_1.default.assemble(packet_1.default.A_AUTH, AUTH_TOKEN, 0, this.token));
                return bluebird_1.default.resolve(b);
            case AUTH_RSAPUBLICKEY:
                if (!this.signature) {
                    throw new Socket.AuthError('Public key sent before signature');
                }
                if (!packet.data || packet.data.length < 2) {
                    throw new Socket.AuthError('Empty RSA public key');
                }
                debug(`Received RSA public key '${packet.data.toString('base64')}'`);
                return auth_1.default.parsePublicKey(this._skipNull(packet.data).toString())
                    .then((key) => {
                    const digest = this.token.toString('binary');
                    const sig = this.signature.toString('binary');
                    if (!key.verify(digest, sig)) {
                        debug('Signature mismatch');
                        throw new Socket.AuthError('Signature mismatch');
                    }
                    debug('Signature verified');
                    return key;
                })
                    .then((key) => {
                    if (!this.options.auth)
                        return;
                    return this.options.auth(key).catch(() => {
                        debug('Connection rejected by user-defined auth handler');
                        throw new Socket.AuthError('Rejected by user-defined handler');
                    });
                })
                    .then(() => {
                    return this._deviceId();
                })
                    .then((id) => {
                    this.authorized = true;
                    debug('O:A_CNXN');
                    return this.write(packet_1.default.assemble(packet_1.default.A_CNXN, packet_1.default.swap32(this.version), this.maxPayload, id));
                });
            default:
                throw new Error(`Unknown authentication method ${packet.arg0}`);
        }
    }
    _handleOpenPacket(packet) {
        if (!this.authorized) {
            throw new Socket.UnauthorizedError();
        }
        const remoteId = packet.arg0;
        const localId = this.remoteId.next();
        if (!(packet.data && packet.data.length >= 2)) {
            throw new Error('Empty service name');
        }
        const name = this._skipNull(packet.data);
        debug(`Calling ${name}`);
        const service = new service_1.default(this.client, this.serial, localId, remoteId, this);
        return new bluebird_1.default((resolve, reject) => {
            service.on('error', reject);
            service.on('end', resolve);
            this.services.insert(localId, service);
            debug(`Handling ${this.services.count} services simultaneously`);
            return service.handle(packet);
        })
            .catch(() => true)
            .finally(() => {
            this.services.remove(localId);
            debug(`Handling ${this.services.count} services simultaneously`);
            return service.end();
        });
    }
    _forwardServicePacket(packet) {
        if (!this.authorized) {
            throw new Socket.UnauthorizedError();
        }
        const localId = packet.arg1;
        const service = this.services.get(localId);
        if (service) {
            return service.handle(packet);
        }
        else {
            debug('Received a packet to a service that may have been closed already');
            return Promise.resolve(false);
        }
    }
    write(chunk) {
        if (this.ended) {
            return false;
        }
        return this.socket.write(chunk);
    }
    _createToken() {
        return bluebird_1.default.promisify(crypto.randomBytes)(TOKEN_LENGTH);
    }
    _skipNull(data) {
        return data.slice(0, -1); // Discard null byte at end
    }
    _deviceId() {
        debug('Loading device properties to form a standard device ID');
        return this.client
            .getDevice(this.serial)
            .getProperties()
            .then(function (properties) {
            const id = (function () {
                const ref = ['ro.product.name', 'ro.product.model', 'ro.product.device'];
                const results = [];
                for (let i = 0, len = ref.length; i < len; i++) {
                    const prop = ref[i];
                    results.push(`${prop}=${properties[prop]};`);
                }
                return results;
            })().join('');
            return Buffer.from(`device::${id}\x00`);
        });
    }
}
Socket.AuthError = AuthError;
Socket.UnauthorizedError = UnauthorizedError;
exports.default = Socket;
//# sourceMappingURL=socket.js.map