UNPKG

3.5 kBJavaScriptView Raw
1/* @flow */
2
3// Logic of recieving data from trezor
4// Logic of "call" is broken to two parts - sending and recieving
5
6import {MessageDecoder} from "./protobuf/message_decoder.js";
7import {ByteBuffer} from "protobufjs";
8import type {Messages} from "./protobuf/messages.js";
9import type {MessageFromTrezor} from "../transport";
10
11const MESSAGE_HEADER_BYTE: number = 0x23;
12
13// input that might or might not be fully parsed yet
14class PartiallyParsedInput {
15 // Message type number
16 typeNumber: number;
17 // Expected length of the raq message, in bytes
18 expectedLength: number;
19 // Buffer with the beginning of message; can be non-complete and WILL be modified
20 // during the object's lifetime
21 buffer: ByteBuffer;
22 constructor(typeNumber: number, length: number) {
23 this.typeNumber = typeNumber;
24 this.expectedLength = length;
25 this.buffer = new ByteBuffer(length);
26 }
27 isDone(): boolean {
28 return (this.buffer.offset >= this.expectedLength);
29 }
30 append(buffer: ByteBuffer):void {
31 this.buffer.append(buffer);
32 }
33 arrayBuffer(): ArrayBuffer {
34 const byteBuffer: ByteBuffer = this.buffer;
35 byteBuffer.reset();
36 return byteBuffer.toArrayBuffer();
37 }
38}
39
40// Parses first raw input that comes from Trezor and returns some information about the whole message.
41function parseFirstInput(bytes: ArrayBuffer): PartiallyParsedInput {
42 // convert to ByteBuffer so it's easier to read
43 const byteBuffer: ByteBuffer = ByteBuffer.concat([bytes]);
44
45 // checking first two bytes
46 const sharp1: number = byteBuffer.readByte();
47 const sharp2: number = byteBuffer.readByte();
48 if (sharp1 !== MESSAGE_HEADER_BYTE || sharp2 !== MESSAGE_HEADER_BYTE) {
49 throw new Error(`Didn't receive expected header signature.`);
50 }
51
52 // reading things from header
53 const type: number = byteBuffer.readUint16();
54 const length: number = byteBuffer.readUint32();
55
56 // creating a new buffer with the right size
57 const res: PartiallyParsedInput = new PartiallyParsedInput(type, length);
58 res.append(byteBuffer);
59 return res;
60}
61
62// If the whole message wasn't loaded in the first input, loads more inputs until everything is loaded.
63// note: the return value is not at all important since it's still the same parsedinput
64async function receiveRest(
65 parsedInput: PartiallyParsedInput,
66 receiver: () => Promise<ArrayBuffer>
67): Promise<void> {
68 if (parsedInput.isDone()) {
69 return;
70 }
71 const data = await receiver();
72
73 // sanity check
74 if (data == null) {
75 throw new Error(`Received no data.`);
76 }
77
78 parsedInput.append(data);
79 return receiveRest(parsedInput, receiver);
80}
81
82// Receives the whole message as a raw data buffer (but without headers or type info)
83async function receiveBuffer(
84 receiver: () => Promise<ArrayBuffer>
85): Promise<PartiallyParsedInput> {
86 const data = await receiver();
87 const partialInput: PartiallyParsedInput = parseFirstInput(data);
88
89 await receiveRest(partialInput, receiver);
90 return partialInput;
91}
92
93// Reads data from device and returns decoded message, that can be sent back to trezor.js
94export async function receiveAndParse(
95 messages: Messages,
96 receiver: () => Promise<ArrayBuffer>
97): Promise<MessageFromTrezor> {
98 const received = await receiveBuffer(receiver);
99 const typeId: number = received.typeNumber;
100 const buffer: ArrayBuffer = received.arrayBuffer();
101 const decoder: MessageDecoder = new MessageDecoder(messages, typeId, buffer);
102 return {
103 message: decoder.decodedJSON(),
104 type: decoder.messageName(),
105 };
106}