@trezor/transport
Version:
Low level library facilitating protocol buffers based communication with Trezor devices
169 lines (168 loc) • 5.31 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 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