@trezor/transport
Version:
Low level library facilitating protocol buffers based communication with Trezor devices
158 lines • 5.94 kB
JavaScript
"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 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 });
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, 1000);
});
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: abstract_1.DEVICE_TYPE.TypeEmulator, product: 0, vendor: 0 }
: 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, _first, _signal) {
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