UNPKG

4.19 kBJavaScriptView Raw
1/* @flow */
2
3// Logic of sending data to trezor
4//
5// Logic of "call" is broken to two parts - sending and recieving
6
7import * as ProtoBuf from "protobufjs";
8import {ByteBuffer} from "protobufjs";
9import type {Messages} from "./protobuf/messages.js";
10
11const HEADER_SIZE = 1 + 1 + 4 + 2;
12const MESSAGE_HEADER_BYTE: number = 0x23;
13const BUFFER_SIZE: number = 63;
14
15// Sends more buffers to device.
16async function sendBuffers(
17 sender: (data: ArrayBuffer) => Promise<void>,
18 buffers: Array<ArrayBuffer>
19): Promise<void> {
20 // eslint-disable-next-line prefer-const
21 for (let buffer of buffers) {
22 await sender(buffer);
23 }
24}
25
26// already built PB message
27class BuiltMessage {
28 message: ProtoBuf.Builder.Message;
29 type: number;
30
31 constructor(messages: Messages, // Builders, generated by reading config
32 name: string, // Name of the message
33 data: Object // data as "pure" object, from trezor.js
34 ) {
35 const Builder = messages.messagesByName[name];
36 if (Builder == null) {
37 throw new Error(`The message name ${name} is not found.`);
38 }
39
40 // cleans up stuff from angular and remove "null" that crashes in builder
41 cleanupInput(data);
42
43 if (data) {
44 this.message = new Builder(data);
45 } else {
46 this.message = new Builder();
47 }
48
49 this.type = messages.messageTypes[`MessageType_${name}`];
50 }
51
52 // encodes into "raw" data, but it can be too long and needs to be split into
53 // smaller buffers
54 _encodeLong(): Uint8Array {
55 const headerSize: number = HEADER_SIZE; // should be 8
56 const bytes: Uint8Array = new Uint8Array(this.message.encodeAB());
57 const fullSize: number = headerSize + bytes.length;
58
59 const encodedByteBuffer = new ByteBuffer(fullSize);
60
61 // first encode header
62
63 // 2*1 byte
64 encodedByteBuffer.writeByte(MESSAGE_HEADER_BYTE);
65 encodedByteBuffer.writeByte(MESSAGE_HEADER_BYTE);
66
67 // 2 bytes
68 encodedByteBuffer.writeUint16(this.type);
69
70 // 4 bytes (so 8 in total)
71 encodedByteBuffer.writeUint32(bytes.length);
72
73 // then put in the actual message
74 encodedByteBuffer.append(bytes);
75
76 // and convert to uint8 array
77 // (it can still be too long to send though)
78 const encoded: Uint8Array = new Uint8Array(encodedByteBuffer.buffer);
79
80 return encoded;
81 }
82
83 // encodes itself and splits into "nice" chunks
84 encode(): Array<ArrayBuffer> {
85 const bytes: Uint8Array = this._encodeLong();
86
87 const result: Array<ArrayBuffer> = [];
88 const size: number = BUFFER_SIZE;
89
90 // How many pieces will there actually be
91 const count: number = Math.floor((bytes.length - 1) / size) + 1;
92
93 // slice and dice
94 for (let i = 0; i < count; i++) {
95 const slice: Uint8Array = bytes.subarray(i * size, (i + 1) * size);
96 const newArray: Uint8Array = new Uint8Array(size);
97 newArray.set(slice);
98 result.push(newArray.buffer);
99 }
100
101 return result;
102 }
103}
104
105// Removes $$hashkey from angular and remove nulls
106function cleanupInput(message: Object): void {
107 delete message.$$hashKey;
108
109 for (const key in message) {
110 const value = message[key];
111 if (value == null) {
112 delete message[key];
113 } else {
114 if (Array.isArray(value)) {
115 value.forEach((i) => {
116 if (typeof i === `object`) {
117 cleanupInput(i);
118 }
119 });
120 }
121 if (typeof value === `object`) {
122 cleanupInput(value);
123 }
124 }
125 }
126}
127
128// Builds buffers to send.
129// messages: Builders, generated by reading config
130// name: Name of the message
131// data: Data to serialize, exactly as given by trezor.js
132// Returning buffers that will be sent to Trezor
133function buildBuffers(messages: Messages, name: string, data: Object): Array<ArrayBuffer> {
134 const message: BuiltMessage = new BuiltMessage(messages, name, data);
135 const encoded: Array<ArrayBuffer> = message.encode();
136 return encoded;
137}
138
139// Sends message to device.
140// Resolves iff everything gets sent
141export async function buildAndSend(
142 messages: Messages,
143 sender: (data: ArrayBuffer) => Promise<void>,
144 name: string,
145 data: Object
146): Promise<void> {
147 const buffers: Array<ArrayBuffer> = buildBuffers(messages, name, data);
148 return sendBuffers(sender, buffers);
149}