UNPKG

@trezor/transport

Version:

Low level library facilitating protocol buffers based communication with Trezor devices

169 lines (168 loc) 5.31 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.UdpApi = void 0; const tslib_1 = require("tslib"); const dgram_1 = tslib_1.__importDefault(require("dgram")); const utils_1 = require("@trezor/utils"); const abstract_1 = require("./abstract"); const constants_1 = require("../constants"); const ERRORS = tslib_1.__importStar(require("../errors")); const types_1 = require("../types"); const readMessageBuffer_1 = require("../utils/readMessageBuffer"); const PING = Buffer.from('PINGPING'); const PONG = Buffer.from('PONGPONG'); class UdpApi extends abstract_1.AbstractApi { chunkSize = 64; devices = []; listenAbortController = new AbortController(); interface = dgram_1.default.createSocket({ type: 'udp4', signal: this.listenAbortController.signal }); debugLink; readBuffer; constructor({ logger, debugLink }) { super({ logger, type: 'udp' }); this.debugLink = debugLink; this.readBuffer = (0, readMessageBuffer_1.readMessageBuffer)(); const onMessage = (message, info) => { if (message.compare(PONG) === 0) { return; } const id = `${info.address}:${info.port}`; this.readBuffer.onMessage(id, message); this.logger?.debug('udp: globalOnMessage log:', message.toString('hex')); }; this.interface.addListener('message', onMessage); } listen() { if (this.listening) return; this.listening = true; this.listenLoop(); } async listenLoop() { while (this.listening) { await (0, utils_1.resolveAfter)(500); if (!this.listening) break; await this.enumerate(this.listenAbortController.signal); } } write(path, buffer, signal) { const [hostname, port] = path.split(':'); return new Promise(resolve => { const listener = () => { resolve(this.error({ error: ERRORS.ABORTED_BY_SIGNAL })); }; signal?.addEventListener('abort', listener); let chunk; if (buffer.compare(PING) === 0) { chunk = buffer; } else { chunk = Buffer.alloc(this.chunkSize); buffer.copy(chunk); } this.interface.send(chunk, Number.parseInt(port, 10), hostname, err => { signal?.removeEventListener('abort', listener); if (signal?.aborted) { return; } if (err) { this.logger?.error(err.message); resolve(this.error({ error: ERRORS.INTERFACE_DATA_TRANSFER, message: err.message })); } resolve(this.success(undefined)); }); }); } read(path, signal) { return this.readBuffer.read(path, signal); } async ping(path, signal) { await this.write(path, PING, signal); if (signal?.aborted) { throw new Error(ERRORS.ABORTED_BY_SIGNAL); } const pinged = new Promise(resolve => { const onClear = () => { this.interface.removeListener('error', onError); this.interface.removeListener('message', onMessage); clearTimeout(timeout); signal?.removeEventListener('abort', onError); }; const onError = () => { resolve(false); onClear(); }; const onMessage = (message, _info) => { if (message.compare(PONG) === 0) { resolve(true); onClear(); } }; signal?.addEventListener('abort', onError); this.interface.addListener('error', onError); this.interface.addListener('message', onMessage); const timeout = setTimeout(onError, 4000); }); return pinged; } async enumerate(signal) { const paths = this.debugLink ? [(0, types_1.PathInternal)('127.0.0.1:21325')] : [(0, types_1.PathInternal)('127.0.0.1:21324')]; try { const enumerateResult = await Promise.all(paths.map(path => this.ping(path, signal).then(pinged => pinged ? { path, type: constants_1.DEVICE_TYPE.TypeEmulator, product: 0, vendor: 0, id: path, apiType: this.type } : undefined))).then(res => res.filter(utils_1.isNotUndefined)); this.handleDevicesChange(enumerateResult); return this.success(enumerateResult); } catch { this.handleDevicesChange([]); return this.error({ error: ERRORS.ABORTED_BY_SIGNAL }); } } handleDevicesChange(devices) { const [known, unknown] = (0, utils_1.arrayPartition)(devices, device => !!this.devices.find(d => d.path === device.path)); const [disconnected] = (0, utils_1.arrayPartition)(this.devices, device => !devices.find(d => d.path === device.path)); disconnected.forEach(d => this.readBuffer.cancelRead(d.path)); if (known.length !== this.devices.length || unknown.length > 0) { this.devices = devices; if (this.listening) { this.emit('transport-interface-change', this.devices); } } } openDevice(_path) { return Promise.resolve(this.success(undefined)); } closeDevice(path) { this.readBuffer.cancelRead(path); return Promise.resolve(this.success(undefined)); } dispose() { this.interface.removeAllListeners(); this.interface.close(); this.listening = false; this.listenAbortController.abort(); } } exports.UdpApi = UdpApi; //# sourceMappingURL=udp.js.map