UNPKG

@trezor/transport

Version:

Low level library facilitating protocol buffers based communication with Trezor devices

284 lines 11.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AbstractApiTransport = void 0; const tslib_1 = require("tslib"); const protocol_1 = require("@trezor/protocol"); const abstract_1 = require("./abstract"); const constants_1 = require("../constants"); const ERRORS = tslib_1.__importStar(require("../errors")); const background_1 = require("../sessions/background"); const client_1 = require("../sessions/client"); const thp_1 = require("../thp"); const receive_1 = require("../utils/receive"); const send_1 = require("../utils/send"); class AbstractApiTransport extends abstract_1.AbstractTransport { sessionsClient; sessionsBackground; api; constructor({ api, ...rest }) { super(rest); this.api = api; this.sessionsBackground = new background_1.SessionsBackground(); this.sessionsClient = new client_1.SessionsClient(this.sessionsBackground); } init({ signal } = {}) { return this.scheduleAction(async () => { this.sessionsClient.setBackground(this.sessionsBackground); const handshakeRes = await this.sessionsClient.handshake(); this.stopped = !handshakeRes.success; return handshakeRes; }, { signal }); } listen() { if (this.listening) { return this.error({ error: ERRORS.ALREADY_LISTENING }); } this.api.listen(); this.listening = true; this.api.on('transport-interface-change', descriptors => { this.logger?.debug('new descriptors from api', descriptors); this.sessionsClient.enumerateDone({ descriptors, }); }); this.sessionsClient.on('descriptors', descriptors => { this.logger?.debug('new descriptors from background', descriptors); this.handleDescriptorsChange(descriptors); }); this.sessionsClient.on('releaseRequest', descriptor => { this.deviceEvents.emit(descriptor.path, { type: constants_1.TRANSPORT.DEVICE_REQUEST_RELEASE }); }); return this.success(undefined); } enumerate({ signal } = {}) { return this.scheduleAction(async (signal) => { const enumerateResult = await this.api.enumerate(signal); if (!enumerateResult.success) { return enumerateResult; } const descriptors = enumerateResult.payload; const enumerateDoneResponse = await this.sessionsClient.enumerateDone({ descriptors, }); return this.success(enumerateDoneResponse.payload.descriptors); }, { signal }); } acquire({ input, signal }) { return this.scheduleAction(async (signal) => { const { path } = input; const acquireIntentResponse = await this.sessionsClient.acquireIntent(input); if (!acquireIntentResponse.success) { return this.error({ error: acquireIntentResponse.error }); } const reset = !!input.previous; const openDeviceResult = await this.api.openDevice(acquireIntentResponse.payload.path, reset, signal); if (!openDeviceResult.success) { return openDeviceResult; } this.sessionsClient.acquireDone({ path, sessionOwner: this.id }); return this.success(acquireIntentResponse.payload.session); }, { signal }, [ERRORS.DEVICE_DISCONNECTED_DURING_ACTION, ERRORS.SESSION_WRONG_PREVIOUS]); } release({ path: _, session, signal }) { return this.scheduleAction(async () => { const releaseIntentResponse = await this.sessionsClient.releaseIntent({ session, }); if (!releaseIntentResponse.success) { return this.error({ error: releaseIntentResponse.error }); } await this.api.closeDevice(releaseIntentResponse.payload.path); await this.sessionsClient.releaseDone({ path: releaseIntentResponse.payload.path, }); return this.success(null); }, { signal }); } releaseSync(session) { this.sessionsClient.releaseIntent({ session }).then(res => { if (res.success) this.api.closeDevice(res.payload.path); }); } call({ session, name, data, protocol: customProtocol, thpState, signal, timeout, }) { return this.scheduleAction(async (signal) => { const handleError = (error) => { if (error === ERRORS.DEVICE_DISCONNECTED_DURING_ACTION) { this.enumerate(); } }; const getPathBySessionResponse = await this.sessionsClient.getPathBySession({ session, }); if (!getPathBySessionResponse.success) { if (getPathBySessionResponse.error === 'session not found') { return this.error({ error: ERRORS.DEVICE_DISCONNECTED_DURING_ACTION }); } return this.error({ error: ERRORS.UNEXPECTED_ERROR }); } const { path } = getPathBySessionResponse.payload; const protocol = customProtocol || protocol_1.v1; const bytes = (0, send_1.buildMessage)({ messages: this.messages, name, data, protocol, thpState, }); const [, chunkHeader] = protocol.getHeaders(bytes); const chunks = (0, send_1.createChunks)(bytes, chunkHeader, this.api.chunkSize); let progress = 0; const apiWrite = (chunk, attemptSignal) => { if (chunks.length > 1) { progress++; this.emit(constants_1.TRANSPORT.SEND_MESSAGE_PROGRESS, progress / chunks.length); } return this.api.write(path, chunk, attemptSignal || signal); }; const apiRead = (attemptSignal) => this.api.read(path, attemptSignal || signal); if (protocol.name === 'v2') { const callResult = await (0, thp_1.callThpMessage)({ thpState, chunks, apiWrite, apiRead, signal, logger: this.logger, }); if (!callResult.success) { handleError(callResult.error); return callResult; } thpState?.sync('send', name); const message = (0, thp_1.parseThpMessage)({ messages: this.messages, decoded: callResult.payload, thpState, }); thpState?.sync('recv', message.type); return this.success(message); } const sendResult = await (0, send_1.sendChunks)(chunks, apiWrite); if (!sendResult.success) { handleError(sendResult.error); return sendResult; } const readResult = await (0, receive_1.receiveAndParse)(this.messages, apiRead, protocol); if (!readResult.success) { handleError(readResult.error); return readResult; } return readResult; }, { signal, timeout }); } send({ data, session, name, protocol: customProtocol, thpState, signal, timeout, }) { return this.scheduleAction(async (signal) => { const getPathBySessionResponse = await this.sessionsClient.getPathBySession({ session, }); if (!getPathBySessionResponse.success) { return this.error({ error: getPathBySessionResponse.error }); } const { path } = getPathBySessionResponse.payload; const protocol = customProtocol || protocol_1.v1; const bytes = (0, send_1.buildMessage)({ messages: this.messages, name, data, protocol, thpState, }); const [_, chunkHeader] = protocol.getHeaders(bytes); const chunks = (0, send_1.createChunks)(bytes, chunkHeader, this.api.chunkSize); let progress = 0; const apiWrite = (chunk) => { if (chunks.length > 1) { progress++; this.emit(constants_1.TRANSPORT.SEND_MESSAGE_PROGRESS, progress / chunks.length); } return this.api.write(path, chunk, signal); }; let sendResult; if (protocol.name === 'v2') { sendResult = await (0, thp_1.sendThpMessage)({ thpState, skipAck: true, chunks, apiWrite, apiRead: attemptSignal => this.api.read(path, attemptSignal || signal), signal, logger: this.logger, }); } else { sendResult = await (0, send_1.sendChunks)(chunks, apiWrite); } if (!sendResult.success) { if (sendResult.error === ERRORS.DEVICE_DISCONNECTED_DURING_ACTION) { this.enumerate(); } } return sendResult; }, { signal, timeout }); } receive({ session, protocol: customProtocol, thpState, signal, timeout, }) { return this.scheduleAction(async (signal) => { const getPathBySessionResponse = await this.sessionsClient.getPathBySession({ session, }); if (!getPathBySessionResponse.success) { return this.error({ error: getPathBySessionResponse.error }); } const { path } = getPathBySessionResponse.payload; const apiRead = (attemptSignal) => this.api.read(path, attemptSignal || signal); const protocol = customProtocol || protocol_1.v1; if (protocol.name === 'v2') { const decoded = await (0, thp_1.receiveThpMessage)({ thpState, skipAck: true, apiWrite: (chunk, attemptSignal) => this.api.write(path, chunk, attemptSignal || signal), apiRead, signal, }); if (!decoded.success) { return decoded; } const message = (0, thp_1.parseThpMessage)({ messages: this.messages, decoded: decoded.payload, thpState, }); return this.success(message); } const message = await (0, receive_1.receiveAndParse)(this.messages, apiRead, protocol); if (!message.success) { if (message.error === ERRORS.DEVICE_DISCONNECTED_DURING_ACTION) { this.enumerate(); } } return message; }, { signal, timeout }); } releaseDevice(session) { return this.sessionsClient .getPathBySession({ session, }) .then(response => { if (response.success) { return this.api.closeDevice(response.payload.path); } return this.success(undefined); }); } stop() { super.stop(); this.api.on('transport-interface-change', () => { this.logger?.debug('device connected after transport stopped'); }); this.sessionsClient.dispose(); this.api.dispose(); } } exports.AbstractApiTransport = AbstractApiTransport; //# sourceMappingURL=abstractApi.js.map